diff --git a/container-creation/create-container.sh b/container-creation/create-container.sh new file mode 100644 index 00000000..583be02a --- /dev/null +++ b/container-creation/create-container.sh @@ -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}" \ No newline at end of file diff --git a/container-creation/get-lxc-container-details.sh b/container-creation/get-lxc-container-details.sh new file mode 100644 index 00000000..9715de8c --- /dev/null +++ b/container-creation/get-lxc-container-details.sh @@ -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 < response.status === 200).catch(() => false); +} + +module.exports = { authenticateUser }; diff --git a/container-creation/js/authenticateUserRunner.js b/container-creation/js/authenticateUserRunner.js new file mode 100644 index 00000000..5843dd9c --- /dev/null +++ b/container-creation/js/authenticateUserRunner.js @@ -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); + }); +} diff --git a/container-creation/ssh/detectPublicKey.sh b/container-creation/ssh/detectPublicKey.sh new file mode 100755 index 00000000..29edf5ab --- /dev/null +++ b/container-creation/ssh/detectPublicKey.sh @@ -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 \ No newline at end of file diff --git a/container-creation/ssh/publicKeyAppendJumpHost.sh b/container-creation/ssh/publicKeyAppendJumpHost.sh new file mode 100644 index 00000000..0a92b167 --- /dev/null +++ b/container-creation/ssh/publicKeyAppendJumpHost.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# A script that appends a user's public key to the SSH jump host container to prevent them having to enter a password +# June 25th, 2025 Maxwell Klema + +PUBLIC_KEY=$1 +JUMP_HOST="10.15.0.4" #temporary until jump server ready + +# SSH into the Jump Host + +ssh root@"$JUMP_HOST" "echo '$PUBLIC_KEY' >> /home/create-container/.ssh/authorized_keys && systemctl restart ssh" + +exit 0 diff --git a/intern-dnsmasq/dnsmasq.conf b/intern-dnsmasq/dnsmasq.conf index 889e9892..980db907 100644 --- a/intern-dnsmasq/dnsmasq.conf +++ b/intern-dnsmasq/dnsmasq.conf @@ -1,9 +1,11 @@ # /etc/dnsmasq.conf # Domain and interface +domain=opensource.mieweb.org domain=opensource.mieweb.com interface=eth0 listen-address=127.0.0.1,10.15.0.3 +server=8.8.8.8 # DHCP range dhcp-range=10.15.0.10,10.15.254.254,255.255.0.0,24h @@ -25,6 +27,7 @@ bogus-priv # --- REVERSE PROXY WILDCARD RULE --- # Route all traffic for *.opensource.mieweb.com to the NGINX proxy address=/.opensource.mieweb.com/10.15.20.69 +address=/.opensource.mieweb.org/10.15.20.69 # Dynamic DNS hostname injection -addn-hosts=/etc/dnsmasq.d/hosts +addn-hosts=/etc/dnsmasq.d/hosts \ No newline at end of file diff --git a/intern-jump/extract-fingerprint.sh b/intern-jump/extract-fingerprint.sh new file mode 100644 index 00000000..42c5e0df --- /dev/null +++ b/intern-jump/extract-fingerprint.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# A script to collect the client's SSH fingerprint and pass that to the content creation container +# Last Modified June 24th, 2025 by Maxwell Klema +# --------------------- + +CURRENT_TIME=$(date +"%B %d %T") + +USER="create-container" +SSH_CLIENT_IP=$(echo $SSH_CLIENT | awk '{print $1}') +RECENT_LOG=$(journalctl _COMM=sshd | grep "Accepted publickey for $USER from $SSH_CLIENT_IP" | tail -1) +LOGGED_TIME=$(echo $RECENT_LOG | awk '{print $3}') + +#check most recent logged time and current time are only max 2 seconds off since multiple users may log in from same IP over time + +epoch1=$(date -d "today $CURRENT_TIME" +%s) +epoch2=$(date -d "today $LOGGED_TIME" +%s) +diff=$((epoch1 - epoch2)) + +KEY_FINGERPRINT="" + +if [ "$diff" -ge 0 ] && [ "$diff" -le 2 ]; then + KEY_FINGERPRINT=$(echo $RECENT_LOG | grep -o 'SHA256[^ ]*') +fi + +export SSH_KEY_FP="$KEY_FINGERPRINT" +export PROXMOX_USERNAME="$PROXMOX_USERNAME" +export PROXMOX_PASSWORD="$PROXMOX_PASSWORD" +export CONTAINER_NAME="$CONTAINER_NAME" +export CONTAINER_PASSWORD="$CONTAINER_PASSWORD" +export PUBLIC_KEY="$PUBLIC_KEY" + +ssh -o "SendEnv=SSH_KEY_FP PROXMOX_USERNAME PROXMOX_PASSWORD CONTAINER_NAME CONTAINER_PASSWORD PUBLIC_KEY" -A create-container@10.15.234.122 \ No newline at end of file diff --git a/intern-nginx/port_map.js b/intern-nginx/port_map.js index b0fadd37..0843a810 100644 --- a/intern-nginx/port_map.js +++ b/intern-nginx/port_map.js @@ -1,10 +1,11 @@ // /etc/nginx/port_map.js // This is a reverse proxy configuration for Nginx that uses JavaScript to dynamically -// map subdomains to specific ports and IP addresses based on a JSON file. +// map subdomains to specific IP addresses based on a JSON file. // Code is based off of bluehive-testflight's port_map.js -// Last updated: 06-08-2025 Carter Myers +// Last updated: 06-08-2025 Carter Myers \\ 06-25-2025 Maxwell Klema + var fs = require('fs'); -var filePath = "/etc/nginx/port_map.json"; +var filePath = "/etc/nginx/port_map.json"; // Make sure Nginx has read access var cachedMapping = null; function loadMapping() { @@ -13,13 +14,14 @@ function loadMapping() { cachedMapping = JSON.parse(content); return true; } catch (e) { + // Optionally log error return false; } } function extractSubdomain(r) { var host = r.variables.host; - var match = host.match(/^([^.]+)\.opensource\.mieweb\.com$/); + var match = host.match(/^([^.]+)\.opensource\.mieweb\.(com|org)$/); if (!match) { r.error("Invalid hostname format: " + host); return null; @@ -27,7 +29,7 @@ function extractSubdomain(r) { return match[1]; } -function portLookup(r) { +function httpLookup(r) { if (cachedMapping === null && !loadMapping()) { r.error("Failed to load port mapping file."); r.return(500); @@ -55,7 +57,7 @@ function portLookup(r) { } } - return entry.port.toString(); // Always return string + return entry.ports.http.toString(); // Always return string } function ipLookup(r) { @@ -89,4 +91,4 @@ function ipLookup(r) { return entry.ip; } -export default { portLookup, ipLookup }; \ No newline at end of file +export default { httpLookup, ipLookup }; \ No newline at end of file diff --git a/intern-nginx/reverse_proxy.conf b/intern-nginx/reverse_proxy.conf index c2757e5f..596949d3 100644 --- a/intern-nginx/reverse_proxy.conf +++ b/intern-nginx/reverse_proxy.conf @@ -1,5 +1,6 @@ js_import port_module from /etc/nginx/port_map.js; js_set $backend_ip port_module.ipLookup; +js_set $http_port port_module.httpLookup; # Define a custom log format log_format proxy_log '$remote_addr - $host [$time_local] ' @@ -11,6 +12,41 @@ log_format proxy_log '$remote_addr - $host [$time_local] ' access_log /var/log/nginx/reverse_proxy_access.log proxy_log; error_log /var/log/nginx/reverse_proxy_error.log info; +# HTTPS, uncomment when nginx gets private key, will not work w/o it +server { + listen 443 ssl; + server_name .opensource.mieweb.org; + + ssl_certificate /root/.acme.sh/opensource.mieweb.org/fullchain.cer; + ssl_certificate_key /root/.acme.sh/opensource.mieweb.org/opensource.mieweb.org.key; + + location / { + if ($backend_ip = "") { + return 404 "Backend IP not found."; + } + + if ($http_port = "") { + return 404 "http port not found."; + } + + proxy_pass http://$backend_ip:$http_port; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; # Use HTTP/1.1 for WebSocket support + proxy_set_header Upgrade $http_upgrade; # Upgrade header for WebSocket support + proxy_set_header Connection "upgrade"; # Connection header for WebSocket support + + # Disable response buffering (important for SSE) + proxy_buffering off; + proxy_cache off; + chunked_transfer_encoding off; + proxy_read_timeout 300s; + + } +} + server { listen 80; server_name .opensource.mieweb.com; @@ -18,12 +54,23 @@ server { location / { if ($backend_ip = "") { return 404 "Backend IP not found."; - } + } + + if ($http_port = "") { + return 404 "http port not found."; + } + + proxy_pass http://$backend_ip:$http_port; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Disable response buffering (important for SSE) + proxy_buffering off; + proxy_cache off; + chunked_transfer_encoding off; + proxy_read_timeout 300s; - proxy_pass http://$backend_ip:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; } -} +} \ No newline at end of file diff --git a/intern-phxdc-pve1/register-container.sh b/intern-phxdc-pve1/register-container.sh index 23a00ef5..83a3cdd4 100644 --- a/intern-phxdc-pve1/register-container.sh +++ b/intern-phxdc-pve1/register-container.sh @@ -1,30 +1,32 @@ #!/bin/bash +# var/lib/vz/snippets/register-container.sh +# Script to register a container's IP and ports in the NGINX port map JSON file. +# Last Modified June 27 2025 by Maxwell Klema -# /var/lib/vz/snippets/register-container.sh -# This script registers a Proxmox container with the host's NGINX proxy and assigns it an available HTTP and SSH port. -# Last updated: 06-08-2025 Carter Myers -# ---------------------------------------- set -euo pipefail -set -x -CTID="$1" -if [ -z "$CTID" ]; then - echo "Usage: $0 " +if [[ -z "${1-}" || -z "${2-}" ]]; then + echo "Usage: $0 " exit 1 fi +CTID="$1" +http_port="$2" +ADDITIONAL_PROTOCOLS="${3-}" #set to empty string if not passed + # Redirect stdout and stderr to a log file LOGFILE="/var/log/pve-hook-$CTID.log" exec > >(tee -a "$LOGFILE") 2>&1 echo "---- Hookscript started at $(date) ----" echo "⏳ Waiting for container to boot and get DHCP lease..." -sleep 10 +#sleep 10 # Extract IP container_ip="" 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++)) @@ -37,11 +39,8 @@ fi hostname=$(pct exec "$CTID" -- hostname) -# Get available HTTP port (3000–3999) -used_http_ports=$(ssh root@10.15.20.69 'jq -r ".[] | .port" /etc/nginx/port_map.json 2>/dev/null || echo ""') -http_port=$(comm -23 <(seq 3000 3999 | sort) <(echo "$used_http_ports" | sort) | head -n 1) +# Check if this container already has a SSH port assigned in PREROUTING -# Check if this container already has a 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) if [[ -n "$existing_ssh_port" ]]; then @@ -49,7 +48,7 @@ if [[ -n "$existing_ssh_port" ]]; then ssh_port="$existing_ssh_port" else # Get used SSH ports - used_ssh_ports=$(iptables -t nat -S PREROUTING | awk -F'--dport ' '/--dport / {print $2}' | awk '{print $1}') + used_ssh_ports=$(iptables -t nat -S PREROUTING | awk -F'--dport ' '/--dport / {print $2}' | awk '/22$/' | awk '{print $1}') ssh_port=$(comm -23 <(seq 2222 2999 | sort) <(echo "$used_ssh_ports" | sort) | head -n 1) if [[ -z "$ssh_port" ]]; then @@ -64,22 +63,132 @@ else iptables -t nat -A POSTROUTING -o vmbr0 -p tcp -d "$container_ip" --dport 22 -j MASQUERADE fi +# Take input file of protocols, check if the container already has a port assigned for those protocols in PREROUTING + +# Store all protocols and ports to write to JSON list later. + +if [ ! -z "$ADDITIONAL_PROTOCOLS" ]; then + + list_all_protocols=() + list_all_ports=() + + while read line; do + + protocol=$(echo "$line" | awk '{print $1}') + underlying_protocol=$(echo "$line" | awk '{print $2}') + default_port_number=$(echo "$line" | awk '{print $3}') + + protocol_port="" + existing_port=$(iptables -t nat -S PREROUTING | grep "to-destination $container_ip:$default_port_number" | awk -F'--dport ' '{print $2}' | awk '{print $1}' | head -n 1 || true) + + if [[ -n "$existing_port" ]]; then + # Port already exists, so just assign it to protocol_port + echo "ℹ️ This Container already has a $protocol port at $existing_port" + protocol_port="$existing_port" + else + used_protocol_ports=$(iptables -t nat -S PREROUTING | awk -F'--dport ' '/--dport / {print $2}' | awk '{print $1}') + protocol_port=$(comm -23 <(seq 10001 29999 | sort) <(echo "$used_protocol_ports" | sort) | head -n 1 || true) + + if [[ -z "protocol_port" ]]; then + echo "❌ No available $protocol ports found" + exit 2 + fi + + # Add PREROUTING rule + iptables -t nat -A PREROUTING -i vmbr0 -p "$underlying_protocol" --dport "$protocol_port" -j DNAT --to-destination "$container_ip:$default_port_number" + + # Add POSTROUTING rule + iptables -t nat -A POSTROUTING -o vmbr0 -p "$underlying_protocol" -d "$container_ip" --dport "$default_port_number" -j MASQUERADE + + fi + + list_all_protocols+=("$protocol") + list_all_ports+=("$protocol_port") + + done < <(tac "$ADDITIONAL_PROTOCOLS") + + # Space Seperate Lists + + ss_protocols="$(IFS=, ; echo "${list_all_protocols[*]}")" + ss_ports="$(IFS=, ; echo "${list_all_ports[*]}")" + + #Update NGINX port map JSON on the remote host safely using a heredoc and positional parameters + +ssh root@10.15.20.69 bash -s -- "$hostname" "$container_ip" "$ssh_port" "$http_port" "$ss_protocols" "$ss_ports" <<'EOF' +set -euo pipefail + +hostname="$1" +container_ip="$2" +ssh_port="$3" +http_port="$4" +protos_json=$(echo "$5" | tr ',' '\n' | jq -R . | jq -s .) +ports_json=$(echo "$6" | tr ',' '\n' | jq -R . | jq -s 'map(tonumber)') + +jq --arg hn "$hostname" \ + --arg ip "$container_ip" \ + --argjson ssh "$ssh_port" \ + --argjson http "$http_port" \ + --argjson protos "$protos_json" \ + --argjson ports_list "$ports_json" \ + '. + {($hn): {ip: $ip, ports: ( reduce range(0; $protos | length) as $i ( {ssh: $ssh, http: $http}; . + { ($protos[$i]): $ports_list[$i]}))}}' /etc/nginx/port_map.json > /tmp/port_map.json.new + +mv -f /tmp/port_map.json.new /etc/nginx/port_map.json +nginx -s reload +EOF + +else + # Update NGINX port map JSON on the remote host safely using a heredoc and positional parameters -ssh root@10.15.20.69 bash -s -- "$hostname" "$container_ip" "$http_port" "$ssh_port" <<'EOF' + +ssh root@10.15.20.69 bash -s -- "$hostname" "$container_ip" "$ssh_port" "$http_port" <<'EOF' set -euo pipefail hostname="$1" container_ip="$2" -http_port="$3" -ssh_port="$4" +ssh_port="$3" +http_port="$4" -jq --arg hn "$hostname" --arg ip "$container_ip" --argjson port "$http_port" --argjson ssh "$ssh_port" \ - '. + {($hn): {ip: $ip, port: $port, ssh: $ssh}}' /etc/nginx/port_map.json > /tmp/port_map.json.new +jq --arg hn "$hostname" \ + --arg ip "$container_ip" \ + --argjson http "$http_port" \ + --argjson ssh "$ssh_port" \ + '. + {($hn): {ip: $ip, ports: {ssh: $ssh, http: $http}}}' /etc/nginx/port_map.json > /tmp/port_map.json.new mv -f /tmp/port_map.json.new /etc/nginx/port_map.json nginx -s reload EOF -echo "✅ Registered $hostname → $container_ip" -echo "🌐 HTTP port: $http_port" -echo "🔐 SSH port: $ssh_port" \ No newline at end of file +fi + +# Results + +# Define high-contrast colors +BOLD='\033[1m' +BLUE='\033[34m' +MAGENTA='\033[35m' +GREEN='\033[32m' +CYAN='\033[36m' +RESET='\033[0m' + +# Top border and title +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +echo -e "${BOLD}🔔 ${MAGENTA}COPY THESE PORTS DOWN${RESET} — ${CYAN}For External Access${RESET}" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" +echo -e "📌 ${BLUE}Note:${RESET} Your container listens on default ports internally," +echo -e " but EXTERNAL traffic must use the ports listed below:" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + +# Port info +echo -e "✅ ${GREEN}Hostname Registration:${RESET} $hostname → $container_ip" +echo -e "🔐 ${MAGENTA}SSH Port :${RESET} $ssh_port" +echo -e "🌐 ${BLUE}HTTP Port :${RESET} $http_port" + +# Additional protocols (if any) +if [ ! -z "$ADDITIONAL_PROTOCOLS" ]; then + for i in "${!list_all_protocols[@]}"; do + echo -e "📡 ${CYAN}${list_all_protocols[$i]} Port :${RESET} ${list_all_ports[$i]}" + done +fi + +# Bottom border +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" \ No newline at end of file diff --git a/intern-phxdc-pve1/register_proxy_hook.sh b/intern-phxdc-pve1/register_proxy_hook.sh index 91da08dc..a4ade333 100644 --- a/intern-phxdc-pve1/register_proxy_hook.sh +++ b/intern-phxdc-pve1/register_proxy_hook.sh @@ -1,8 +1,6 @@ #!/bin/bash - # /var/lib/vz/snippets/register_proxy_hook.sh -set -x # Enable debug output echo "DEBUG: Hook script /var/lib/vz/snippets/register_proxy_hook.sh started. Event: $2, CTID: $1" >> /tmp/hook_debug.log # Hook script for container events