From 7ad42a1f2edbdc922bde2e027570ca627190bbea Mon Sep 17 00:00:00 2001 From: Carter Myers <206+cmyers@users.noreply.github.mieweb.com> Date: Thu, 24 Jul 2025 10:24:42 -0700 Subject: [PATCH 1/6] Container Creation Front End Commit to push the new container creator website to the repository for review. --- create-a-container/container-creator.service | 14 ++ .../create-container-wrapper.sh | 218 ++++++++++++++++++ create-a-container/public/index.html | 66 ++++++ create-a-container/public/logo.png | Bin 0 -> 11044 bytes create-a-container/public/style.css | 77 +++++++ create-a-container/server.js | 135 +++++++++++ create-a-container/views/form.html | 95 ++++++++ create-a-container/views/logo.png | Bin 0 -> 11044 bytes create-a-container/views/status.html | 57 +++++ 9 files changed, 662 insertions(+) create mode 100644 create-a-container/container-creator.service create mode 100644 create-a-container/create-container-wrapper.sh create mode 100644 create-a-container/public/index.html create mode 100644 create-a-container/public/logo.png create mode 100644 create-a-container/public/style.css create mode 100644 create-a-container/server.js create mode 100644 create-a-container/views/form.html create mode 100644 create-a-container/views/logo.png create mode 100644 create-a-container/views/status.html diff --git a/create-a-container/container-creator.service b/create-a-container/container-creator.service new file mode 100644 index 00000000..ece707d6 --- /dev/null +++ b/create-a-container/container-creator.service @@ -0,0 +1,14 @@ +[Unit] +Description=Container Creator Node.js App +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/container-creator +ExecStart=/usr/bin/node /opt/container-creator/server.js +Restart=on-failure +Environment=NODE_ENV=production + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/create-a-container/create-container-wrapper.sh b/create-a-container/create-container-wrapper.sh new file mode 100644 index 00000000..9dc46de6 --- /dev/null +++ b/create-a-container/create-container-wrapper.sh @@ -0,0 +1,218 @@ +#!/bin/bash +# Wrapper for non-interactive container creation +# Reads all inputs from environment variables and validates them +# Exits with error messages if invalid/missing + +set -euo pipefail + +GH_ACTION="${GH_ACTION:-}" + +RESET="\033[0m" +BOLD="\033[1m" +MAGENTA='\033[35m' + +outputError() { + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e "${BOLD}${MAGENTA}❌ Script Failed. Exiting... ${RESET}" + echo -e "$1" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + exit 1 +} + +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +echo -e "${BOLD}${MAGENTA}📦 MIE Container Creation Script (Wrapper)${RESET}" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + +# Required variables, fail if not set or empty +: "${PROXMOX_USERNAME:?Environment variable PROXMOX_USERNAME is required}" +: "${PROXMOX_PASSWORD:?Environment variable PROXMOX_PASSWORD is required}" +: "${CONTAINER_NAME:?Environment variable CONTAINER_NAME is required}" +: "${CONTAINER_PASSWORD:?Environment variable CONTAINER_PASSWORD is required}" +: "${LINUX_DISTRIBUTION:?Environment variable LINUX_DISTRIBUTION is required}" +: "${HTTP_PORT:?Environment variable HTTP_PORT is required}" +: "${DEPLOY_ON_START:=n}" # default to "n" if not set + +# Convert container name and linux distribution to lowercase +CONTAINER_NAME="${CONTAINER_NAME,,}" +LINUX_DISTRIBUTION="${LINUX_DISTRIBUTION,,}" +DEPLOY_ON_START="${DEPLOY_ON_START,,}" + +# Validate Proxmox credentials using your Node.js authenticateUser +USER_AUTHENTICATED=$(node /root/bin/js/runner.js authenticateUser "$PROXMOX_USERNAME" "$PROXMOX_PASSWORD") +if [ "$USER_AUTHENTICATED" != "true" ]; then + outputError "Invalid Proxmox Credentials." +fi + +echo "🎉 Proxmox user '$PROXMOX_USERNAME' authenticated." + +# Validate container name: alphanumeric + dash only +if ! [[ "$CONTAINER_NAME" =~ ^[a-z0-9-]+$ ]]; then + outputError "Invalid container name: Only lowercase letters, numbers, and dashes are allowed." +fi + +# Check if hostname already exists remotely +HOST_NAME_EXISTS=$(ssh root@10.15.20.69 "node /etc/nginx/checkHostnameRunner.js checkHostnameExists ${CONTAINER_NAME}") +if [ "$HOST_NAME_EXISTS" == "true" ]; then + outputError "Container hostname '$CONTAINER_NAME' already exists." +fi +echo "✅ Container name '$CONTAINER_NAME' is available." + +# Validate container password length +if [ "${#CONTAINER_PASSWORD}" -lt 8 ]; then + outputError "Container password must be at least 8 characters." +fi + +# Validate Linux distribution choice +if [[ "$LINUX_DISTRIBUTION" != "debian" && "$LINUX_DISTRIBUTION" != "rocky" ]]; then + outputError "Linux distribution must be 'debian' or 'rocky'." +fi + +# Validate HTTP_PORT: integer between 80 and 60000 +if ! [[ "$HTTP_PORT" =~ ^[0-9]+$ ]] || [ "$HTTP_PORT" -lt 80 ] || [ "$HTTP_PORT" -gt 60000 ]; then + outputError "HTTP_PORT must be a number between 80 and 60000." +fi + +echo "✅ HTTP port set to $HTTP_PORT." + +# Public key optional +if [ -n "${PUBLIC_KEY-}" ]; then + # Validate public key format (simple check) + if echo "$PUBLIC_KEY" | ssh-keygen -l -f - &>/dev/null; then + AUTHORIZED_KEYS="/root/.ssh/authorized_keys" + echo "$PUBLIC_KEY" > "$AUTHORIZED_KEYS" + systemctl restart ssh + echo "$PUBLIC_KEY" > "/root/bin/ssh/temp_pubs/key_$(shuf -i 100000-999999 -n1).pub" + sudo /root/bin/ssh/publicKeyAppendJumpHost.sh "$PUBLIC_KEY" + echo "🔐 Public key added." + else + outputError "Invalid PUBLIC_KEY format." + fi +else + echo "ℹ️ No public key provided." +fi + +# Protocol list handling (optional) +PROTOCOL_BASE_FILE="protocol_list_$(shuf -i 100000-999999 -n 1).txt" +PROTOCOL_FILE="/root/bin/protocols/$PROTOCOL_BASE_FILE" +touch "$PROTOCOL_FILE" + +# --- Logic for named protocols from a list (existing) --- +if [[ "${USE_OTHER_PROTOCOLS-}" == "y" || "${USE_OTHER_PROTOCOLS-}" == "Y" ]]; then + if [ -z "${OTHER_PROTOCOLS_LIST-}" ]; then + outputError "USE_OTHER_PROTOCOLS is yes but OTHER_PROTOCOLS_LIST is empty." + fi + IFS=',' read -ra PROTOCOLS <<< "$OTHER_PROTOCOLS_LIST" + for PROTOCOL_NAME in "${PROTOCOLS[@]}"; do + PROTOCOL_NAME=$(echo "$PROTOCOL_NAME" | tr '[:lower:]' '[:upper:]') + FOUND=0 + while read -r line; do + PROTOCOL_ABBRV=$(echo "$line" | awk '{print $1}') + if [[ "$PROTOCOL_ABBRV" == "$PROTOCOL_NAME" ]]; then + echo "$line" >> "$PROTOCOL_FILE" + echo " ^|^e Protocol $PROTOCOL_NAME added." + FOUND=1 + break + fi + done < "/root/bin/protocols/master_protocol_list.txt" + if [ "$FOUND" -eq 0 ]; then + echo " ^}^l Protocol $PROTOCOL_NAME not found, skipping." + fi + done +fi + +# --- START: Added logic for single custom port --- +# Check if the OTHER_PORT variable is set and not empty +if [ -n "${OTHER_PORT-}" ]; then + # Validate that it's an integer + if [[ "$OTHER_PORT" =~ ^[0-9]+$ ]]; then + echo "TCP $OTHER_PORT" >> "$PROTOCOL_FILE" + echo "UDP $OTHER_PORT" >> "$PROTOCOL_FILE" + echo " ^|^e Custom port $OTHER_PORT (TCP/UDP) added." + else + echo " ^}^l Invalid custom port specified: $OTHER_PORT. Must be an integer. Skipping." + fi +fi + +# Deploy on start must be y or n +if [[ "$DEPLOY_ON_START" != "y" && "$DEPLOY_ON_START" != "n" ]]; then + outputError "DEPLOY_ON_START must be 'y' or 'n'." +fi + +if [ "$DEPLOY_ON_START" == "y" ]; then + source /root/bin/deploy-application.sh +fi + +# Send files to hypervisor (public keys, protocols, env vars, services) +send_file_to_hypervisor() { + local LOCAL_FILE="$1" + local REMOTE_FOLDER="$2" + if [ "$REMOTE_FOLDER" != "container-env-vars" ]; then + if [ -s "$LOCAL_FILE" ]; then + sftp root@10.15.0.4 < /dev/null +put $LOCAL_FILE /var/lib/vz/snippets/$REMOTE_FOLDER/ +EOF + fi + else + if [ -d "$LOCAL_FILE" ]; then + sftp root@10.15.0.4 < /dev/null +put -r $LOCAL_FILE /var/lib/vz/snippets/$REMOTE_FOLDER/ +EOF + fi + fi +} + +# Example paths, set or export these in environment if used +send_file_to_hypervisor "/root/bin/ssh/temp_pubs/key_*.pub" "container-public-keys" +send_file_to_hypervisor "$PROTOCOL_FILE" "container-port-maps" +send_file_to_hypervisor "${ENV_FOLDER_PATH:-}" "container-env-vars" +send_file_to_hypervisor "${TEMP_SERVICES_FILE_PATH:-}" "container-services" + +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +echo -e "${BOLD}${MAGENTA}🚀 Starting Container Creation...${RESET}" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + +# Safely get the basename of the temporary public key file. +KEY_BASENAME="" +# The 'find' command is safer than 'ls' for script usage. +KEY_FILE=$(find /root/bin/ssh/temp_pubs -type f -name "*.pub" | head -n1) + +if [[ -n "$KEY_FILE" ]]; then + KEY_BASENAME=$(basename "$KEY_FILE") +fi + +# Run your create-container.sh remotely over SSH with corrected quoting and simplified variable +ssh -t root@10.15.0.4 "bash -c \"/var/lib/vz/snippets/create-container.sh \ + '$CONTAINER_NAME' \ + '$CONTAINER_PASSWORD' \ + '$GH_ACTION' \ + '$HTTP_PORT' \ + '$PROXMOX_USERNAME' \ + '$KEY_BASENAME' \ + '$PROTOCOL_BASE_FILE' \ + '$DEPLOY_ON_START' \ + '${PROJECT_REPOSITORY:-}' \ + '${PROJECT_BRANCH:-}' \ + '${PROJECT_ROOT:-}' \ + '${INSTALL_COMMAND:-}' \ + '${BUILD_COMMAND:-}' \ + '${START_COMMAND:-}' \ + '${RUNTIME_LANGUAGE:-}' \ + '${ENV_FOLDER:-}' \ + '${SERVICES_FILE:-}' \ + '$LINUX_DISTRIBUTION' \ + '${MULTI_COMPONENT:-}' \ + '${ROOT_START_COMMAND:-}' \ +\"" + +# Clean up temp files +rm -f "$PROTOCOL_FILE" +rm -f /root/bin/ssh/temp_pubs/key_*.pub +rm -f "${TEMP_SERVICES_FILE_PATH:-}" +rm -rf "${ENV_FOLDER_PATH:-}" + +# Unset sensitive variables +unset CONFIRM_PASSWORD +unset CONTAINER_PASSWORD +unset PUBLIC_KEY + +echo "✅ Container creation wrapper script finished successfully." \ No newline at end of file diff --git a/create-a-container/public/index.html b/create-a-container/public/index.html new file mode 100644 index 00000000..63d37ff1 --- /dev/null +++ b/create-a-container/public/index.html @@ -0,0 +1,66 @@ + + + + + MIE Container Creation Login + + + + + + + + \ No newline at end of file diff --git a/create-a-container/public/logo.png b/create-a-container/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3429aeaaf1d2855a850fb10cca03a3f11ca756cd GIT binary patch literal 11044 zcmb7qRa6{J*EJ*%NN@-a!6CQ>2<~pdZEzjrA%kXcf$$Cf`Re)Z77y-z zexSN*E6Si$Pf+bVchKym)umBT>f*5OEzzI*uRkjpxuc*Edi=Xkm+ff2prEj|D9cIf zela`9dHtCR>4~Hh1qlxS_p?$vMJbiTL;^9=%2uvkHTvDJxZcazsHyNbFH=G?$3i02 zPwIv5+{3B1yR`BrFEoSDT!T9#30RpNA_}~ikx)>Z(W*VNePRM z>>(GJ_LlcXZMG0~WPY<}+jT_MAb`DJoH*QRp?0nR{2X$Afv4o+U#P1;ucB@mf{lwq z>D^EVGyDK*!SJ=I>XhS}Js&~Rvu*HO+Da#2^o?$Ao~iTk9A368Lq=Jvd(>K%a~bj^ zPpEGq%4#s=-+jDHZ;wBv+{q(5ZZWGxLB+$yRk4kg9p3((4k9HQ7#JvC)Z*UM6X5$G zW_5p)YT@AwsBom#Uir%I7>mWY3hi+XnH9a#k zv2}nM=-|lv2yr-W*K72wzdGhM>hf%EZvjR)|9!_*OsqFx`qULVx~cD?@nq)6kqI5x z!zu=^)D#s~Dt7wue1V1K_t<{Gxgq;JQy9i)c=q)icYfHL(EJ0zC)%%Do@Mm<-i@ol z6L~tHIQ9g?M<|c}ODjk`6@HyRFijiyn~bf?yi1<1;q05_3Lym2G&(TFBcQ~>%seYN zL@O*HYZhpqMHMB?l*E;GQfm^9HC%o=P(rP3tmSe)OjuZGU5~UV+K>B|(cq7KIWfJo zo*et9@2zEyL>i-Q%3NZK!F$ss@qr~*Q8hI+mGkljH^jLxsLekGQwq!8X0trNM~q8) zV6emAuDsR(LDBA~G!k$rPwwIx|0K3jrY6pSicVj{*gTZJx0M#y%B_;!@}#P8=eBt@ z-#qj%*l9_hFSXG>S~FDU`xi+bg8E9z$L@Q3Q$Wo9;lX%fx`z?LSIOEyJFm{gt7cc1 z#3cJkO79k?AgG}8p959E%#53O^QWf61@70%tO#Qngh-m7{I8BSQ)$TaZ^+_{@Z7;>irdIMCr5b)n-aQcc6S>k3i-W})sVK3h}6 z6dp(Mj5g0u35GByNxF%@s)Qz6$&;_90dk!_MJgiUu7crhHxclp8zg+5j0;JT7~;r9 z|Kzu;T)yvqEoRMwqHNlp&DFJ1s|N%qS|RKNp0*h=3cgo9nEU!RP>H`QeKq|voEU^E z^4Z;~jXu1Xs&-Tz;6RS#0w2*e*M5AD1Sqd|xMGRYw$a}ck-E$-FNiw@cIp|v1%VYX ziq{nL5)q{m9K=7kRe?@jem54yQGPEH?)pXKjCntF@A5Iqzqr5A)m4bzYNh{N!xgm|egcId^dWPvEemd$s%IZmZ;=Uh))5 zFY?IMKR-qT;v`MYf*!AAcf@n-E#suzBed5io5i_v2t(<9hUHjA85V2z&mnI8r_-S$e9waM|NwC za%U>IWyhd;oy)buSOCd?zgmGVFHCUFu4a=VP2Nh2jn2aF@@RQWCsf>w!#^}aq6&Wu zCqj?U-Xk3nA%{(0%$mIlAqvRlQn%>CJP??9QFH>{AQjRF!B?Qpwrbl%V zs}eK#4VhzuBpw;&M{fQep4n}2+@tgxGsb}g{ufZk=L_^jBht)5fgh&$TA!}@{1?0% znB{e-H$TM9mhu(BN8{add|dW6MPJFuw{*t{NGUN@PHyBzFQL`w4d_Kz?nq_Q6Y!Ks z(xv(%`LS&AVC+V9*9g4DZcwA@Pn0BYH$T!FHM*{4SFpa*R^)aOv9Va8sp6{eEkskxC=8Y>%<<=nb~pC2LJpYIE~` zA{J};uAi&RGHV+>uByXtY0sMWb;uaUMergy$Z%=>dq;%L-EzoChtwS+YsLgOTS(+^ zf9&M?=a+zq@ea7);=YT$cO1RNVNyV^Fxbmx?+`QZ6aFpPQQm8+2el6ydAS*!CSLMo zaXc>M0X!;A2g}^9?UWVE5uJfDQx#7xi*%Gt2MNcrgR{Jb?@o}398q6-V4H_dX#;sH z+}|4mne?0v6UUDH)f}%L^%0{Yn^<=tdq`vAz4+T)LC}1F0zjUQFO`P=d(2T%>OIS* z_5=D2eE2A1hz0*fvNSjGTN7Yro&g`?=`r%h#JilFV)iIx(8~5t?n5fSfCCz(%#{*s zdL1sw^L{O&VB_cbSk(=#naJyqSPl77=}K#T48CB8-xEQ6Ud*pJlFTGSm@UeH?rnV_x|Lq=P)a=~zCP_o_PxE#uwg z2fNk$oymPrOeukZ!JP>B8-ZGI1Oz99x?s)7L1!qgr!(Nsf1Wiy5Kpg)OQbOL-Qbj< zgI|s7Z#IK!yNW#q{IG^*^QQgfQl+|@F7t3#hp5{)(naQtG>^>+=#tQish&#*z3nZn zr8re-wXh&_$HN00zQ`-9a83X@p8w^VYF#?FTbp76~smJct}Qp8jc)&X6Y5>C?%K4}OqLg^bk zHS<4*H$G%f^A>+6o^ni=cK1f+0W=N7x`@Sh)oTBZnXQXMAfE3z!%#=^r=_wSB)G=p z9zYPhBGlI>1Uyz>Y~+CH5K0=tSp2sjm*w(T0!OnBFMu#~Aa$Ik?n}c!(^tZFq(`X> zcFP?nV4nVM%COL->wBxEM~8lg7{6J};3*y&nzUydXsWSwalrQd70wLdE9gfD4r8of z_cM+1vg!*ug`Z1RwX}#+wYK6bSeEwHgYR3=Ty=|1y}rMio7sJp$6G)Oa&mUmzE?#Z z4PqGeemnW(dvm|uJ?it{B#aL3CoJcf^rW5bGPs%IM5*@dIPtAc+!kqU{!&vRKg4R3 zBH4#fHUjTS+jU=sJr++zW%SE5+L2({fPKAFXBd zfOvL8MZA^4#UO9=SG6f|qtVdYJB*ZJ-;Z0m-QVwjtXT@brLKI{unc*_20FkrkRVc8 z!mOSIr7K)NeYkd?aQ_cNuuQk5uURus^((|Xf)yhe#stts!ih)^>;!c7nX1`{zXr#R zV+ITc4mtB@HW45HkoE)4*ME~VdNEF#SR99oi!%;cvWlu~_*1@^xIWd6e6bPpG%D(A zVny#BXowF|$GKQOc3mou#2N-8B!&TJK%l6%6fTKJIfR-4l~VyzhVy9qt@n&Eh`39W zfE1oeLNA~kNUt%tM|h<4$v83LIeKu-1#7epj^}`QKjl1G)!*{1>!Lf5`7MEd?Wwb7 z*P(&yxic}SG3W9$(h~z*N(#?}_AQcy(iEXhhZi6u1wI5%-{v#y!znPPg<@@=^jw}k{P149Q}@ktL4P3>AgArVhRCZMQbI0 zk(2H68{fqT#Mfgh1YvB%dk0w9ukpcA%z#O*_u4Q|){BZ0`>@jrW82%cw|RIFBiDOX zD9#6tROk?3wXo^Q;hRshd+=klz>DE^qV+zByo?8Qf_XWej1Lc|Yvgd5H@Wwb0P~C2 z*;ne$PA2G~#sOzdLP=aQ+N+e2KjhWGCn#fg1Uu0mB`& zpa;hH=4Hx+Dh7Or0D;KFe#g!c3WXYoIJ+&X%j96Co24^d_tF0SRs~RQ&E|?C<*r1U z@xqu2>@4Wwwsq|N>;~;FPrl5y$-963yNm_vZ?I;Q<&ZDlbmL?<$O*~l&$+{`Y{JC_ z2~-9s(g?Nk-lMLNX0p2UH7HU==`)`>!l^f7C*y#Lw=bO!oQ;*%a|I-n*TU-M>s z`u#58Q=_Cm5oexqqy-V_gSW(L3vEkcy-Jh>SOFM#{#wnj<4!6LoxnhIqw?8PwE0I? z?w9*AI?W9-`5u(oLQAABNj*mUP%~rV>XN2H>V7Bv^D*ElU=9Zb6`AanNwb)V%J`_VuKSEnL9JiJDKShE^TWengRN+jfh*wx{H%X?b6cM5Q*Yv%4jJeZ4Os}Q8j@}ID=AqV- z9b4V(5?YkS$y<*24Ycg->zPw_`Z+M(*1X*!?#YaQE1if^5c!d!Fw%mKU}+75VGjIt zZ5?3-4V?95k7#~JC5w&6tAovEorPeC{${Ey!4(&Mg9woeV#9S|APa)6N&grB^%7)^ zi|8GEjVjF(I!E?L^0x$6uUT2X^m|R(%|J3zs1YsqZBRfH!Sh!RP=mFvLgH@YNo17d zCMx1oCMxcV&eP13esPsnN$eOxT82P-HBF;1r7-B|<46Pq->M~3r+s5C^JX4CZSa1Q z<;ghX%l|Sb(tVsEK&Zu33FV{G+3q^y05&vPb3-l!J#*|QjK%yP3sivdHVM#HwmAaPvD3$RUQ5rl z%)R#Bk&*OU^bchAC%$b65P(8Nv$PgZk)VH7p57dcO?RI1F74YS~bc{hdN z!mZb*Od~b%AX5RcI+NRQq%B{phP_N!1lcyNI%lWww{~~9-nFUZm-Jx9U8+(^$3}we zyj$a2jS%IaRH;%v5p~}Asdr=!XLMXtkN8|K(PSXe&c7hoXpMPJGpHAGn41xyBp2-H*909^$W} z$ThdWO%I~4S#H%+&j%5s8WE5pkuS&Y2H<4W0;1b3)m{>hgo&17;ytB?BoN-k%9GnO z`s$r1E#U3VOKp1|A_A+H)4c1l4GBZjgLgi8fbIK((u!zN(pOGt^1ap6}mV zi!<%Uq;Qi5BH=xyWbW+i>gQyMU~IB|g3wxLjMA?dlQArPn`a;#87P9L)rXCr-J;-0 z=Fr%Z{xBVqrW|42O*9{-LnG{en5cb3C*bcUNWJS}mAH%f(Vm;Jv^+yxjl7^vfG`IS zgsS!;vRBn>+EFFTpUX_yfK9?Ub!)z|0yHH0=c-KOUEUWhQ&KDXxUfqwA(c5ybD8}{(@#$Mg0hma>xap^m2UgNT_g@lcKHA+ay<>R8{D6nt= zX0^m*W@d`5Pfk3P!~1VCqXMJ~ey%pq>b`gpB886`8z#RZgW)&M`Ul?0K0Y&N+7K3~yCU@2~CIkFEb8T(BMJ z?VqHkmNA}x6Ev_j+Be#K3!fzQz*Lhh1eZR%5^nVLDmUU7ir7V$3z2mmAf0&ni-k5l zyI2DE+?F!_o|}4BBKduV?B2&k6)&LF9pvKo-XX!mSqzFe7=DO@NZXmAvVMGPafbUkoM^`74h*=@~8K&86SBFWe9R_e>X!|C=l$bkIoE~JPh;uwD^jKZT|3JPluwTE>7z)qZ`P9cT0F= zI{LA;Hyb7(F2Rx0mMb*Qyj$#iAI47C(BUPPHi-HwT4!xOe_iCbpaiXqehU8zFACjH~WfrA^-`>@v52^Oo5Ki%f=5~Oj`4VGc0A$Bu6bb)Hhszn(Cv^6??KK_sSpwx;z`i@7W;6D}HKJ zP7im@_+LM%=H1pNj}3Levj|ytE*?&Y#A2`l4jfWEa5bab)=Us=0bNm5=Q8+?M*`en z@TNZ>{F-vuaT2Gq<;&>`Q|XM*qr;D>Y($aND%wQ?lUb+?-gBnmU@UBFAo3Y*(x0{f z73X0GwfGoseIO$E`f1o`R<-YzrKr48vy(uN;cBu^`DwJsj-2uc-@Ctqxq$BtI{F=7 zM?*{|cCy%emzOE^x4oJh{L&h&xa_e24?{FoF<5PUYs1qvC@K46jzcD@@f)dBG<`>2 z!%+}k^P92Ox_7j7CS$dW(1+)FaZH89N;4v7t2=bqa$8MC@>=Z`DsrD7=iW^tmBZtX zo{kJ!JDY3%#BS|XnkBC$A}cp0)#Ys879cZ>SaPYw-A#vEAeZ? zVOcTQ`5{_$Y=U88(gW48Nn5!Dv(A3p(|PXG^# z+-<{uD(1ha`QWD|Fx|mnhCv#k1Wnr08vDTa#S=H1V?f&QFbmCsbUNTK8mL?uj*^Q-aaN53k`RZwW0lE5MJ3y4|XY+=H|NY|9v#l63&jrN6M=; z)JWeiOVfXE_Vu)5=XBsdmVB+{?~}HDSY0pykBjtKaipa2^7}CkLXsb(gPJsnO{pYc zk%_>!;&ZgWikn8t&WfNak7IJ$k2W9XO|n$EWy7vi($4C|ymv_dodm;IE>3F_8QJ!4 z#b1UO4-L&{X`-X!U^ko$E88}Ka8J92HtCm2+FSJ%6e7zmYozfvzk!W?tph7Q)=DDj z6RD>|hHSEb54aa_YN)m9&I-d6U;-Pew|I>SRRyO4Qe<)ph5ZSd3x9w87o999L(MN- zUQp8F3ehPT{rTOJ^3E~y?evP=S9eziA?=X%D~kX#7tzUn@E0$~g9fFS(~FM1v(@WBhlsd_o`Z@|15g&c9z#W{nVf zxM{n#7i-dQsRQsyzyTapBT}>as?th!o7|P{Wb?EeK1<>qj!ROB)s6lHgobqW&ne}2 z6;&a1->KpC()k=a+1SQGT{PZH5fLtFPVas~CpJ3oGhAVOFd!piKzgE^1ut%KO}A$C zwrW|%jH=kTD$#a9p_hmKq_)Njo2kr*17`T`yBCfqemMzV7};TgUyuq+lscxRFC*{wdL%bV8BadYM-?QTm_G>B{D;!Bg=lI-{sSCcM+Xhsz zdH#k9)G~5;YmUb9VDd}fBt3`9$``wMHzSL_#YosI<*Mz}$(wSLlg*G6__i~F_d_AG zTc&!t5!EpN2+gqEjB4VqZ2BiM8_9Wtb$wOOJkN_y3?%B$bOhT@Mvt`o%S3OZpyU*= zQbn;E=Ib)XwcW16jRpC4aR%SLR8|t9V~84EukM@4pu#fh;guUO^AZ%J~JNX!PHobys?}eW~*;@3wdId8VZZ_DXe;@CxnIk&Ot3K0?sCMg;Ohux7 z9uF-VjYgx*%&>l=nv#K2o)}aq84gg|RjqS)zsSMl?)D~Hx3|Sh5`r-~-mw=|SupT} zFn~^3#j3%PuaaMfkBmNqBZDUx|KbW$xt`iyGC(yl`l{z7J7yhf5IH>n?@ojcu<;e0 zumn2Mmo7X98}Vsci%k-ooIhPoCnKt60)Gw6jeauEgt8yZ7-4uA> zk^GN7{pUA6oj2Q<10i+g9F~k+B(vxG!8~dDzxFtyp}kj?YPV-;+UmB!%5oRK-Y<$Y zoDD ztC-LvIBBs*QK%0P*9drs=0xw4$N=^=AAH%<@c0wrlfA_K6lUkGUJ)ORIWlXSV9QnN z#DyyiTf7o0*84_Gd!6I1#iGJ_60rXGTo3D}6J-DfFGR+iY0(ck;J&D!anQ49B(`&S z`GGC^gxudQ=&bfSN*$nz#Sc=~R7O_QM6d6(^wECPZX4kEezd~gw@}K|SzsVRx<)Dg z6!wxB{(*;0hm$+9ayw3K<3kotLu89Xa$PT8WSdhGqd+nG65u`!Du5t6u8#Xic@FDG ziQwU>`_|iSD~Uq^S`!7%5I4?rgOg_Xk`==96#jT|TtXJ@^fF05sT001eGdY99>(T3 z5rcpfU>RMikS6W<-!}AgLF?3KX}jSVd{X8=3ukG5 zm-E(w2!y`{2?g~IAWoY*o3blIAFZyj!&ERlyd25D%&@xfdbKgi31~@Q4V;J0J`Jv< zE3xA5EM5_nAH7Qs6xqn~=(yC)!H&I%X*(riw#rmxc6Bh-M^yN#W!^e%tPq;<{KK@= zxwTsv->=^`%-c~|l4=H;aeQ^(3dY|#rpG{OLsdy&@@pS!$n8vPd49t@IoT)Epe3bx zS|To+S)nm$>V!mj`5(Bbw&L+O&_P<>D=WO|@z>{uOt1M~P=K_$r)Zvi3bm<9)(laa z_elhgu=}#lPxWW~VK5^oSk)3+?T>h$7XiQDQ!MJ5XMIW-+Am0hT(@y|nUsX{_nY2I z6Y-+cA4bQGPC3NhBX&)s1xOPN_=QHGzLyt>b6BGV@}52lOWPve$g(^##{Fb0c}YMu znI6q?jvd}94G_a}V$Zya(@i3xlGW4a{N&vd8B>v?UmN8FwxH*7o$e0HifT|E+$yzk zbC$c@@EYzDvLiL>-B)?YS8IHf1IPzDKi7zCMoz$>mhAd24(4qqE(y@%ptjJIjk6$4 zdemrpXbSNQrs~aK&|?~2hWVL4+#2jVdwCh3ry`%1HD?vTe-J1Ox#p+^z=#96hV27! z1VNm5evwX&P=j6bb90jbt<&=yw)DC6jMOuMhtn|1Ir`j4PbC-cG$^|ykUlr4==y7?R|m`>t!E{l1TiOavT)o5KMV+*USSZa(P_(x==`%WY~o1huNC;uc*s%cIZuFdtY z(5CVX1$Ogz0tyhpq6IP07M`jmV0q7S*LbN=9WeTm22tQ(Q9BL`Pm|zW*UQ+w_w*-< zu8mB6TH4enCm$j-h#!|T*fAeEUnTeK*jf`bazC;C7ro;t2C81B4MzGdin*64nm^_5 z3xCn&{I1M-aAQ#ilmEO`}6b1O{c z!LmH6a&Up=hvo9-ZfsAj6z5!Oq9f5f;W{%2T+&Z|V*(muDEFa*U;Yy6pF0rvIRKbB zoxxH+omzJLM|ngw8T9M5*Dk*|)fe1u~Zsk!imq8X8a#9U&l2(gA5?;E3Tqw^ol1cbs0sFT*oD86PHgBJ ze_a_}VVrjmj|)^zALKZWCLg*{u2}<54}80cj%PrBg1!sM^CzG`1Jc}EP(-1S5HfMc z3*6nKx-k-O51r@;J$SjTBwo<>XV96uyECS?u`T zMG>Y)HPu(71beq{$-BD?7hiS}L5FXQL=#4^<3m#7Vlp%jhZhv1Ct`imGqrmPHbibK ziGLmJ>%Cl1k_pPRc2MPDR0$RNr#o5VA2cF()ma$5OOcWvD?Gp(k2U!D=+=5*fTgH@ zajjt&La=XcvQkFBcG20;^Mj7HvP$dqj(+|)u&p)RPNyGxBI;GtrBc?9FiXNhoRT8* zhA5|~ACaT{n~Ax7U2lxZ&|(I?4!>yp6(1k*wu6QGYX7C~A~gWT_tHi`8UFP&CzwQI zeZRHWKbxHrVby8-PneZupO@65eAaz|5snu!q~dqk@Y@dNe0QCFHz0@^?Lox=0=)@D z)-RS=HmwI;Qif1ZuOvxsU*+wQRl1z@j0wj|Zu8{1?;XX)q1OSQohWBJY%?=?Io(r5 z)^UX}-Q(yfLj``RmQQfKgO>X`W7ki;gyLON`K{v1q^+aT^Ht#lph}0Kc@3ymFCeE* z`jP+QB7eB1`e#~Zz@9BUjmS*9g*9HvPNVbGmO4(g7U21N;C_{f-T%&KcAs~2#9F3c zZNZRY?D`-3uqlV9t3+N8uU9)(i&Tq4mxeCy|9Gv-RgAd`o0XdiJ>x8*-_=Gj{}iB= zVT>kAo<8 fe { + if (!req.session.user) { + return res.redirect('/'); // Redirect to login page if not authenticated + } + res.sendFile(path.join(__dirname, 'views', 'form.html')); +}); + +// Handles user login +app.post('/login', (req, res) => { + const { username, password } = req.body; + + // This command should be secure and not directly expose passwords if possible + exec(`node /root/bin/js/runner.js authenticateUser ${username} ${password}`, (err, stdout) => { + if (err) { + console.error("Login script execution error:", err); + return res.status(500).json({ error: "Server error during authentication." }); + } + + if (stdout.trim() === 'true') { + req.session.user = username; + req.session.proxmoxUsername = username; + req.session.proxmoxPassword = password; + return res.json({ success: true, redirect: '/form.html' }); + } else { + return res.status(401).json({ error: "Invalid credentials" }); + } + }); +}); + +// Kicks off the container creation script as a background job +app.post('/create-container', (req, res) => { + if (!req.session.user) { + return res.status(401).json({ error: "Unauthorized" }); + } + + const jobId = crypto.randomUUID(); + const commandEnv = { ...process.env, ...req.body, PROXMOX_USERNAME: req.session.proxmoxUsername, PROXMOX_PASSWORD: req.session.proxmoxPassword }; + const scriptPath = '/opt/container-creator/create-container-wrapper.sh'; + + jobs[jobId] = { status: 'running', output: '' }; + + // ✨ FIX: Run the script via bash and merge stderr into stdout with 2>&1 + const command = `${scriptPath} 2>&1`; + const child = spawn('bash', ['-c', command], { env: commandEnv }); + + // Since we merged streams, we only need to listen to stdout + child.stdout.on('data', (data) => { + const message = data.toString(); + console.log(`[${jobId}]: ${message.trim()}`); + jobs[jobId].output += message; + }); + + child.on('close', (code) => { + console.log(`[${jobId}] process exited with code ${code}`); + jobs[jobId].status = (code === 0) ? 'completed' : 'failed'; + }); + + res.json({ success: true, redirect: `/status/${jobId}` }); +}); + +// Serves the status page for a specific job +app.get('/status/:jobId', (req, res) => { + if (!jobs[req.params.jobId]) { + return res.status(404).send("Job not found."); + } + res.sendFile(path.join(__dirname, 'views', 'status.html')); +}); + +// Streams the log output to the status page using Server-Sent Events (SSE) +app.get('/api/stream/:jobId', (req, res) => { + const { jobId } = req.params; + if (!jobs[jobId]) { + return res.status(404).end(); + } + + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.flushHeaders(); + + // Send the output that has already been generated + res.write(`data: ${JSON.stringify(jobs[jobId].output)}\n\n`); + + let lastSentLength = jobs[jobId].output.length; + const intervalId = setInterval(() => { + const currentOutput = jobs[jobId].output; + if (currentOutput.length > lastSentLength) { + const newData = currentOutput.substring(lastSentLength); + res.write(`data: ${JSON.stringify(newData)}\n\n`); + lastSentLength = currentOutput.length; + } + + if (jobs[jobId].status !== 'running') { + res.write(`event: close\ndata: Process finished with status: ${jobs[jobId].status}\n\n`); + clearInterval(intervalId); + res.end(); + } + }, 500); + + req.on('close', () => { + clearInterval(intervalId); + res.end(); + }); +}); + + +// --- Server Initialization --- +const PORT = 3000; +app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); \ No newline at end of file diff --git a/create-a-container/views/form.html b/create-a-container/views/form.html new file mode 100644 index 00000000..732c17a0 --- /dev/null +++ b/create-a-container/views/form.html @@ -0,0 +1,95 @@ + + + + + MIE Container Creation + + + + + + + + + + \ No newline at end of file diff --git a/create-a-container/views/logo.png b/create-a-container/views/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3429aeaaf1d2855a850fb10cca03a3f11ca756cd GIT binary patch literal 11044 zcmb7qRa6{J*EJ*%NN@-a!6CQ>2<~pdZEzjrA%kXcf$$Cf`Re)Z77y-z zexSN*E6Si$Pf+bVchKym)umBT>f*5OEzzI*uRkjpxuc*Edi=Xkm+ff2prEj|D9cIf zela`9dHtCR>4~Hh1qlxS_p?$vMJbiTL;^9=%2uvkHTvDJxZcazsHyNbFH=G?$3i02 zPwIv5+{3B1yR`BrFEoSDT!T9#30RpNA_}~ikx)>Z(W*VNePRM z>>(GJ_LlcXZMG0~WPY<}+jT_MAb`DJoH*QRp?0nR{2X$Afv4o+U#P1;ucB@mf{lwq z>D^EVGyDK*!SJ=I>XhS}Js&~Rvu*HO+Da#2^o?$Ao~iTk9A368Lq=Jvd(>K%a~bj^ zPpEGq%4#s=-+jDHZ;wBv+{q(5ZZWGxLB+$yRk4kg9p3((4k9HQ7#JvC)Z*UM6X5$G zW_5p)YT@AwsBom#Uir%I7>mWY3hi+XnH9a#k zv2}nM=-|lv2yr-W*K72wzdGhM>hf%EZvjR)|9!_*OsqFx`qULVx~cD?@nq)6kqI5x z!zu=^)D#s~Dt7wue1V1K_t<{Gxgq;JQy9i)c=q)icYfHL(EJ0zC)%%Do@Mm<-i@ol z6L~tHIQ9g?M<|c}ODjk`6@HyRFijiyn~bf?yi1<1;q05_3Lym2G&(TFBcQ~>%seYN zL@O*HYZhpqMHMB?l*E;GQfm^9HC%o=P(rP3tmSe)OjuZGU5~UV+K>B|(cq7KIWfJo zo*et9@2zEyL>i-Q%3NZK!F$ss@qr~*Q8hI+mGkljH^jLxsLekGQwq!8X0trNM~q8) zV6emAuDsR(LDBA~G!k$rPwwIx|0K3jrY6pSicVj{*gTZJx0M#y%B_;!@}#P8=eBt@ z-#qj%*l9_hFSXG>S~FDU`xi+bg8E9z$L@Q3Q$Wo9;lX%fx`z?LSIOEyJFm{gt7cc1 z#3cJkO79k?AgG}8p959E%#53O^QWf61@70%tO#Qngh-m7{I8BSQ)$TaZ^+_{@Z7;>irdIMCr5b)n-aQcc6S>k3i-W})sVK3h}6 z6dp(Mj5g0u35GByNxF%@s)Qz6$&;_90dk!_MJgiUu7crhHxclp8zg+5j0;JT7~;r9 z|Kzu;T)yvqEoRMwqHNlp&DFJ1s|N%qS|RKNp0*h=3cgo9nEU!RP>H`QeKq|voEU^E z^4Z;~jXu1Xs&-Tz;6RS#0w2*e*M5AD1Sqd|xMGRYw$a}ck-E$-FNiw@cIp|v1%VYX ziq{nL5)q{m9K=7kRe?@jem54yQGPEH?)pXKjCntF@A5Iqzqr5A)m4bzYNh{N!xgm|egcId^dWPvEemd$s%IZmZ;=Uh))5 zFY?IMKR-qT;v`MYf*!AAcf@n-E#suzBed5io5i_v2t(<9hUHjA85V2z&mnI8r_-S$e9waM|NwC za%U>IWyhd;oy)buSOCd?zgmGVFHCUFu4a=VP2Nh2jn2aF@@RQWCsf>w!#^}aq6&Wu zCqj?U-Xk3nA%{(0%$mIlAqvRlQn%>CJP??9QFH>{AQjRF!B?Qpwrbl%V zs}eK#4VhzuBpw;&M{fQep4n}2+@tgxGsb}g{ufZk=L_^jBht)5fgh&$TA!}@{1?0% znB{e-H$TM9mhu(BN8{add|dW6MPJFuw{*t{NGUN@PHyBzFQL`w4d_Kz?nq_Q6Y!Ks z(xv(%`LS&AVC+V9*9g4DZcwA@Pn0BYH$T!FHM*{4SFpa*R^)aOv9Va8sp6{eEkskxC=8Y>%<<=nb~pC2LJpYIE~` zA{J};uAi&RGHV+>uByXtY0sMWb;uaUMergy$Z%=>dq;%L-EzoChtwS+YsLgOTS(+^ zf9&M?=a+zq@ea7);=YT$cO1RNVNyV^Fxbmx?+`QZ6aFpPQQm8+2el6ydAS*!CSLMo zaXc>M0X!;A2g}^9?UWVE5uJfDQx#7xi*%Gt2MNcrgR{Jb?@o}398q6-V4H_dX#;sH z+}|4mne?0v6UUDH)f}%L^%0{Yn^<=tdq`vAz4+T)LC}1F0zjUQFO`P=d(2T%>OIS* z_5=D2eE2A1hz0*fvNSjGTN7Yro&g`?=`r%h#JilFV)iIx(8~5t?n5fSfCCz(%#{*s zdL1sw^L{O&VB_cbSk(=#naJyqSPl77=}K#T48CB8-xEQ6Ud*pJlFTGSm@UeH?rnV_x|Lq=P)a=~zCP_o_PxE#uwg z2fNk$oymPrOeukZ!JP>B8-ZGI1Oz99x?s)7L1!qgr!(Nsf1Wiy5Kpg)OQbOL-Qbj< zgI|s7Z#IK!yNW#q{IG^*^QQgfQl+|@F7t3#hp5{)(naQtG>^>+=#tQish&#*z3nZn zr8re-wXh&_$HN00zQ`-9a83X@p8w^VYF#?FTbp76~smJct}Qp8jc)&X6Y5>C?%K4}OqLg^bk zHS<4*H$G%f^A>+6o^ni=cK1f+0W=N7x`@Sh)oTBZnXQXMAfE3z!%#=^r=_wSB)G=p z9zYPhBGlI>1Uyz>Y~+CH5K0=tSp2sjm*w(T0!OnBFMu#~Aa$Ik?n}c!(^tZFq(`X> zcFP?nV4nVM%COL->wBxEM~8lg7{6J};3*y&nzUydXsWSwalrQd70wLdE9gfD4r8of z_cM+1vg!*ug`Z1RwX}#+wYK6bSeEwHgYR3=Ty=|1y}rMio7sJp$6G)Oa&mUmzE?#Z z4PqGeemnW(dvm|uJ?it{B#aL3CoJcf^rW5bGPs%IM5*@dIPtAc+!kqU{!&vRKg4R3 zBH4#fHUjTS+jU=sJr++zW%SE5+L2({fPKAFXBd zfOvL8MZA^4#UO9=SG6f|qtVdYJB*ZJ-;Z0m-QVwjtXT@brLKI{unc*_20FkrkRVc8 z!mOSIr7K)NeYkd?aQ_cNuuQk5uURus^((|Xf)yhe#stts!ih)^>;!c7nX1`{zXr#R zV+ITc4mtB@HW45HkoE)4*ME~VdNEF#SR99oi!%;cvWlu~_*1@^xIWd6e6bPpG%D(A zVny#BXowF|$GKQOc3mou#2N-8B!&TJK%l6%6fTKJIfR-4l~VyzhVy9qt@n&Eh`39W zfE1oeLNA~kNUt%tM|h<4$v83LIeKu-1#7epj^}`QKjl1G)!*{1>!Lf5`7MEd?Wwb7 z*P(&yxic}SG3W9$(h~z*N(#?}_AQcy(iEXhhZi6u1wI5%-{v#y!znPPg<@@=^jw}k{P149Q}@ktL4P3>AgArVhRCZMQbI0 zk(2H68{fqT#Mfgh1YvB%dk0w9ukpcA%z#O*_u4Q|){BZ0`>@jrW82%cw|RIFBiDOX zD9#6tROk?3wXo^Q;hRshd+=klz>DE^qV+zByo?8Qf_XWej1Lc|Yvgd5H@Wwb0P~C2 z*;ne$PA2G~#sOzdLP=aQ+N+e2KjhWGCn#fg1Uu0mB`& zpa;hH=4Hx+Dh7Or0D;KFe#g!c3WXYoIJ+&X%j96Co24^d_tF0SRs~RQ&E|?C<*r1U z@xqu2>@4Wwwsq|N>;~;FPrl5y$-963yNm_vZ?I;Q<&ZDlbmL?<$O*~l&$+{`Y{JC_ z2~-9s(g?Nk-lMLNX0p2UH7HU==`)`>!l^f7C*y#Lw=bO!oQ;*%a|I-n*TU-M>s z`u#58Q=_Cm5oexqqy-V_gSW(L3vEkcy-Jh>SOFM#{#wnj<4!6LoxnhIqw?8PwE0I? z?w9*AI?W9-`5u(oLQAABNj*mUP%~rV>XN2H>V7Bv^D*ElU=9Zb6`AanNwb)V%J`_VuKSEnL9JiJDKShE^TWengRN+jfh*wx{H%X?b6cM5Q*Yv%4jJeZ4Os}Q8j@}ID=AqV- z9b4V(5?YkS$y<*24Ycg->zPw_`Z+M(*1X*!?#YaQE1if^5c!d!Fw%mKU}+75VGjIt zZ5?3-4V?95k7#~JC5w&6tAovEorPeC{${Ey!4(&Mg9woeV#9S|APa)6N&grB^%7)^ zi|8GEjVjF(I!E?L^0x$6uUT2X^m|R(%|J3zs1YsqZBRfH!Sh!RP=mFvLgH@YNo17d zCMx1oCMxcV&eP13esPsnN$eOxT82P-HBF;1r7-B|<46Pq->M~3r+s5C^JX4CZSa1Q z<;ghX%l|Sb(tVsEK&Zu33FV{G+3q^y05&vPb3-l!J#*|QjK%yP3sivdHVM#HwmAaPvD3$RUQ5rl z%)R#Bk&*OU^bchAC%$b65P(8Nv$PgZk)VH7p57dcO?RI1F74YS~bc{hdN z!mZb*Od~b%AX5RcI+NRQq%B{phP_N!1lcyNI%lWww{~~9-nFUZm-Jx9U8+(^$3}we zyj$a2jS%IaRH;%v5p~}Asdr=!XLMXtkN8|K(PSXe&c7hoXpMPJGpHAGn41xyBp2-H*909^$W} z$ThdWO%I~4S#H%+&j%5s8WE5pkuS&Y2H<4W0;1b3)m{>hgo&17;ytB?BoN-k%9GnO z`s$r1E#U3VOKp1|A_A+H)4c1l4GBZjgLgi8fbIK((u!zN(pOGt^1ap6}mV zi!<%Uq;Qi5BH=xyWbW+i>gQyMU~IB|g3wxLjMA?dlQArPn`a;#87P9L)rXCr-J;-0 z=Fr%Z{xBVqrW|42O*9{-LnG{en5cb3C*bcUNWJS}mAH%f(Vm;Jv^+yxjl7^vfG`IS zgsS!;vRBn>+EFFTpUX_yfK9?Ub!)z|0yHH0=c-KOUEUWhQ&KDXxUfqwA(c5ybD8}{(@#$Mg0hma>xap^m2UgNT_g@lcKHA+ay<>R8{D6nt= zX0^m*W@d`5Pfk3P!~1VCqXMJ~ey%pq>b`gpB886`8z#RZgW)&M`Ul?0K0Y&N+7K3~yCU@2~CIkFEb8T(BMJ z?VqHkmNA}x6Ev_j+Be#K3!fzQz*Lhh1eZR%5^nVLDmUU7ir7V$3z2mmAf0&ni-k5l zyI2DE+?F!_o|}4BBKduV?B2&k6)&LF9pvKo-XX!mSqzFe7=DO@NZXmAvVMGPafbUkoM^`74h*=@~8K&86SBFWe9R_e>X!|C=l$bkIoE~JPh;uwD^jKZT|3JPluwTE>7z)qZ`P9cT0F= zI{LA;Hyb7(F2Rx0mMb*Qyj$#iAI47C(BUPPHi-HwT4!xOe_iCbpaiXqehU8zFACjH~WfrA^-`>@v52^Oo5Ki%f=5~Oj`4VGc0A$Bu6bb)Hhszn(Cv^6??KK_sSpwx;z`i@7W;6D}HKJ zP7im@_+LM%=H1pNj}3Levj|ytE*?&Y#A2`l4jfWEa5bab)=Us=0bNm5=Q8+?M*`en z@TNZ>{F-vuaT2Gq<;&>`Q|XM*qr;D>Y($aND%wQ?lUb+?-gBnmU@UBFAo3Y*(x0{f z73X0GwfGoseIO$E`f1o`R<-YzrKr48vy(uN;cBu^`DwJsj-2uc-@Ctqxq$BtI{F=7 zM?*{|cCy%emzOE^x4oJh{L&h&xa_e24?{FoF<5PUYs1qvC@K46jzcD@@f)dBG<`>2 z!%+}k^P92Ox_7j7CS$dW(1+)FaZH89N;4v7t2=bqa$8MC@>=Z`DsrD7=iW^tmBZtX zo{kJ!JDY3%#BS|XnkBC$A}cp0)#Ys879cZ>SaPYw-A#vEAeZ? zVOcTQ`5{_$Y=U88(gW48Nn5!Dv(A3p(|PXG^# z+-<{uD(1ha`QWD|Fx|mnhCv#k1Wnr08vDTa#S=H1V?f&QFbmCsbUNTK8mL?uj*^Q-aaN53k`RZwW0lE5MJ3y4|XY+=H|NY|9v#l63&jrN6M=; z)JWeiOVfXE_Vu)5=XBsdmVB+{?~}HDSY0pykBjtKaipa2^7}CkLXsb(gPJsnO{pYc zk%_>!;&ZgWikn8t&WfNak7IJ$k2W9XO|n$EWy7vi($4C|ymv_dodm;IE>3F_8QJ!4 z#b1UO4-L&{X`-X!U^ko$E88}Ka8J92HtCm2+FSJ%6e7zmYozfvzk!W?tph7Q)=DDj z6RD>|hHSEb54aa_YN)m9&I-d6U;-Pew|I>SRRyO4Qe<)ph5ZSd3x9w87o999L(MN- zUQp8F3ehPT{rTOJ^3E~y?evP=S9eziA?=X%D~kX#7tzUn@E0$~g9fFS(~FM1v(@WBhlsd_o`Z@|15g&c9z#W{nVf zxM{n#7i-dQsRQsyzyTapBT}>as?th!o7|P{Wb?EeK1<>qj!ROB)s6lHgobqW&ne}2 z6;&a1->KpC()k=a+1SQGT{PZH5fLtFPVas~CpJ3oGhAVOFd!piKzgE^1ut%KO}A$C zwrW|%jH=kTD$#a9p_hmKq_)Njo2kr*17`T`yBCfqemMzV7};TgUyuq+lscxRFC*{wdL%bV8BadYM-?QTm_G>B{D;!Bg=lI-{sSCcM+Xhsz zdH#k9)G~5;YmUb9VDd}fBt3`9$``wMHzSL_#YosI<*Mz}$(wSLlg*G6__i~F_d_AG zTc&!t5!EpN2+gqEjB4VqZ2BiM8_9Wtb$wOOJkN_y3?%B$bOhT@Mvt`o%S3OZpyU*= zQbn;E=Ib)XwcW16jRpC4aR%SLR8|t9V~84EukM@4pu#fh;guUO^AZ%J~JNX!PHobys?}eW~*;@3wdId8VZZ_DXe;@CxnIk&Ot3K0?sCMg;Ohux7 z9uF-VjYgx*%&>l=nv#K2o)}aq84gg|RjqS)zsSMl?)D~Hx3|Sh5`r-~-mw=|SupT} zFn~^3#j3%PuaaMfkBmNqBZDUx|KbW$xt`iyGC(yl`l{z7J7yhf5IH>n?@ojcu<;e0 zumn2Mmo7X98}Vsci%k-ooIhPoCnKt60)Gw6jeauEgt8yZ7-4uA> zk^GN7{pUA6oj2Q<10i+g9F~k+B(vxG!8~dDzxFtyp}kj?YPV-;+UmB!%5oRK-Y<$Y zoDD ztC-LvIBBs*QK%0P*9drs=0xw4$N=^=AAH%<@c0wrlfA_K6lUkGUJ)ORIWlXSV9QnN z#DyyiTf7o0*84_Gd!6I1#iGJ_60rXGTo3D}6J-DfFGR+iY0(ck;J&D!anQ49B(`&S z`GGC^gxudQ=&bfSN*$nz#Sc=~R7O_QM6d6(^wECPZX4kEezd~gw@}K|SzsVRx<)Dg z6!wxB{(*;0hm$+9ayw3K<3kotLu89Xa$PT8WSdhGqd+nG65u`!Du5t6u8#Xic@FDG ziQwU>`_|iSD~Uq^S`!7%5I4?rgOg_Xk`==96#jT|TtXJ@^fF05sT001eGdY99>(T3 z5rcpfU>RMikS6W<-!}AgLF?3KX}jSVd{X8=3ukG5 zm-E(w2!y`{2?g~IAWoY*o3blIAFZyj!&ERlyd25D%&@xfdbKgi31~@Q4V;J0J`Jv< zE3xA5EM5_nAH7Qs6xqn~=(yC)!H&I%X*(riw#rmxc6Bh-M^yN#W!^e%tPq;<{KK@= zxwTsv->=^`%-c~|l4=H;aeQ^(3dY|#rpG{OLsdy&@@pS!$n8vPd49t@IoT)Epe3bx zS|To+S)nm$>V!mj`5(Bbw&L+O&_P<>D=WO|@z>{uOt1M~P=K_$r)Zvi3bm<9)(laa z_elhgu=}#lPxWW~VK5^oSk)3+?T>h$7XiQDQ!MJ5XMIW-+Am0hT(@y|nUsX{_nY2I z6Y-+cA4bQGPC3NhBX&)s1xOPN_=QHGzLyt>b6BGV@}52lOWPve$g(^##{Fb0c}YMu znI6q?jvd}94G_a}V$Zya(@i3xlGW4a{N&vd8B>v?UmN8FwxH*7o$e0HifT|E+$yzk zbC$c@@EYzDvLiL>-B)?YS8IHf1IPzDKi7zCMoz$>mhAd24(4qqE(y@%ptjJIjk6$4 zdemrpXbSNQrs~aK&|?~2hWVL4+#2jVdwCh3ry`%1HD?vTe-J1Ox#p+^z=#96hV27! z1VNm5evwX&P=j6bb90jbt<&=yw)DC6jMOuMhtn|1Ir`j4PbC-cG$^|ykUlr4==y7?R|m`>t!E{l1TiOavT)o5KMV+*USSZa(P_(x==`%WY~o1huNC;uc*s%cIZuFdtY z(5CVX1$Ogz0tyhpq6IP07M`jmV0q7S*LbN=9WeTm22tQ(Q9BL`Pm|zW*UQ+w_w*-< zu8mB6TH4enCm$j-h#!|T*fAeEUnTeK*jf`bazC;C7ro;t2C81B4MzGdin*64nm^_5 z3xCn&{I1M-aAQ#ilmEO`}6b1O{c z!LmH6a&Up=hvo9-ZfsAj6z5!Oq9f5f;W{%2T+&Z|V*(muDEFa*U;Yy6pF0rvIRKbB zoxxH+omzJLM|ngw8T9M5*Dk*|)fe1u~Zsk!imq8X8a#9U&l2(gA5?;E3Tqw^ol1cbs0sFT*oD86PHgBJ ze_a_}VVrjmj|)^zALKZWCLg*{u2}<54}80cj%PrBg1!sM^CzG`1Jc}EP(-1S5HfMc z3*6nKx-k-O51r@;J$SjTBwo<>XV96uyECS?u`T zMG>Y)HPu(71beq{$-BD?7hiS}L5FXQL=#4^<3m#7Vlp%jhZhv1Ct`imGqrmPHbibK ziGLmJ>%Cl1k_pPRc2MPDR0$RNr#o5VA2cF()ma$5OOcWvD?Gp(k2U!D=+=5*fTgH@ zajjt&La=XcvQkFBcG20;^Mj7HvP$dqj(+|)u&p)RPNyGxBI;GtrBc?9FiXNhoRT8* zhA5|~ACaT{n~Ax7U2lxZ&|(I?4!>yp6(1k*wu6QGYX7C~A~gWT_tHi`8UFP&CzwQI zeZRHWKbxHrVby8-PneZupO@65eAaz|5snu!q~dqk@Y@dNe0QCFHz0@^?Lox=0=)@D z)-RS=HmwI;Qif1ZuOvxsU*+wQRl1z@j0wj|Zu8{1?;XX)q1OSQohWBJY%?=?Io(r5 z)^UX}-Q(yfLj``RmQQfKgO>X`W7ki;gyLON`K{v1q^+aT^Ht#lph}0Kc@3ymFCeE* z`jP+QB7eB1`e#~Zz@9BUjmS*9g*9HvPNVbGmO4(g7U21N;C_{f-T%&KcAs~2#9F3c zZNZRY?D`-3uqlV9t3+N8uU9)(i&Tq4mxeCy|9Gv-RgAd`o0XdiJ>x8*-_=Gj{}iB= zVT>kAo<8 fe + + + + Container Creation Status + + + + + + + + + + + \ No newline at end of file From e7a359b6770c661b5a3b9ceba047b8df949bd358 Mon Sep 17 00:00:00 2001 From: Carter Myers <206+cmyers@users.noreply.github.mieweb.com> Date: Thu, 31 Jul 2025 10:39:22 -0700 Subject: [PATCH 2/6] Additions for new container features --- create-a-container/public/style.css | 74 ++++++++++++++- create-a-container/server.js | 39 ++++++-- create-a-container/views/form.html | 132 ++++++++++++++++++--------- create-a-container/views/status.html | 2 +- 4 files changed, 195 insertions(+), 52 deletions(-) diff --git a/create-a-container/public/style.css b/create-a-container/public/style.css index c7535b6f..099489e5 100644 --- a/create-a-container/public/style.css +++ b/create-a-container/public/style.css @@ -2,10 +2,22 @@ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(to right, #2c3e50, #3498db); display: flex; - align-items: center; + align-items: flex-start; /* Changed from center to flex-start for top alignment */ justify-content: center; height: 100vh; margin: 0; + padding-top: 2rem; /* Added padding to give space from the top */ + overflow-y: auto; /* Allow scrolling if content is too long */ +} + +.main-content-wrapper { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 2rem; + width: 100%; + max-width: 900px; /* Increased max-width for two columns */ + padding: 0 1rem; } .login-container { @@ -13,11 +25,67 @@ body { padding: 2rem 3rem; border-radius: 12px; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); - max-width: 400px; - width: -webkit-fill-available; + width: 100%; /* Changed from max-width to be more flexible */ + text-align: center; + flex: 1; /* Allow flex item to grow/shrink */ +} + +.container-list-box { + background-color: white; + padding: 2rem; + border-radius: 12px; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); + width: 100%; + flex: 1; + text-align: left; +} + +.container-list-box h3 { text-align: center; + margin-top: 0; + color: #2c3e50; + border-bottom: 2px solid #f0f0f0; + padding-bottom: 1rem; } +.container-list-box ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +.container-list-box li { + background-color: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; +} + +.container-list-box li h4 { + margin: 0 0 0.5rem 0; + color: #3498db; +} + +.container-list-box li p { + margin: 0.25rem 0; + font-size: 0.95em; +} + +.container-list-box li ul { + margin-top: 0.5rem; + padding-left: 1rem; + font-size: 0.9em; +} + +.container-list-box li ul li { + padding: 0; + margin-bottom: 0.2rem; + border: none; + background: none; +} + + .logo { max-width: 180px; margin: 0 auto 1.5rem auto; diff --git a/create-a-container/server.js b/create-a-container/server.js index 08cbb1ce..5b97cc62 100644 --- a/create-a-container/server.js +++ b/create-a-container/server.js @@ -4,6 +4,7 @@ const session = require('express-session'); const { spawn, exec } = require('child_process'); const path = require('path'); const crypto = require('crypto'); +const fs = require('fs'); // Added fs module const app = express(); @@ -13,7 +14,7 @@ const jobs = {}; // --- Middleware Setup --- app.use(bodyParser.urlencoded({ extended: true })); app.use(express.json()); -app.use(express.static('public')); // For CSS, images, etc. +app.use(express.static('public')); app.use(session({ secret: 'A7d#9Lm!qW2z%Xf8@Rj3&bK6^Yp$0Nc', resave: false, @@ -35,7 +36,6 @@ app.get('/form.html', (req, res) => { app.post('/login', (req, res) => { const { username, password } = req.body; - // This command should be secure and not directly expose passwords if possible exec(`node /root/bin/js/runner.js authenticateUser ${username} ${password}`, (err, stdout) => { if (err) { console.error("Login script execution error:", err); @@ -53,6 +53,37 @@ app.post('/login', (req, res) => { }); }); +// ✨ UPDATED: API endpoint to get user's containers +app.get('/api/my-containers', (req, res) => { + if (!req.session.user) { + return res.status(401).json({ error: "Unauthorized" }); + } + // The username in port_map.json doesn't have the @pve suffix + const username = req.session.user.split('@')[0]; + + // Command to read the remote JSON file + const command = "ssh root@10.15.20.69 'cat /etc/nginx/port_map.json'"; + + exec(command, (err, stdout, stderr) => { + if (err) { + console.error("Error fetching port_map.json:", stderr); + return res.status(500).json({ error: "Could not fetch container list." }); + } + try { + const portMap = JSON.parse(stdout); + const userContainers = Object.entries(portMap) + // This check now ensures 'details' exists and has a 'user' property before comparing + .filter(([_, details]) => details && details.user === username) + .map(([name, details]) => ({ name, ...details })); + + res.json(userContainers); + } catch (parseError) { + console.error("Error parsing port_map.json:", parseError); + res.status(500).json({ error: "Could not parse container list." }); + } + }); +}); + // Kicks off the container creation script as a background job app.post('/create-container', (req, res) => { if (!req.session.user) { @@ -65,11 +96,9 @@ app.post('/create-container', (req, res) => { jobs[jobId] = { status: 'running', output: '' }; - // ✨ FIX: Run the script via bash and merge stderr into stdout with 2>&1 const command = `${scriptPath} 2>&1`; const child = spawn('bash', ['-c', command], { env: commandEnv }); - // Since we merged streams, we only need to listen to stdout child.stdout.on('data', (data) => { const message = data.toString(); console.log(`[${jobId}]: ${message.trim()}`); @@ -104,7 +133,6 @@ app.get('/api/stream/:jobId', (req, res) => { res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); - // Send the output that has already been generated res.write(`data: ${JSON.stringify(jobs[jobId].output)}\n\n`); let lastSentLength = jobs[jobId].output.length; @@ -129,7 +157,6 @@ app.get('/api/stream/:jobId', (req, res) => { }); }); - // --- Server Initialization --- const PORT = 3000; app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); \ No newline at end of file diff --git a/create-a-container/views/form.html b/create-a-container/views/form.html index 732c17a0..3e3e97c1 100644 --- a/create-a-container/views/form.html +++ b/create-a-container/views/form.html @@ -7,51 +7,59 @@ -