Files
nixos-infra-framework/scripts/create-lxc-nixos.sh
T

325 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
# --- Dependencies ---
# Check if docopts is installed (for Bash)
if ! command -v docopts &> /dev/null; then
echo "❌ Error: 'docopts' is required for Bash." >&2
echo "See https://github.com/docopt/docopts to install it." >&2
exit 1
fi
# --- Usage and Documentation ---
usage="Create, bootstrap and deploy a NixOS LXC container on a remote Proxmox VE 9 server.
Usage:
$0 <short_name> [options]
$0 -h|--help
Options:
-h, --help Show this message.
-t, --template TEMPLATE LXC template (e.g. local:vztmpl/nixos-unstable).
-r, --rootfs-size SIZE Root filesystem size (e.g. 8G).
-c, --cores CORES Number of CPU cores.
-m, --memory MEMORY RAM in MiB.
-s, --swap SWAP Swap in MiB.
-p, --password PASSWORD Root password for the container.
-b, --bridge BRIDGE Network bridge (e.g. vmbr0).
-v, --vlan VLAN VLAN tag (e.g. tag=10).
-d, --domain DOMAIN DNS domain.
-u, --unprivileged UNPRIV Unprivileged container (0 or 1).
-i, --ip IP Static IPv4 address (e.g. 192.168.1.100/24).
--ip6 TOKEN IPv6 token for SLAAC (e.g. ::1:2:3:4).
-C, --cmode CMODE Console mode (console or tty). Default: console.
-T, --tags TAGS Tags for the container (optional).
-k, --ssh-public-keys KEYS SSH public keys for the container.
--pve-host HOST Proxmox host (e.g. pve).
--pve-user USER Proxmox user (default: admin).
--pve-port PORT SSH port for Proxmox (default: 22).
--pve-ssh-key KEY SSH key file for authentication.
--initial-config FILE Initial NixOS configuration file to push
[default: ./initial-lxc-configuration.nix].
--repo-url URL Git repository URL for deploy.sh
[default: (none, must be provided)].
--branch BRANCH Git branch for deploy.sh [default: main].
--environment ENV Environment name (production, dev, etc.)
[default: (none, must be provided or set via config)].
--skip-deploy Skip the post-creation bootstrap (push + nixos-rebuild).
--dry-run Simulate container creation without execution.
Optional configuration files (loaded in order, later overrides earlier):
/etc/nixos-infra/hosts/config
\${XDG_CONFIG_HOME}/nixos-infra/hosts/config
./config
"
# --- Default Parameters (Environment Variables) ---
# Proxmox Server
PVE_HOST="${PVE_HOST:-}"
PVE_USER="${PVE_USER:-admin}"
PVE_PORT="${PVE_PORT:-22}"
PVE_SSH_KEY="${PVE_SSH_KEY:-}"
DRY_RUN="${DRY_RUN:-false}"
# LXC Container
TEMPLATE="${TEMPLATE:-local:vztmpl/nixos-unstable-amd64-default_20260428}"
ROOTFS_SIZE="${ROOTFS_SIZE:-8G}"
CORES="${CORES:-2}"
MEMORY="${MEMORY:-2048}"
SWAP="${SWAP:-1024}"
PASSWORD="${PASSWORD:-changeme}"
BRIDGE="${BRIDGE:-vmbr0}"
VLAN="${VLAN:-}"
DOMAIN="${DOMAIN:-}"
UNPRIVILEGED="${UNPRIVILEGED:-0}"
IP="${IP:-}"
IP6="${IP6:-}"
CMODE="${CMODE:-console}"
TAGS="${TAGS:-}"
SSH_PUBLIC_KEYS="${SSH_PUBLIC_KEYS:-}"
# Bootstrap
INITIAL_CONFIG="${INITIAL_CONFIG:-./initial-lxc-configuration.nix}"
REPO_URL="${REPO_URL:-}"
BRANCH="${BRANCH:-main}"
SKIP_DEPLOY="${SKIP_DEPLOY:-false}"
ENVIRONMENT="${ENVIRONMENT:-}"
# --- Parse Arguments with docopts (Highest priority) ---
# set +e is to prevent set -e from eating the error message from docopts.
# This code is up here to prevent useless error messages to be printed
# in case the "-h" or "--help" argument is used.
set +e
args=$(docopts -h "$usage" : "$@")
eval "$args"
set -e
# --- Apply Configuration Files (by increasing priority) ---
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
CONFIG_FILES=(\
"/etc/nixos-infra/hosts/config" \
"$XDG_CONFIG_HOME/nixos-infra/hosts/config" \
"./config")
for conffile in ${CONFIG_FILES[*]}; do
if [ -f "$conffile" ]; then
echo "📄 Applying parameters from $conffile..."
set -a
source "$conffile"
set +a
else
echo "$conffile not found (optional)."
fi
done
# Override with CLI arguments (have priority over config files)
PVE_HOST="${pve_host:-$PVE_HOST}"
PVE_USER="${pve_user:-$PVE_USER}"
PVE_PORT="${pve_port:-$PVE_PORT}"
PVE_SSH_KEY="${pve_ssh_key:-$PVE_SSH_KEY}"
DRY_RUN="${dry_run:-$DRY_RUN}"
TEMPLATE="${template:-$TEMPLATE}"
ROOTFS_SIZE="${rootfs_size:-$ROOTFS_SIZE}"
CORES="${cores:-$CORES}"
MEMORY="${memory:-$MEMORY}"
SWAP="${swap:-$SWAP}"
PASSWORD="${password:-$PASSWORD}"
BRIDGE="${bridge:-$BRIDGE}"
VLAN="${vlan:-$VLAN}"
DOMAIN="${domain:-$DOMAIN}"
UNPRIVILEGED="${unprivileged:-$UNPRIVILEGED}"
IP="${ip:-$IP}"
IP6="${ip6:-$IP6}"
CMODE="${cmode:-$CMODE}"
TAGS="${tags:-$TAGS}"
SSH_PUBLIC_KEYS="${ssh_public_keys:-$SSH_PUBLIC_KEYS}"
INITIAL_CONFIG="${initial_config:-$INITIAL_CONFIG}"
REPO_URL="${repo_url:-$REPO_URL}"
BRANCH="${branch:-$BRANCH}"
SKIP_DEPLOY="${skip_deploy:-$SKIP_DEPLOY}"
ENVIRONMENT="${environment:-$ENVIRONMENT}"
# --- SSH Key Default Logic ---
if [ "$PVE_SSH_KEY" = "default" ]; then
PVE_SSH_KEY="${HOME}/.ssh/id_${PVE_USER}"
fi
# --- Critical Parameters Validation ---
mandatory_params=(
"TEMPLATE"
"ROOTFS_SIZE"
"CORES"
"MEMORY"
"SWAP"
"PASSWORD"
"BRIDGE"
"DOMAIN"
"UNPRIVILEGED"
"CMODE"
"SSH_PUBLIC_KEYS"
"PVE_HOST"
"PVE_USER"
"PVE_PORT"
)
missing_params=()
for param in ${mandatory_params[*]}; do
if [ -z "${!param}" ]; then missing_params+=("$param"); fi
done
if [ ${#missing_params[@]} -gt 0 ]; then
echo "❌ Error: The following necessary parameters are missing: ${missing_params[*]}" >&2
echo "❌ Error: Please provide them through a config file or the command line." >&2
exit 1
fi
# Authentication Validation
if [ ! -f "$PVE_SSH_KEY" ]; then
echo "❌ Error: SSH key file '$PVE_SSH_KEY' does not exist." >&2
exit 1
fi
# Validate initial-config.nix exists (unless --skip-deploy)
if [ "$SKIP_DEPLOY" != "true" ] && [ ! -f "$INITIAL_CONFIG" ]; then
echo "❌ Error: Initial NixOS configuration '$INITIAL_CONFIG' not found." >&2
echo " Provide a valid path with --initial-config or use --skip-deploy." >&2
exit 1
fi
# Validate deploy.sh exists (unless --skip-deploy)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOY_SCRIPT="${SCRIPT_DIR}/deploy.sh"
if [ "$SKIP_DEPLOY" != "true" ] && [ ! -f "$DEPLOY_SCRIPT" ]; then
echo "❌ Error: deploy.sh not found at '$DEPLOY_SCRIPT'." >&2
echo " The bootstrap phase requires deploy.sh to be present." >&2
exit 1
fi
# --- SSH Connection to Proxmox Server ---
run_proxmox() {
local ssh_cmd="ssh -p $PVE_PORT"
if [ -n "$PVE_SSH_KEY" ] && [ -f "$PVE_SSH_KEY" ]; then
ssh_cmd="$ssh_cmd -i $PVE_SSH_KEY "
else
ssh_cmd="$ssh_cmd -o PreferredAuthentications=password "
fi
$ssh_cmd "$PVE_USER@$PVE_HOST" "$1"
}
# --- Network Options Construction ---
NET_OPTS="name=eth0,bridge=$BRIDGE"
if [ -n "$VLAN" ]; then
NET_OPTS="$NET_OPTS,$VLAN"
fi
if [ -n "$IP" ]; then
NET_OPTS="$NET_OPTS,ip=$IP"
fi
# IPv6: use SLAAC with a token if provided, otherwise DHCP
if [ -n "$IP6" ]; then
NET_OPTS="$NET_OPTS,ip6=auto,token6=${IP6}"
else
NET_OPTS="$NET_OPTS,ip6=dhcp"
fi
# --- Container Creation ---
echo "🚀 Creating LXC container $short_name on $PVE_HOST (domain: $DOMAIN)..."
CREATE_CMD="pct create $ROOTFS_SIZE $TEMPLATE --cores $CORES \
--memory $MEMORY --swap $SWAP --hostname $short_name.$DOMAIN \
--password $PASSWORD --unprivileged $UNPRIVILEGED --net0 $NET_OPTS \
--onboot 0 --cmode $CMODE --ssh-public-keys $SSH_PUBLIC_KEYS"
if [ -n "$TAGS" ]; then
CREATE_CMD="$CREATE_CMD --tags $TAGS"
fi
# Display the command (with password masked)
DISPLAY_CMD=$(echo "$CREATE_CMD" |
sed "s/--password [^ ]*/--password \*\*\*\*\*/g")
echo "🔧 Command to execute on $PVE_HOST: $DISPLAY_CMD"
# Execute or simulate
if [ "$DRY_RUN" = "true" ]; then
echo "🧪 Dry run mode:"
echo " - Container creation skipped"
echo " - Bootstrap phase skipped"
exit 0
fi
LXC_ID=$(run_proxmox "$CREATE_CMD" | grep -oP '\d+')
if [ -z "$LXC_ID" ]; then
echo "❌ Error: Failed to create the container." >&2
exit 1
fi
echo "✅ LXC container $short_name created successfully (ID: $LXC_ID)."
# --- Bootstrap Phase (unless --skip-deploy) ---
if [ "$SKIP_DEPLOY" = "true" ]; then
echo "⏭️ --skip-deploy set. Container created but not bootstrapped."
echo " Start it manually with: pct start $LXC_ID"
echo " Then apply a configuration manually."
exit 0
fi
echo ""
echo "🚀 Starting bootstrap phase for CT $LXC_ID..."
# 1. Start the container
echo "▶️ Starting container $LXC_ID..."
run_proxmox "pct start $LXC_ID" || {
echo "❌ Error: Failed to start container $LXC_ID." >&2
exit 1
}
# 2. Wait for the container to be ready (SSH or pct exec available)
echo "⏳ Waiting for container to be ready..."
for i in $(seq 1 30); do
if run_proxmox "pct exec $LXC_ID -- true" 2>/dev/null; then
echo "✅ Container $LXC_ID is ready."
break
fi
if [ "$i" -eq 30 ]; then
echo "❌ Error: Container $LXC_ID did not become ready in time." >&2
echo " You can retry bootstrap manually." >&2
exit 1
fi
sleep 2
done
# 3. Push initial-lxc-configuration.nix
echo "📄 Pushing initial NixOS configuration..."
run_proxmox "pct push $LXC_ID '$INITIAL_CONFIG' /etc/nixos/configuration.nix" || {
echo "❌ Error: Failed to push initial configuration." >&2
exit 1
}
# 4. Push deploy.sh
echo "📄 Pushing deploy script..."
run_proxmox "pct push $LXC_ID '$DEPLOY_SCRIPT' /usr/local/bin/deploy-nixos" || {
echo "❌ Error: Failed to push deploy script." >&2
exit 1
}
run_proxmox "pct exec $LXC_ID -- chmod +x /usr/local/bin/deploy-nixos" || {
echo "❌ Error: Failed to make deploy script executable." >&2
exit 1
}
# 5. Apply initial configuration (nixos-rebuild switch)
echo "⚙️ Applying initial NixOS configuration (this may take a while)..."
if ! run_proxmox "pct exec $LXC_ID -- nixos-rebuild switch" 2>&1; then
echo "⚠️ Warning: Initial nixos-rebuild may have issues." >&2
echo " Check the container manually: pct exec $LXC_ID -- nixos-rebuild switch" >&2
# Continue anyway — deploy.sh might still work
fi
echo "✅ Initial configuration applied."
# 6. Run deploy.sh to clone the repo and apply the specific configuration
echo "🌐 Running deploy.sh to clone repo and apply specific configuration..."
# Pass REPO_URL, BRANCH and ENVIRONMENT as env vars to deploy.sh inside the container
DEPLOY_CMD="REPO_URL='$REPO_URL' BRANCH='$BRANCH' ENVIRONMENT='$ENVIRONMENT' /usr/local/bin/deploy-nixos"
if ! run_proxmox "pct exec $LXC_ID -- bash -c '$DEPLOY_CMD'" 2>&1; then
echo "⚠️ Warning: deploy.sh encountered issues." >&2
echo " You can retry manually: pct exec $LXC_ID -- /usr/local/bin/deploy-nixos" >&2
exit 1
fi
echo ""
echo "🎉 Container $short_name (CT $LXC_ID) successfully created and deployed!"
echo " Connect with: ssh root@${IP%%/*} (or use pct exec $LXC_ID)"