diff --git a/container-creation/create-container-new.sh b/container-creation/create-container-new.sh new file mode 100644 index 00000000..62ec9e63 --- /dev/null +++ b/container-creation/create-container-new.sh @@ -0,0 +1,294 @@ +#!/bin/bash +# Script to create the pct container, run register container, and migrate container accordingly. +# Last Modified by August 27th, 2025 by Carter Myers +# ----------------------------------------------------- + +BOLD='\033[1m' +BLUE='\033[34m' +MAGENTA='\033[35m' +GREEN='\033[32m' +RESET='\033[0m' + +cleanup() { + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo "⚠️ Script was abruptly exited. Running cleanup tasks." + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + pct_unlock $CTID_TEMPLATE + for file in \ + "/var/lib/vz/snippets/container-public-keys/$PUB_FILE" \ + "/var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE" \ + "/var/lib/vz/snippets/container-env-vars/$ENV_BASE_FOLDER" \ + "/var/lib/vz/snippets/container-services/$SERVICES_BASE_FILE" + do + [ -f "$file" ] && rm -rf "$file" + done + exit 1 +} + +echoContainerDetails() { + echo -e "📦 ${BLUE}Container ID :${RESET} $CONTAINER_ID" + echo -e "🌐 ${MAGENTA}Internal IP :${RESET} $CONTAINER_IP" + echo -e "🔗 ${GREEN}Domain Name :${RESET} https://$CONTAINER_NAME.opensource.mieweb.org" + echo -e "🛠️ ${BLUE}SSH Access :${RESET} ssh -p $SSH_PORT $PROXMOX_USERNAME@$CONTAINER_NAME.opensource.mieweb.org" + echo -e "🔑 ${BLUE}Container Password :${RESET} Your proxmox account password" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e "${BOLD}${MAGENTA}NOTE: Additional background scripts are being ran in detached terminal sessions.${RESET}" + echo -e "${BOLD}${MAGENTA}Wait up to two minutes for all processes to complete.${RESET}" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e "${BOLD}${BLUE}Still not working? Contact Max K. at maxklema@gmail.com${RESET}" + echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +} + +trap cleanup SIGINT SIGTERM SIGHUP + +CONTAINER_NAME="$1" +GH_ACTION="$2" +HTTP_PORT="$3" +PROXMOX_USERNAME="$4" +USERNAME_ONLY="${PROXMOX_USERNAME%@*}" +PUB_FILE="$5" +PROTOCOL_FILE="$6" + +# Deployment ENVS +DEPLOY_ON_START="$7" +PROJECT_REPOSITORY="$8" +PROJECT_BRANCH="$9" +PROJECT_ROOT="${10}" +INSTALL_COMMAND=$(echo "${11}" | base64 -d) +BUILD_COMMAND=$(echo "${12}" | base64 -d) +START_COMMAND=$(echo "${13}" | base64 -d) +RUNTIME_LANGUAGE=$(echo "${14}" | base64 -d) +ENV_BASE_FOLDER="${15}" +SERVICES_BASE_FILE="${16}" +LINUX_DISTRO="${17}" +MULTI_COMPONENTS="${18}" +ROOT_START_COMMAND="${19}" +SELF_HOSTED_RUNNER="${20}" +VERSIONS_DICT=$(echo "${21}" | base64 -d) +AI_CONTAINER="${22}" # new argument from HTML form + +echo "PROJECT ROOT: \"$PROJECT_ROOT\"" + +# === Wrapper for pct exec (and optionally pct commands for AI) === +run_pct_exec() { + local ctid="$1" + shift + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pct exec $ctid -- $*" + else + pct exec "$ctid" -- "$@" + fi +} + +run_pct() { + # $@ = full pct command, e.g., clone, set, start, etc. + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pct $*" + else + pct "$@" + fi +} + +run_pveum() { + # Wrapper for pveum commands in AI case + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pveum $*" + else + pveum "$@" + fi +} + +run_pvesh() { + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pvesh $*" + else + pvesh "$@" + fi +} + +run_pct_push() { + local ctid="$1" + local src="$2" + local dest="$3" + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pct push $ctid $src $dest" + else + pct push "$ctid" "$src" "$dest" + fi +} + +# === Template Selection === +if [ "${AI_CONTAINER^^}" == "Y" ]; then + echo "⏳ AI container requested. Using debian12-ai-template (CTID 150) on 10.15.0.6..." + CTID_TEMPLATE="163" + CONTAINER_ID=$(run_pvesh get /cluster/nextid) + + run_pct clone $CTID_TEMPLATE $CONTAINER_ID \ + --hostname $CONTAINER_NAME \ + --full true + + run_pct set $CONTAINER_ID \ + --tags "$PROXMOX_USERNAME" \ + --tags "$LINUX_DISTRO" \ + --tags "AI" \ + --onboot 1 + + run_pct start $CONTAINER_ID + run_pveum aclmod /vms/$CONTAINER_ID --user "$PROXMOX_USERNAME@pve" --role PVEVMUser +else + REPO_BASE_NAME=$(basename -s .git "$PROJECT_REPOSITORY") + REPO_BASE_NAME_WITH_OWNER=$(echo "$PROJECT_REPOSITORY" | cut -d'/' -f4) + + TEMPLATE_NAME="template-$REPO_BASE_NAME-$REPO_BASE_NAME_WITH_OWNER" + CTID_TEMPLATE=$( { pct list; ssh root@10.15.0.5 'pct list'; } | awk -v name="$TEMPLATE_NAME" '$3 == name {print $1}') + + case "${LINUX_DISTRO^^}" in + DEBIAN) PACKAGE_MANAGER="apt-get" ;; + ROCKY) PACKAGE_MANAGER="dnf" ;; + esac + + if [ -z "$CTID_TEMPLATE" ]; then + case "${LINUX_DISTRO^^}" in + DEBIAN) CTID_TEMPLATE="160" ;; + ROCKY) CTID_TEMPLATE="138" ;; + esac + fi + + CONTAINER_ID=$(pvesh get /cluster/nextid) + + echo "⏳ Cloning Container..." + run_pct clone $CTID_TEMPLATE $CONTAINER_ID \ + --hostname $CONTAINER_NAME \ + --full true + + echo "⏳ Setting Container Properties..." + run_pct set $CONTAINER_ID \ + --tags "$PROXMOX_USERNAME" \ + --tags "$LINUX_DISTRO" \ + --tags "LDAP" \ + --onboot 1 + + run_pct start $CONTAINER_ID + run_pveum aclmod /vms/$CONTAINER_ID --user "$PROXMOX_USERNAME@pve" --role PVEVMUser +fi + +# === Post-Provisioning (pct exec wrapped) === +if [ -f "/var/lib/vz/snippets/container-public-keys/$PUB_FILE" ]; then + echo "⏳ Appending Public Key..." + run_pct_exec $CONTAINER_ID touch ~/.ssh/authorized_keys > /dev/null 2>&1 + run_pct_exec $CONTAINER_ID bash -c "cat > ~/.ssh/authorized_keys" < /var/lib/vz/snippets/container-public-keys/$PUB_FILE > /dev/null 2>&1 + rm -rf /var/lib/vz/snippets/container-public-keys/$PUB_FILE > /dev/null 2>&1 +fi + +ROOT_PSWD=$(tr -dc 'A-Za-z0-9' /dev/null 2>&1 + +CONTAINER_IP="" +attempts=0 +max_attempts=10 + +while [[ -z "$CONTAINER_IP" && $attempts -lt $max_attempts ]]; do + CONTAINER_IP=$(run_pct_exec "$CONTAINER_ID" hostname -I | awk '{print $1}') + [[ -z "$CONTAINER_IP" ]] && sleep 2 && ((attempts++)) +done + +if [[ -z "$CONTAINER_IP" ]]; then + echo "❌ Timed out waiting for container to get an IP address." + exit 1 +fi + +echo "⏳ Updatig container packages..." +if [[ "${LINUX_DISTRO^^}" == "ROCKY" ]]; then + run_pct_exec $CONTAINER_ID bash -c "dnf upgrade -y" +else + run_pct_exec $CONTAINER_ID bash -c "apt-get update && apt-get upgrade -y" +fi + +echo "⏳ Configuring LDAP connection via SSSD..." +export AI_CONTAINER="$AI_CONTAINER" +source /var/lib/vz/snippets/helper-scripts/configureLDAP.sh + +echo "⏳ Setting up Wazuh-Agent..." +source /var/lib/vz/snippets/Wazuh/register-agent.sh + +if [ "${DEPLOY_ON_START^^}" == "Y" ]; then + source /var/lib/vz/snippets/helper-scripts/deployOnStart.sh + for file in \ + "/var/lib/vz/snippets/container-env-vars/$ENV_BASE_FOLDER" \ + "/var/lib/vz/snippets/container-services/$SERVICES_BASE_FILE" + do + [ -f "$file" ] && rm -rf "$file" > /dev/null 2>&1 + done +fi + +run_pct_exec $CONTAINER_ID bash -c "cd /root && touch container-updates.log" + +echo "⏳ Running Container Provision Script..." +if [ -f "/var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE" ]; then + /var/lib/vz/snippets/register-container.sh $CONTAINER_ID $HTTP_PORT /var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE "$USERNAME_ONLY" + rm -rf /var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE > /dev/null 2>&1 +else + /var/lib/vz/snippets/register-container.sh $CONTAINER_ID $HTTP_PORT "" "$PROXMOX_USERNAME" +fi + +SSH_PORT=$(iptables -t nat -S PREROUTING | grep "to-destination $CONTAINER_IP:22" | awk -F'--dport ' '{print $2}' | awk '{print $1}' | head -n 1 || true) + +echo "Adding container MOTD information..." +scp 10.15.20.69:/etc/nginx/port_map.json /tmp/port_map.json +CONTAINER_INFO=$(jq -r --arg hn "$CONTAINER_NAME" '.[$hn]' /tmp/port_map.json) + +if [ "$CONTAINER_INFO" != "null" ]; then + HOSTNAME="$CONTAINER_NAME" + IP=$(echo "$CONTAINER_INFO" | jq -r '.ip') + OWNER=$(echo "$CONTAINER_INFO" | jq -r '.user') + OS_RELEASE=$(echo "$CONTAINER_INFO" | jq -r '.os_release') + PORTS=$(echo "$CONTAINER_INFO" | jq -r '.ports | to_entries[] | "\(.key): \(.value)"' | paste -sd ", " -) + PROTOCOLS=$(echo "$CONTAINER_INFO" | jq -r '.ports | keys | join(", ")') + + cat < /tmp/container_motd +Container Information: +---------------------- +Hostname : $HOSTNAME +IP Address : $IP +Ports : $PORTS +Protocols : $PROTOCOLS +Primary Owner : $OWNER +OS Release : $OS_RELEASE +EOF +else + echo "No container info found for $CONTAINER_NAME" > /tmp/container_motd +fi + +run_pct_push $CONTAINER_ID /tmp/container_motd /etc/motd + +echoContainerDetails + +BUILD_COMMAND_B64=$(echo -n "$BUILD_COMMAND" | base64) +RUNTIME_LANGUAGE_B64=$(echo -n "$RUNTIME_LANGUAGE" | base64) +START_COMMAND_B64=$(echo -n "$START_COMMAND" | base64) + +if [[ "$AI" != "Y" ]]; then + CMD=( + bash /var/lib/vz/snippets/start_services.sh + "$CONTAINER_ID" + "$CONTAINER_NAME" + "$REPO_BASE_NAME" + "$REPO_BASE_NAME_WITH_OWNER" + "$SSH_PORT" + "$CONTAINER_IP" + "$PROJECT_ROOT" + "$ROOT_START_COMMAND" + "$DEPLOY_ON_START" + "$MULTI_COMPONENTS" + "$START_COMMAND_B64" + "$BUILD_COMMAND_B64" + "$RUNTIME_LANGUAGE_B64" + "$GH_ACTION" + "$PROJECT_BRANCH" + ) +fi + +QUOTED_CMD=$(printf ' %q' "${CMD[@]}") + +tmux new-session -d -s "$CONTAINER_NAME" "$QUOTED_CMD" +exit 0 \ No newline at end of file diff --git a/container-creation/helper-scripts/PVE_user_authentication.sh b/container-creation/helper-scripts/PVE_user_authentication.sh new file mode 100644 index 00000000..050d7b56 --- /dev/null +++ b/container-creation/helper-scripts/PVE_user_authentication.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Script that checks if a user is authenticated in Proxmox PVE Realm @ opensource.mieweb.org +# Last Modified by Maxwell Klema on July 13th, 2025 +# ----------------------------------------------------- + +# Authenticate User (Only Valid Users can Create Containers) + +if [ -z "$PROXMOX_USERNAME" ]; then + read -p "Enter Proxmox Username → " PROXMOX_USERNAME +fi + +if [ -z "$PROXMOX_PASSWORD" ]; then + read -sp "Enter Proxmox Password → " PROXMOX_PASSWORD + echo "" +fi + +USER_AUTHENTICATED=$(ssh root@create-container "node /root/bin/js/runner.js authenticateUser \"$PROXMOX_USERNAME\" \"$PROXMOX_PASSWORD\"") + +if [ "$USER_AUTHENTICATED" == "false" ]; then + outputError 1 "Your Proxmox account, $PROXMOX_USERNAME@pve, was not authenticated. Retry with valid credentials." +fi + +echo "🎉 Your proxmox account, $PROXMOX_USERNAME@pve, has been authenticated" \ No newline at end of file diff --git a/container-creation/helper-scripts/configureLDAP.sh b/container-creation/helper-scripts/configureLDAP.sh new file mode 100644 index 00000000..bf8ec3fa --- /dev/null +++ b/container-creation/helper-scripts/configureLDAP.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Script to connect a container to the LDAP server via SSSD +# Last Modified by Carter Myers on Aug 28th, 2025 +# ----------------------------------------------------- + +run_pct_exec() { + local ctid="$1" + shift + if [ "${AI_CONTAINER^^}" == "Y" ]; then + # Use printf %q to safely quote all arguments for the remote shell + local remote_cmd + printf -v remote_cmd '%q ' "$@" + ssh root@10.15.0.6 "pct exec $ctid -- $remote_cmd" + else + pct exec "$ctid" -- "$@" + fi +} + +run_pct() { + # $@ = full pct command, e.g., clone, set, start, etc. + if [ "${AI_CONTAINER^^}" == "Y" ]; then + ssh root@10.15.0.6 "pct $*" + else + pct "$@" + fi +} + +# Curl Pown.sh script to install SSSD and configure LDAP +run_pct_exec $CONTAINER_ID bash -c " +cd /root && \ +curl -O https://raw.githubusercontent.com/anishapant21/pown.sh/main/pown.sh > /dev/null 2>&1 && \ +chmod +x pown.sh +" + +# Copy .env file to container (safe for SSH / AI_CONTAINER) +ENV_FILE="/var/lib/vz/snippets/.env" +ENV_CONTENT=$(<"$ENV_FILE" sed 's/["\$`]/\\&/g') # Escape special characters +run_pct_exec $CONTAINER_ID bash -c "printf '%s\n' \"$ENV_CONTENT\" > /root/.env" + +# Run the pown.sh script to configure LDAP +run_pct_exec $CONTAINER_ID bash -c "cd /root && ./pown.sh" > /dev/null 2>&1 + +# Remove ldap_tls_cert from /etc/sssd/sssd.conf +run_pct_exec $CONTAINER_ID sed -i '/ldap_tls_cacert/d' /etc/sssd/sssd.conf > /dev/null 2>&1 + +# Add TLS_REQCERT to never in ROCKY +if [ "${LINUX_DISTRO^^}" == "ROCKY" ]; then + run_pct_exec $CONTAINER_ID bash -c "echo 'TLS_REQCERT never' >> /etc/openldap/ldap.conf" > /dev/null 2>&1 + run_pct_exec $CONTAINER_ID bash -c "authselect select sssd --force" > /dev/null 2>&1 + run_pct_exec $CONTAINER_ID bash -c "systemctl restart sssd" > /dev/null 2>&1 +fi diff --git a/container-creation/helper-scripts/create-template.sh b/container-creation/helper-scripts/create-template.sh new file mode 100644 index 00000000..54d5e1ea --- /dev/null +++ b/container-creation/helper-scripts/create-template.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Creates a template of a LXC container +# Last modified by Maxwell Klema on July 23rd, 2025. +# -------------------------------------------------- + +if [ "${DEPLOY_ON_START^^}" != "Y" ] || [ "${GH_ACTION^^}" != "Y" ]; then + return 0 +fi + +DEFAULT_BRANCH=$(curl -s https://api.github.com/repos/$REPO_BASE_NAME_WITH_OWNER/$REPO_BASE_NAME | jq -r '.default_branch') + +if [ "$DEFAULT_BRANCH" != "$PROJECT_BRANCH" ]; then + return 0 +fi + +echo "📝 Creating Container Template..." + +# Check if template already exists, and if it does, destroy it ===== + +TEMPLATE_NAME="template-$REPO_BASE_NAME-$REPO_BASE_NAME_WITH_OWNER" +TEMPLATE_CONTAINER_ID=$( { pct list; ssh root@10.15.0.5 'pct list'; } | awk -v name="$TEMPLATE_NAME" '$3 == name {print $1}') + +if [ ! -z "$TEMPLATE_CONTAINER_ID" ]; then + pct destroy $TEMPLATE_CONTAINER_ID | true +fi + +# Clone LXC container and convert it into a template ===== + +NEXT_ID=$(pvesh get /cluster/nextid) + +if (( $CONTAINER_ID % 2 == 101 )); then + ssh root@10.15.0.5 " + pct clone $CONTAINER_ID $NEXT_ID \ + --hostname "$TEMPLATE_NAME" \ + --full true + pct migrate $NEXT_ID intern-phxdc-pve1 --target-storage containers-pve1 + " > /dev/null 2>&1 +else + pct clone $CONTAINER_ID $NEXT_ID \ + --hostname "$TEMPLATE_NAME" \ + --full true +fi + +# AUTH_TOKEN_RESPONSE=$(curl --location --request POST https://api.github.com/repos/$REPO_BASE_NAME_WITH_OWNER/$REPO_BASE_NAME/actions/runners/registration-token --header "Authorization: token $GITHUB_PAT") +# TOKEN=$(echo "$AUTH_TOKEN_RESPONSE" | jq -r '.token') + +# Remove rsa keys ==== +pct start $NEXT_ID +pct enter $NEXT_ID < /dev/null 2>&1 + else + ssh root@10.15.0.5 "pct destroy $CONTAINER_ID" > /dev/null 2>&1 + fi +else + if pct status "$CONTAINER_ID" | grep -q "status: running"; then + pct stop "$CONTAINER_ID" && pct destroy "$CONTAINER_ID" > /dev/null 2>&1 + else + pct destroy "$CONTAINER_ID" > /dev/null 2>&1 + fi +fi + +source /usr/local/bin/prune_iptables.sh + +REPO_BASE_NAME=$(basename -s .git "$PROJECT_REPOSITORY") +REPO_BASE_NAME_WITH_OWNER=$(echo "$PROJECT_REPOSITORY" | cut -d'/' -f4) + +RUNNERS=$(curl --location https://api.github.com/repos/$REPO_BASE_NAME_WITH_OWNER/$REPO_BASE_NAME/actions/runners --header "Authorization: token $GITHUB_PAT") + +while read -r RUNNER; do + RUNNER_NAME=$(echo "$RUNNER" | jq -r '.name') + if [ "$RUNNER_NAME" == "$CONTAINER_NAME" ]; then + RUNNER_ID=$(echo "$RUNNER" | jq -r '.id') + curl --location --request DELETE "https://api.github.com/repos/$REPO_BASE_NAME_WITH_OWNER/$REPO_BASE_NAME/actions/runners/$RUNNER_ID" \ + --header "Authorization: token $GITHUB_PAT" + fi +done < <(echo "$RUNNERS" | jq -c '.runners[]') \ No newline at end of file diff --git a/container-creation/helper-scripts/deployOnStart.sh b/container-creation/helper-scripts/deployOnStart.sh new file mode 100644 index 00000000..c0c90586 --- /dev/null +++ b/container-creation/helper-scripts/deployOnStart.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# Automation Script for attempting to automatically deploy projects and services on a container +# Last Modifided by Maxwell Klema on August 16th, 2025 +# ----------------------------------------------------- + +echo "🚀 Attempting Automatic Deployment" +REPO_BASE_NAME=$(basename -s .git "$PROJECT_REPOSITORY") + +# Helper function to normalize paths by removing duplicate slashes +normalize_path() { + echo "$1" | sed 's#/\+#/#g' +} + +# Clone github repository from correct branch ==== + +pct enter $CONTAINER_ID < /dev/null 2>&1 && \ +cd /root/$REPO_BASE_NAME && \ +git checkout $PROJECT_BRANCH > /dev/null 2>&1 +else +cd /root/$REPO_BASE_NAME && git fetch > /dev/null 2>&1 && git pull > /dev/null 2>&1 && \ +git checkout $PROJECT_BRANCH > /dev/null 2>&1 +fi +EOF + +pct exec $CONTAINER_ID -- bash -c "chmod 700 ~/.bashrc" # enable full R/W/X permissions + +# Copy over ENV variables ==== + +ENV_BASE_FOLDER="/var/lib/vz/snippets/container-env-vars/${ENV_BASE_FOLDER}" + +echo "$REPO_BASE_NAME" +echo "$PROJECT_ROOT" +if [ -d "$ENV_BASE_FOLDER" ]; then + if [ "${MULTI_COMPONENTS^^}" == "Y" ]; then + for FILE in $ENV_BASE_FOLDER/*; do + FILE_BASENAME=$(basename "$FILE") + FILE_NAME="${FILE_BASENAME%.*}" + ENV_ROUTE=$(echo "$FILE_NAME" | tr '_' '/') # acts as the route to the correct folder to place .env file in. + + ENV_VARS=$(cat $ENV_BASE_FOLDER/$FILE_BASENAME) + COMPONENT_PATH=$(normalize_path "/root/$REPO_BASE_NAME/$PROJECT_ROOT/$ENV_ROUTE") + pct exec $CONTAINER_ID -- bash -c "if [ ! -f \"$COMPONENT_PATH/.env\" ]; then touch \"$COMPONENT_PATH/.env\"; fi; echo \"$ENV_VARS\" >> \"$COMPONENT_PATH/.env\"" > /dev/null 2>&1 + done + else + ENV_FOLDER_BASE_NAME=$(basename "$ENV_BASE_FOLDER") + ENV_VARS=$(cat $ENV_BASE_FOLDER/$ENV_FOLDER_BASE_NAME.txt || true) + COMPONENT_PATH=$(normalize_path "/root/$REPO_BASE_NAME/$PROJECT_ROOT") + pct exec $CONTAINER_ID -- bash -c "if [ ! -f \"$COMPONENT_PATH/.env\" ]; then touch \"$COMPONENT_PATH/.env\"; fi; echo \"$ENV_VARS\" >> \"$COMPONENT_PATH/.env\"" > /dev/null 2>&1 + fi +fi + +# Install Specific Runtime Versions (if Needed) + +echo "VERSIONS_DICT: $VERSIONS_DICT" +echo "RUNTIME_LANGUAGE: $RUNTIME_LANGUAGE" + +pct set $CONTAINER_ID --memory 4096 --swap 0 --cores 8 > /dev/null 2>&1 + +# Function to handle runtime installation +install_runtime() { + local runtime_language=$1 + local version=$2 + + if [ "${runtime_language,,}" == "nodejs" ]; then + local major=$(echo "$version" | cut -d. -f1) + local node_version_exists=$(curl -s https://nodejs.org/dist/index.json | grep "version\":\"v$major") + if [ ! -z "$node_version_exists" ]; then + source /var/lib/vz/snippets/helper-scripts/node_runtime_install.sh "$major" + else + echo "Node.js version $version ($major) is not available. Please check the version number. Using latest version instead." + fi + elif [ "${runtime_language,,}" == "python" ]; then + IFS='.' read -r -a parts <<< "$version" + + # Fill missing parts with 0 + while [ "${#parts[@]}" -lt 3 ]; do + parts+=("0") + done + + version="${parts[0]}.${parts[1]}.${parts[2]}" + local python_version_exists=$(curl -s https://www.python.org/ftp/python/ | grep "$version") + if [ ! -z "$python_version_exists" ]; then + source /var/lib/vz/snippets/helper-scripts/python_runtime_install.sh "${LINUX_DISTRO,,}" "$version" + else + echo "Python version $version is not available. Please check the version number. Using latest version instead." + fi + fi +} + +for key in $(echo "$VERSIONS_DICT" | jq -r 'keys[]'); do + if [ "$key" == "default" ] && [ "${MULTI_COMPONENT^^}" != "Y" ]; then + version=$(echo "$VERSIONS_DICT" | jq --arg k "$key" '.[$k]') + if [ "$version" != "null" ]; then + version=$(echo "$version" | sed 's/"//g') + install_runtime "$RUNTIME_LANGUAGE" "$version" + fi + else + value=$(echo "$RUNTIME_LANGUAGE" | jq --arg k "$key" '.[$k]') + value=$(echo "$value" | sed 's/"//g') + version=$(echo "$VERSIONS_DICT" | jq --arg k "$key" '.[$k]') + if [ "$version" != "null" ]; then + version=$(echo "$version" | sed 's/"//g') + install_runtime "$value" "$version" + fi + fi +done + +# Run Installation Commands ==== + +runInstallCommands() { + + RUNTIME="$1" + COMP_DIR="$2" + + # Create normalized path + WORK_DIR=$(normalize_path "/root/$REPO_BASE_NAME/$PROJECT_ROOT/$COMP_DIR") + + if [ "${RUNTIME^^}" == "NODEJS" ]; then + pct exec $CONTAINER_ID -- bash -c "cd $WORK_DIR && sudo $INSTALL_CMD" > /dev/null 2>&1 + elif [ "${RUNTIME^^}" == "PYTHON" ]; then + pct enter $CONTAINER_ID < /dev/null +cd $WORK_DIR && \ +python3 -m venv venv && source venv/bin/activate && \ +pip install --upgrade pip && \ +$INSTALL_CMD +EOF + fi +} + +if [ "${MULTI_COMPONENTS^^}" == "Y" ]; then + for COMPONENT in $(echo "$RUNTIME_LANGUAGE" | jq -r 'keys[]'); do + RUNTIME=$(echo "$RUNTIME_LANGUAGE" | jq -r --arg k "$COMPONENT" '.[$k]') #get runtime env + INSTALL_CMD=$(echo "$INSTALL_COMMAND" | jq -r --arg k "$COMPONENT" '.[$k]') #get install command + if [ "$INSTALL_CMD" != "null" ]; then + runInstallCommands "$RUNTIME" "$COMPONENT" + fi + done +else + INSTALL_CMD=$INSTALL_COMMAND + runInstallCommands "$RUNTIME_LANGUAGE" "." +fi + +# Install Services ==== + +if [ -f "/var/lib/vz/snippets/container-services/$SERVICES_BASE_FILE" ]; then + while read line; do + pct exec $CONTAINER_ID -- bash -c "$line" > /dev/null 2>&1 + done < "/var/lib/vz/snippets/container-services/$SERVICES_BASE_FILE" +fi \ No newline at end of file diff --git a/container-creation/helper-scripts/node_runtime_install.sh b/container-creation/helper-scripts/node_runtime_install.sh new file mode 100644 index 00000000..fc96cec5 --- /dev/null +++ b/container-creation/helper-scripts/node_runtime_install.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Script to install a specific Node.js runtime version +# Last Modified by Maxwell Klema on August 18th, 2025 +# ---------------------------------------------------------- + +echo "⏳ installing Node.js runtime version $MAJOR_VERSION" +MAJOR_VERSION=$1 + +pct enter "$CONTAINER_ID" -- < /dev/null +$PACKAGE_MANAGER remove nodejs && \ +curl -fsSL https://deb.nodesource.com/setup_$MAJOR_VERSION.x | sudo -E bash - && \ +$PACKAGE_MANAGER install -y nodejs +EOF \ No newline at end of file diff --git a/container-creation/helper-scripts/python_runtime_install.sh b/container-creation/helper-scripts/python_runtime_install.sh new file mode 100644 index 00000000..8680e573 --- /dev/null +++ b/container-creation/helper-scripts/python_runtime_install.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Script to install a specific Python runtime version +# Last Modified by Maxwell Klema on August 18th, 2025 +# ---------------------------------------------------------- + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 debian 3.8.9" + exit 1 +fi + +OS=$1 +PYTHON_VERSION=$2 +PYTHON_URL="https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz" + +# Function to install dependencies based on OS +install_dependencies() { + case "${OS,,}" in + debian) + pct exec $CONTAINER_ID -- bash -c "sudo apt update -y && sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev wget" > /dev/null 2>&1 + ;; + rocky) + pct exec $CONTAINER_ID -- bash -c "sudo dnf update -y && sudo dnf install -y gcc zlib-devel ncurses-devel gdbm-devel nss-devel openssl-devel readline-devel wget libffi-devel" > /dev/null 2>&1 + ;; + esac +} + +# Function to install Python +install_python() { + echo "⏳ Installing Python $PYTHON_VERSION... This may take a while." + pct exec "$CONTAINER_ID" -- bash -c "cd /usr/src && sudo wget $PYTHON_URL && sudo tar xzf Python-$PYTHON_VERSION.tgz && \ + cd Python-$PYTHON_VERSION && sudo ./configure --enable-optimizations && \ + sudo make -j$(nproc) && sudo make altinstall && \ + sudo ln -sfn /usr/local/bin/python${PYTHON_VERSION%.*} /usr/local/bin/python3 && \ + python3 -m ensurepip --upgrade && python3 -m pip install --upgrade pip" + echo "Python $PYTHON_VERSION installed successfully." > /dev/null 2>&1 +} + +install_dependencies +install_python \ No newline at end of file diff --git a/container-creation/helper-scripts/repository_status.sh b/container-creation/helper-scripts/repository_status.sh new file mode 100644 index 00000000..5ec73b76 --- /dev/null +++ b/container-creation/helper-scripts/repository_status.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Helper script to determine if container needs to clone repository or simply update it +# Last Modified by Maxwell Klema on July 21st, 2025 +# ------------------------------------------------- + +set +e +TYPE_RUNNER="true" +source /var/lib/vz/snippets/helper-scripts/PVE_user_authentication.sh +source /var/lib/vz/snippets/helper-scripts/verify_container_ownership.sh + +STATUS=$? + +if [ "$STATUS" != 0 ]; then + exit 1; +fi + +REPO_BASE_NAME=$(basename -s .git "$PROJECT_REPOSITORY") + +# Check if repository folder is present. + +if [ "$PVE1" == "true" ]; then + if pct exec $CONTAINER_ID -- test -d /root/$REPO_BASE_NAME; then + echo "Update" + exit 2; # Update Repository + else + echo "Clone" + exit 0; # Clone Repository + fi +else + if ssh 10.15.0.5 "pct exec $CONTAINER_ID -- test -d /root/$REPO_BASE_NAME"; then + echo "Update" + exit 2; # Update Repository + else + echo "Clone" + exit 0; # Clone Repository + fi +fi \ No newline at end of file diff --git a/container-creation/helper-scripts/verify_container_ownership.sh b/container-creation/helper-scripts/verify_container_ownership.sh new file mode 100644 index 00000000..2fb215b8 --- /dev/null +++ b/container-creation/helper-scripts/verify_container_ownership.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Script to verify container ownership based on name and CTID +# Last Modified by Maxwell Klema on August 5th, 2025 +# ----------------------------------------------------- + +CONTAINER_NAME="${CONTAINER_NAME,,}" +CONTAINER_ID=$( { pct list; ssh root@10.15.0.5 'pct list'; } | awk -v name="$CONTAINER_NAME" '$3 == name {print $1}') + +if [ -z "$CONTAINER_ID" ]; then + echo "✅ Container with name \"$CONTAINER_NAME\" is available for use." + return 0 +fi + +CONTAINER_OWNERSHIP=$(ssh root@10.15.20.69 -- "jq '.\"$CONTAINER_NAME\".user' /etc/nginx/port_map.json") +CONTAINER_OWNERSHIP="${CONTAINER_OWNERSHIP//\"/}" +if [ "$TYPE_RUNNER" == "true" ] && (( $CONTAINER_ID % 2 == 0 )); then + PVE1="false" +elif [ "$TYPE_RUNNER" == "true" ] && (( $CONTAINER_ID % 2 != 0 )); then + PVE1="true" +fi + +if [ "$CONTAINER_OWNERSHIP" != "$PROXMOX_USERNAME" ] && [ "$CONTAINER_OWNERSHIP" != "null" ]; then + echo "❌ You do not own the container with name \"$CONTAINER_NAME\"." + outputError 1 "You do not own the container with name \"$CONTAINER_NAME\"." \ No newline at end of file diff --git a/container-creation/register-container.sh b/container-creation/register-container.sh index 2a0ee4fb..85174216 100644 --- a/container-creation/register-container.sh +++ b/container-creation/register-container.sh @@ -2,7 +2,6 @@ set -euo pipefail - if [[ -z "${1-}" || -z "${2-}" || -z "${4-}" ]]; then echo "Usage: $0 " exit 1 @@ -13,6 +12,20 @@ http_port="$2" ADDITIONAL_PROTOCOLS="${3-}" proxmox_user="$4" +# AI_CONTAINER environment variable should be exported if running on AI node +AI_CONTAINER="${AI_CONTAINER:-N}" + +# run_pct_exec function to handle AI_CONTAINER +run_pct_exec() { + local ctid="$1" + shift + if [[ "${AI_CONTAINER^^}" == "Y" ]]; then + ssh root@10.15.0.6 "pct exec $ctid -- $*" + else + pct exec "$ctid" -- "$@" + fi +} + # Redirect stdout and stderr to a log file LOGFILE="/var/log/pve-hook-$CTID.log" exec > >(tee -a "$LOGFILE") 2>&1 @@ -23,8 +36,8 @@ attempts=0 max_attempts=5 while [[ -z "$container_ip" && $attempts -lt $max_attempts ]]; do - container_ip=$(pct exec "$CTID" -- ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d'/' -f1) - [[ -z "$container_ip" ]] && sleep 2 && ((attempts++)) + container_ip=$(run_pct_exec "$CTID" ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d'/' -f1) + [[ -z "$container_ip" ]] && sleep 2 && ((attempts++)) done if [[ -z "$container_ip" ]]; then @@ -32,8 +45,8 @@ if [[ -z "$container_ip" ]]; then exit 1 fi -hostname=$(pct exec "$CTID" -- hostname) -os_release=$(pct exec "$CTID" -- grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d "\"") +hostname=$(run_pct_exec "$CTID" hostname) +os_release=$(run_pct_exec "$CTID" grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d '"') # Check if this container already has a SSH port assigned in PREROUTING existing_ssh_port=$(iptables -t nat -S PREROUTING | grep "to-destination $container_ip:22" | awk -F'--dport ' '{print $2}' | awk '{print $1}' | head -n 1 || true) @@ -195,4 +208,4 @@ if [ ! -z "$ADDITIONAL_PROTOCOLS" ]; then fi # Bottom border -echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" \ No newline at end of file