Created
April 21, 2026 11:54
-
-
Save kking124/3917f550b74bc6492d0d040e88049d75 to your computer and use it in GitHub Desktop.
Configure Ubuntu 24.04 (Proxmox VM/LXC) as a remote devcontainer host
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ============================================================================= | |
| # setup-devcontainer-host.sh | |
| # Configure Ubuntu 24.04 (Proxmox VM/LXC) as a remote devcontainer host | |
| # Usage: sudo bash setup-devcontainer-host.sh [OPTIONS] | |
| # | |
| # Options: | |
| # --user <name> Dev user to create (default: devuser) | |
| # --ssh-port <n> SSH port (default: 22) | |
| # --skip-docker Skip Docker installation | |
| # --skip-hardening Skip SSH/firewall hardening | |
| # | |
| # Copyright 2026 https://github.com/kking124 All Rights Reserved | |
| # WARNING: BUILT USING AI | |
| # ============================================================================= | |
| set -euo pipefail | |
| # ── Defaults ───────────────────────────────────────────────────────────────── | |
| DEV_USER="devuser" | |
| SSH_PORT=22 | |
| SKIP_DOCKER=false | |
| SKIP_HARDENING=false | |
| # ── Argument parsing ────────────────────────────────────────────────────────── | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --user) DEV_USER="$2"; shift 2 ;; | |
| --ssh-port) SSH_PORT="$2"; shift 2 ;; | |
| --skip-docker) SKIP_DOCKER=true; shift ;; | |
| --skip-hardening) SKIP_HARDENING=true; shift ;; | |
| *) echo "Unknown option: $1"; exit 1 ;; | |
| esac | |
| done | |
| # ── Helpers ─────────────────────────────────────────────────────────────────── | |
| log() { echo -e "\e[1;34m[INFO]\e[0m $*"; } | |
| ok() { echo -e "\e[1;32m[ OK ]\e[0m $*"; } | |
| warn() { echo -e "\e[1;33m[WARN]\e[0m $*"; } | |
| die() { echo -e "\e[1;31m[ERR ]\e[0m $*" >&2; exit 1; } | |
| require_root() { [[ $EUID -eq 0 ]] || die "Run as root: sudo bash $0"; } | |
| require_root | |
| # ── 1. System update ────────────────────────────────────────────────────────── | |
| log "Updating system packages…" | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -qq | |
| apt-get upgrade -y -qq | |
| apt-get install -y -qq \ | |
| curl wget git ca-certificates gnupg lsb-release \ | |
| apt-transport-https software-properties-common \ | |
| ufw fail2ban unzip jq htop vim | |
| ok "System updated" | |
| # ── 2. Create dev user ──────────────────────────────────────────────────────── | |
| log "Setting up user: $DEV_USER" | |
| if id "$DEV_USER" &>/dev/null; then | |
| warn "User $DEV_USER already exists — skipping creation" | |
| else | |
| useradd -m -s /bin/bash -G sudo "$DEV_USER" | |
| # Lock password login — key-only auth enforced below | |
| passwd -l "$DEV_USER" | |
| ok "User $DEV_USER created (password login disabled)" | |
| fi | |
| # Ensure .ssh directory exists | |
| DEV_HOME=$(getent passwd "$DEV_USER" | cut -d: -f6) | |
| mkdir -p "$DEV_HOME/.ssh" | |
| chmod 700 "$DEV_HOME/.ssh" | |
| touch "$DEV_HOME/.ssh/authorized_keys" | |
| chmod 600 "$DEV_HOME/.ssh/authorized_keys" | |
| chown -R "$DEV_USER:$DEV_USER" "$DEV_HOME/.ssh" | |
| # ── 3. Docker ───────────────────────────────────────────────────────────────── | |
| if [[ "$SKIP_DOCKER" == false ]]; then | |
| log "Installing Docker Engine…" | |
| if command -v docker &>/dev/null; then | |
| warn "Docker already installed ($(docker --version)) — skipping" | |
| else | |
| install -m 0755 -d /etc/apt/keyrings | |
| curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ | |
| | gpg --dearmor -o /etc/apt/keyrings/docker.gpg | |
| chmod a+r /etc/apt/keyrings/docker.gpg | |
| echo \ | |
| "deb [arch=$(dpkg --print-architecture) \ | |
| signed-by=/etc/apt/keyrings/docker.gpg] \ | |
| https://download.docker.com/linux/ubuntu \ | |
| $(lsb_release -cs) stable" \ | |
| > /etc/apt/sources.list.d/docker.list | |
| apt-get update -qq | |
| apt-get install -y -qq \ | |
| docker-ce docker-ce-cli containerd.io \ | |
| docker-buildx-plugin docker-compose-plugin | |
| systemctl enable --now docker | |
| ok "Docker installed: $(docker --version)" | |
| fi | |
| # Add dev user to docker group | |
| usermod -aG docker "$DEV_USER" | |
| ok "$DEV_USER added to docker group" | |
| else | |
| log "Skipping Docker installation (--skip-docker)" | |
| fi | |
| # ── 4. SSH hardening ────────────────────────────────────────────────────────── | |
| if [[ "$SKIP_HARDENING" == false ]]; then | |
| log "Hardening SSH (port $SSH_PORT)…" | |
| SSHD_CONFIG=/etc/ssh/sshd_config | |
| # Back up original | |
| cp -n "$SSHD_CONFIG" "${SSHD_CONFIG}.bak.$(date +%s)" | |
| # Apply settings — idempotent sed (replace if exists, append if not) | |
| set_sshd() { | |
| local key="$1" val="$2" | |
| if grep -qE "^#?${key}\s" "$SSHD_CONFIG"; then | |
| sed -i -E "s|^#?${key}\s.*|${key} ${val}|" "$SSHD_CONFIG" | |
| else | |
| echo "${key} ${val}" >> "$SSHD_CONFIG" | |
| fi | |
| } | |
| set_sshd Port "$SSH_PORT" | |
| set_sshd PermitRootLogin "no" | |
| set_sshd PasswordAuthentication "no" | |
| set_sshd PubkeyAuthentication "yes" | |
| set_sshd AuthorizedKeysFile ".ssh/authorized_keys" | |
| set_sshd X11Forwarding "no" | |
| set_sshd AllowTcpForwarding "yes" # required for VS Code remote tunnels | |
| set_sshd GatewayPorts "no" | |
| set_sshd MaxAuthTries "3" | |
| set_sshd ClientAliveInterval "120" | |
| set_sshd ClientAliveCountMax "3" | |
| sshd -t || die "sshd_config validation failed — check ${SSHD_CONFIG}" | |
| systemctl restart ssh | |
| ok "SSH hardened on port $SSH_PORT" | |
| else | |
| log "Skipping SSH hardening (--skip-hardening)" | |
| fi | |
| # ── 5. Firewall ─────────────────────────────────────────────────────────────── | |
| if [[ "$SKIP_HARDENING" == false ]]; then | |
| log "Configuring UFW firewall…" | |
| ufw --force reset | |
| ufw default deny incoming | |
| ufw default allow outgoing | |
| ufw allow "$SSH_PORT/tcp" comment "SSH" | |
| # Uncomment if you expose additional services: | |
| # ufw allow 8080/tcp comment "Dev HTTP" | |
| ufw --force enable | |
| ok "UFW enabled — only port $SSH_PORT inbound allowed" | |
| else | |
| log "Skipping UFW setup (--skip-hardening)" | |
| fi | |
| # ── 6. Fail2ban ─────────────────────────────────────────────────────────────── | |
| if [[ "$SKIP_HARDENING" == false ]]; then | |
| log "Configuring fail2ban…" | |
| cat > /etc/fail2ban/jail.local <<EOF | |
| [DEFAULT] | |
| bantime = 1h | |
| findtime = 10m | |
| maxretry = 5 | |
| [sshd] | |
| enabled = true | |
| port = $SSH_PORT | |
| logpath = %(sshd_log)s | |
| backend = %(sshd_backend)s | |
| EOF | |
| systemctl enable --now fail2ban | |
| ok "fail2ban enabled" | |
| fi | |
| # ── 7. Proxmox guest agent ──────────────────────────────────────────────────── | |
| log "Installing QEMU guest agent (for Proxmox VM integration)…" | |
| if systemd-detect-virt -q 2>/dev/null; then | |
| apt-get install -y -qq qemu-guest-agent | |
| # Ubuntu 24.04's qemu-guest-agent unit has no [Install] section — | |
| # it starts via udev/socket activation, so 'enable' is not needed. | |
| systemctl start qemu-guest-agent || true | |
| if systemctl is-active --quiet qemu-guest-agent; then | |
| ok "QEMU guest agent running" | |
| else | |
| warn "QEMU guest agent not active — may start automatically via udev" | |
| fi | |
| else | |
| warn "Not running in a VM — skipping guest agent" | |
| fi | |
| # ── 8. Docker daemon tuning ─────────────────────────────────────────────────── | |
| if [[ "$SKIP_DOCKER" == false ]]; then | |
| log "Applying Docker daemon settings…" | |
| mkdir -p /etc/docker | |
| cat > /etc/docker/daemon.json <<'EOF' | |
| { | |
| "log-driver": "json-file", | |
| "log-opts": { | |
| "max-size": "20m", | |
| "max-file": "5" | |
| }, | |
| "storage-driver": "overlay2", | |
| "live-restore": true, | |
| "userland-proxy": false | |
| } | |
| EOF | |
| systemctl reload-or-restart docker | |
| ok "Docker daemon configured" | |
| fi | |
| # ── 9. Summary ──────────────────────────────────────────────────────────────── | |
| HOST_IP=$(hostname -I | awk '{print $1}') | |
| echo "" | |
| echo "========================================================" | |
| echo " Setup complete" | |
| echo "========================================================" | |
| echo "" | |
| echo " Host IP : $HOST_IP" | |
| echo " SSH port : $SSH_PORT" | |
| echo " Dev user : $DEV_USER" | |
| echo "" | |
| echo " NEXT STEPS:" | |
| echo "" | |
| echo " 1. Add your public SSH key to the dev user:" | |
| echo "" | |
| echo " # From your local machine:" | |
| echo " ssh-copy-id -p $SSH_PORT $DEV_USER@$HOST_IP" | |
| echo "" | |
| echo " # Or paste manually:" | |
| echo " echo 'ssh-ed25519 AAAA... you@host' \\" | |
| echo " >> $DEV_HOME/.ssh/authorized_keys" | |
| echo "" | |
| echo " 2. Test SSH access:" | |
| echo " ssh -p $SSH_PORT $DEV_USER@$HOST_IP" | |
| echo "" | |
| echo " 3. In VS Code, install the 'Remote - SSH' extension" | |
| echo " then connect to: $DEV_USER@$HOST_IP:$SSH_PORT" | |
| echo "" | |
| echo " 4. Open a folder or clone a repo — VS Code will offer" | |
| echo " to 'Reopen in Container' if a .devcontainer/ is found." | |
| echo "" | |
| echo " Tip: add this to ~/.ssh/config on your local machine:" | |
| echo "" | |
| echo " Host proxmox-dev" | |
| echo " HostName $HOST_IP" | |
| echo " User $DEV_USER" | |
| echo " Port $SSH_PORT" | |
| echo " ForwardAgent yes" | |
| echo "" | |
| echo "========================================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment