Skip to content

Instantly share code, notes, and snippets.

@kking124
Created April 21, 2026 11:54
Show Gist options
  • Select an option

  • Save kking124/3917f550b74bc6492d0d040e88049d75 to your computer and use it in GitHub Desktop.

Select an option

Save kking124/3917f550b74bc6492d0d040e88049d75 to your computer and use it in GitHub Desktop.
Configure Ubuntu 24.04 (Proxmox VM/LXC) as a remote devcontainer host
#!/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