Skip to content
Merged
106 changes: 106 additions & 0 deletions container-creation/create-container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/bin/bash
# Script to create the pct container, run register container, and migrate container accordingly.
# Last Modified by June 30th, 2025 by Maxwell Klema

trap cleanup SIGINT SIGTERM SIGHUP

CONTAINER_NAME="$1"
CONTAINER_PASSWORD="$2"
HTTP_PORT="$3"
PROXMOX_USERNAME="$4"
PUB_FILE="$5"
PROTOCOL_FILE="$6"
NEXT_ID=$(pvesh get /cluster/nextid) #Get the next available LXC ID

# Run cleanup commands in case script is interrupted

function cleanup()
{
BOLD='\033[1m'
RESET='\033[0m'

echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo "⚠️ Script was abruptly exited. Running cleanup tasks."
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
pct unlock 114
if [ -f "/var/lib/vz/snippets/container-public-keys/$PUB_FILE" ]; then
rm -rf /var/lib/vz/snippets/container-public-keys/$PUB_FILE
fi
if [ -f "/var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE" ]; then
rm -rf /var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE
fi
exit 1
}


# Create the Container Clone

echo "⏳ Cloning Container..."
pct clone 114 $NEXT_ID \
--hostname $CONTAINER_NAME \
--full true \

# Set Container Options

echo "⏳ Setting Container Properties.."
pct set $NEXT_ID \
--tags "$PROXMOX_USERNAME" \
--onboot 1 \

pct start $NEXT_ID
pveum aclmod /vms/$NEXT_ID --user "$PROXMOX_USERNAME@pve" --role PVEVMUser
#pct delete $NEXT_ID

# Get the Container IP Address and install some packages

echo "⏳ Waiting for DHCP to allocate IP address to container..."
sleep 10

CONTAINER_IP=$(pct exec $NEXT_ID -- hostname -I | awk '{print $1}')
pct exec $NEXT_ID -- apt-get upgrade
pct exec $NEXT_ID -- apt install -y sudo
pct exec $NEXT_ID -- apt install -y git
if [ -f "/var/lib/vz/snippets/container-public-keys/$PUB_FILE" ]; then
pct exec $NEXT_ID -- touch ~/.ssh/authorized_keys
pct exec $NEXT_ID -- bash -c "cat > ~/.ssh/authorized_keys"< /var/lib/vz/snippets/container-public-keys/$PUB_FILE
rm -rf /var/lib/vz/snippets/container-public-keys/$PUB_FILE
fi

# Set password inside the container

pct exec $NEXT_ID -- bash -c "echo 'root:$CONTAINER_PASSWORD' | chpasswd"

# Run Contianer Provision Script to add container to port_map.json

if [ -f "/var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE" ]; then
echo "CONTAINS PROTOCOL FILE"
/var/lib/vz/snippets/register-container-test.sh $NEXT_ID $HTTP_PORT /var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE
rm -rf /var/lib/vz/snippets/container-port-maps/$PROTOCOL_FILE
else
/var/lib/vz/snippets/register-container-test.sh $NEXT_ID $HTTP_PORT
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)

# Migrate to pve2 if Container ID is even

if (( $NEXT_ID % 2 == 0 )); then
pct stop $NEXT_ID
pct migrate $NEXT_ID intern-phxdc-pve2 --target-storage containers-pve2 --online
ssh root@10.15.0.5 "pct start $NEXT_ID"
fi

# Echo Container Details

# Define friendly, high-contrast colors
BOLD='\033[1m'
BLUE='\033[34m'
MAGENTA='\033[35m'
GREEN='\033[32m'
RESET='\033[0m'

echo -e "📦 ${BLUE}Container ID :${RESET} $NEXT_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 root@$CONTAINER_NAME.opensource.mieweb.org"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
218 changes: 218 additions & 0 deletions container-creation/get-lxc-container-details.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/bin/bash
# Main Container Creation Script
# Modified June 23rd, 2025 by Maxwell Klema
# ------------------------------------------

# Define color variables (works on both light and dark backgrounds)
RESET="\033[0m"
BOLD="\033[1m"
MAGENTA='\033[35m'

echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}${MAGENTA}📦 MIE Container Creation Script ${RESET}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"

# 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=$(node /root/bin/js/authenticateUserRunner.js authenticateUser "$PROXMOX_USERNAME" "$PROXMOX_PASSWORD")
RETRIES=3

while [ $USER_AUTHENTICATED == 'false' ]; do
if [ $RETRIES -gt 0 ]; then
echo "❌ Authentication Failed. Try Again"
read -p "Enter Proxmox Username → " PROXMOX_USERNAME
read -sp "Enter Proxmox Password → " PROXMOX_PASSWORD
echo ""

USER_AUTHENTICATED=$(node /root/bin/js/authenticateUserRunner.js authenticateUser "$PROXMOX_USERNAME" "$PROXMOX_PASSWORD")
RETRIES=$(($RETRIES-1))
else
echo "Too many incorrect attempts. Exiting..."
exit 0
fi
done

echo "🎉 Your proxmox account, $PROXMOX_USERNAME@pve, has been authenticated"

# Gather Container Hostname (hostname.opensource.mieweb.org)

if [ -z "$CONTAINER_NAME" ]; then
read -p "Enter Application Name (One-Word) → " CONTAINER_NAME
fi

HOST_NAME_EXISTS=$(ssh root@10.15.20.69 "node /etc/nginx/checkHostnameRunner.js checkHostnameExists ${CONTAINER_NAME}")

while [ $HOST_NAME_EXISTS == 'true' ]; do
echo "Sorry! That name has already been registered. Try another name"
read -p "Enter Application Name (One-Word) → " CONTAINER_NAME
HOST_NAME_EXISTS=$(ssh root@10.15.20.69 "node /etc/nginx/checkHostnameRunner.js checkHostnameExists ${CONTAINER_NAME}")
done

echo "✅ $CONTAINER_NAME is available"

# Gather Container Password

if [ -z "$CONTAINER_PASSWORD" ]; then
read -sp "Enter Container Password → " CONTAINER_PASSWORD
echo
read -sp "Confirm Container Password → " CONFIRM_PASSWORD
echo

while [[ "$CONFIRM_PASSWORD" != "$CONTAINER_PASSWORD" || ${#CONTAINER_PASSWORD} -lt 8 ]]; do
echo "Sorry, try again. Ensure passwords are at least 8 characters."
read -sp "Enter Container Password → " CONTAINER_PASSWORD
echo
read -sp "Confirm Container Password → " CONFIRM_PASSWORD
echo
done
else
while [ ${#CONTAINER_PASSWORD} -lt 8 ]; do
echo "Sorry, try again. Ensure passwords are at least 8 characters."
read -sp "Enter Container Password → " CONTAINER_PASSWORD
echo
read -sp "Confirm Container Password → " CONFIRM_PASSWORD
echo
done
fi

# Attempt to detect public keys

echo -e "\n🔑 Attempting to Detect SSH Public Key..."

AUTHORIZED_KEYS="/root/.ssh/authorized_keys"
RANDOM_NUM=$(shuf -i 100000-999999 -n 1)
PUB_FILE="key_$RANDOM_NUM.pub"
TEMP_PUB_FILE="/root/bin/ssh/temp_pubs/$PUB_FILE" # in case two users are running this script at the same time, they do not overwrite each other's temp files
touch "$TEMP_PUB_FILE"
DETECT_PUBLIC_KEY=$(sudo /root/bin/ssh/detectPublicKey.sh "$SSH_KEY_FP" "$TEMP_PUB_FILE")

if [ "$DETECT_PUBLIC_KEY" == "Public key found for create-container" ]; then
echo "🔐 Public Key Found!"
else
echo "🔍 Could not detect Public Key"

if [ -z "$PUBLIC_KEY" ]; then
read -p "Enter Public Key (Allows Easy Access to Container) [OPTIONAL - LEAVE BLANK TO SKIP] → " PUBLIC_KEY
fi

# Check if key is valid

while [[ "$PUBLIC_KEY" != "" && $(echo "$PUBLIC_KEY" | ssh-keygen -l -f - 2>&1 | tr -d '\r') == "(stdin) is not a public key file." ]]; do
echo "❌ \"$PUBLIC_KEY\" is not a valid key. Enter either a valid key or leave blank to skip."
read -p "Enter Public Key (Allows Easy Access to Container) [OPTIONAL - LEAVE BLANK TO SKIP] → " PUBLIC_KEY
done

if [ "$PUBLIC_KEY" != "" ]; then
echo "$PUBLIC_KEY" > "$AUTHORIZED_KEYS" && systemctl restart ssh
echo "$PUBLIC_KEY" > "$TEMP_PUB_FILE"
sudo /root/bin/ssh/publicKeyAppendJumpHost.sh "$PUBLIC_KEY"
fi
fi

# Get HTTP Port Container Listens On

if [ -z "$HTTP_PORT" ]; then
read -p "Enter HTTP Port for your container to listen on (80-9999) → " HTTP_PORT
fi

while ! [[ "$HTTP_PORT" =~ ^[0-9]+$ ]] || [ "$HTTP_PORT" -lt 80 ] || [ "$HTTP_PORT" -gt 9999 ]; do
echo "❌ Invalid HTTP Port. It must be a number between 80 and 9,999."
read -p "Enter HTTP Port for your container to listen on (80-9999) → " HTTP_PORT
done

echo "✅ HTTP Port is set to $HTTP_PORT"

# Get any other protocols

protocol_duplicate() {
PROTOCOL="$1"
shift #remaining params are part of list
LIST="$@"

for item in $LIST; do
if [[ "$item" == "$PROTOCOL" ]]; then
return 0 # Protocol is a duplicate
fi
done
return 1 # Protocol is not a duplicate
}

read -p "Does your Container require any protocols other than SSH and HTTP? (y/n) → " USE_OTHER_PROTOCOLS
while [ "${USE_OTHER_PROTOCOLS^^}" != "Y" ] && [ "${USE_OTHER_PROTOCOLS^^}" != "N" ]; do
echo "Please answer 'y' for yes or 'n' for no."
read -p "Does your Container require any protocols other than SSH and HTTP? (y/n) → " USE_OTHER_PROTOCOLS
done

RANDOM_NUM=$(shuf -i 100000-999999 -n 1)
PROTOCOL_BASE_FILE="protocol_list_$RANDOM_NUM.txt"
PROTOCOL_FILE="/root/bin/protocols/$PROTOCOL_BASE_FILE"
touch "$PROTOCOL_FILE"

if [ "${USE_OTHER_PROTOCOLS^^}" == "Y" ]; then
LIST_PROTOCOLS=()
read -p "Enter the protocol abbreviation (e.g, LDAP for Lightweight Directory Access Protocol). Type \"e\" to exit → " PROTOCOL_NAME
while [ "${PROTOCOL_NAME^^}" != "E" ]; do
FOUND=0 #keep track if protocol was found
while read line; do
PROTOCOL_ABBRV=$(echo "$line" | awk '{print $1}')
protocol_duplicate "$PROTOCOL_ABBRV" "${LIST_PROTOCOLS[@]}"
IS_PROTOCOL_DUPLICATE=$?
if [[ "$PROTOCOL_ABBRV" == "${PROTOCOL_NAME^^}" && "$IS_PROTOCOL_DUPLICATE" -eq 1 ]]; then
LIST_PROTOCOLS+=("$PROTOCOL_ABBRV")
PROTOCOL_UNDRLYING_NAME=$(echo "$line" | awk '{print $3}')
PROTOCOL_DEFAULT_PORT=$(echo "$line" | awk '{print $2}')
echo "$PROTOCOL_ABBRV $PROTOCOL_UNDRLYING_NAME $PROTOCOL_DEFAULT_PORT" >> "$PROTOCOL_FILE"
echo "✅ Protocol ${PROTOCOL_NAME^^} added to container."
FOUND=1 #protocol was found
break
else
echo "❌ Protocol ${PROTOCOL_NAME^^} was already added to your container. Please try again."
FOUND=2 #protocol was a duplicate
break
fi
done < <(cat "/root/bin/protocols/master_protocol_list.txt" | grep "^${PROTOCOL_NAME^^}")

if [ $FOUND -eq 0 ]; then #if no results found, let user know.
echo "❌ Protocol ${PROTOCOL_NAME^^} not found. Please try again."
fi

read -p "Enter the protocol abbreviation (e.g, LDAP for Lightweight Directory Access Protocol). Type \"e\" to exit → " PROTOCOL_NAME
done
fi

# send public key file & port map file to hypervisor and ssh, Create the Container, run port mapping script

if [ -s $TEMP_PUB_FILE ]; then
sftp root@10.15.0.4 <<EOF
put $TEMP_PUB_FILE /var/lib/vz/snippets/container-public-keys/
EOF
fi

# don't send it file size is zero.
if [ -s "$PROTOCOL_FILE" ]; then
sftp root@10.15.0.4 <<EOF
put $PROTOCOL_FILE /var/lib/vz/snippets/container-port-maps/
EOF
fi

echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}${MAGENTA}🚀 Starting Container Creation...${RESET}"
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"

ssh -t root@10.15.0.4 "/var/lib/vz/snippets/create-container.sh $CONTAINER_NAME $CONTAINER_PASSWORD $HTTP_PORT $PROXMOX_USERNAME $PUB_FILE $PROTOCOL_BASE_FILE"

rm -rf "$PROTOCOL_FILE"
rm -rf "$TEMP_PUB_FILE"

unset CONFIRM_PASSWORD
unset CONTAINER_PASSWORD
unset PUBLIC_KEY
30 changes: 30 additions & 0 deletions container-creation/js/authenticateUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Script to authenticate a user into Proxmox
// Last updated June 24th, 2025 by Maxwell Klema

const axios = require('axios');
const qs = require('qs');
const https = require('https');

// authenticates user, ensuring they have a valid proxmox account
function authenticateUser(username, password) {
let data = qs.stringify({
'username': username + "@pve",
'password': password
})

let config = {
method: 'post',
url: ' https://10.15.0.4:8006/api2/json/access/ticket',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
httpsAgent: new https.Agent({
rejectUnauthorized: false // Disable SSL verification for self-signed certificates (Only because public facing domain is resolved to nginx server internally, so have to use hypervisor IP instead of domain)
}),
data: data
};

return axios.request(config).then((response) => response.status === 200).catch(() => false);
}

module.exports = { authenticateUser };
11 changes: 11 additions & 0 deletions container-creation/js/authenticateUserRunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Script to run authenticateUser in the shell
// Last updated June 24th, 2025 by Maxwell Klema

authenticateuser = require("./authenticateUser.js");

const [, , func, ...args] = process.argv;
if (func == "authenticateUser") {
authenticateuser.authenticateUser(...args).then((result) => {
console.log(result);
});
}
25 changes: 25 additions & 0 deletions container-creation/ssh/detectPublicKey.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
# Detect if the user in the current session logged in via an SSH public key
# Last Updated June 26, 2025 Maxwell Klema

USER="create-container" #Change Later
PUBLIC_KEY_LIST="/root/.ssh/authorized_keys"


KEY_FINGERPRINT="$1"
TEMP_PUB_FILE="$2"

if [ "$KEY_FINGERPRINT" != "" ]; then
# Iterate over each public key, compute fingerprint, see if there is a match

while read line; do
echo "$line" > "$TEMP_PUB_FILE"
PUB_FINGERPRINT=$(ssh-keygen -lf "$TEMP_PUB_FILE" | awk '{print $2}')
if [[ "$PUB_FINGERPRINT" == "$KEY_FINGERPRINT" ]]; then
echo "Public key found for $USER"
exit 0
fi
done < <(tac $PUBLIC_KEY_LIST) #Iterates backwards without creating subprocess (allows exit in loop)

echo "" > "$TEMP_PUB_FILE"
fi
Loading