<message>Add a new step-by-step guide for deploying the CM4 eMMC provisioning service on a new Proxmox instance, enhancing clarity for users. Update existing documentation to reflect changes in network configuration options, including the introduction of LAN subnet settings for DHCP and TFTP. Modify cloud-init scripts to ensure proper management of DNS settings and improve the handling of network interfaces. Additionally, enhance the toggle script for network boot to dynamically read the LAN gateway from configuration files, streamlining the setup process and improving user experience.
423 lines
22 KiB
Bash
Executable File
423 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Revision: 2
|
|
# Deploy CM4 eMMC provisioning to a Proxmox host: host scripts, udev, systemd units,
|
|
# LXC container (dashboard), usbboot (rpiboot), and PiShrink. Uses hostname "cm4-provisioning"
|
|
# to find the container on redeploy; creates with next available ID if not found.
|
|
#
|
|
# Redeploy (re-run) behaviour: checks first if the cm4-provisioning container exists;
|
|
# if so, skips storage selection. Skips other steps that are already configured so you
|
|
# can update only what changed. Always updates: host scripts, dashboard files, env,
|
|
# systemd/udev. Skips when present: LXC creation, backups bind-mount (if same path),
|
|
# usbboot, PiShrink, LXC python3-flask and openssh-server apt installs. Set
|
|
# DEPLOY_ROOTFS_STORAGE to avoid storage prompt on first deploy (when container is new).
|
|
#
|
|
# With host internet: installs usbboot and PiShrink so USB flash/backup and dashboard
|
|
# Shrink/Compress work. The only manual step left is to add a golden image for Deploy.
|
|
#
|
|
# Usage: ./deploy-to-proxmox.sh [proxmox_host]
|
|
# Example: ./deploy-to-proxmox.sh root@10.20.30.152
|
|
#
|
|
# Optional env:
|
|
# DEPLOY_ROOTFS_STORAGE=name — LXC rootfs storage (if set and valid, skips interactive choice; otherwise script lists storages and asks for number)
|
|
# CM4_BACKUPS_HOST_PATH=/path — host dir for backups; bind-mounted into LXC
|
|
# DEPLOY_LXC_ROOT_PASSWORD=secret — set root password in LXC and enable SSH
|
|
# DEPLOY_LXC_SSH_KEY=/path/to/pub — copy this key to LXC root (default: ~/.ssh/id_ed25519.pub or id_rsa.pub)
|
|
# DEPLOY_LOG=1 — also log to deploy-YYYYMMDD-HHMMSS.log
|
|
# DEPLOY_LXC_WAN_BRIDGE=vmbr0 — Proxmox bridge for WAN (eth0); default vmbr0
|
|
# DEPLOY_LXC_WAN_IP=dhcp — WAN address: dhcp (default) or static e.g. 192.168.1.10/24
|
|
# DEPLOY_LXC_LAN_BRIDGE=vmbr1 — If set, add eth1 as LAN on this bridge (e.g. provisioning / network-boot)
|
|
# DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24 — LXC IP on LAN (gateway); used only if DEPLOY_LXC_LAN_BRIDGE is set; default 10.20.50.1/24
|
|
#
|
|
# Legacy: DEPLOY_LXC_NET1="name=eth1,bridge=vmbr1,ip=10.20.50.1/24" still works; overridden by DEPLOY_LXC_LAN_BRIDGE + DEPLOY_LXC_LAN_SUBNET if both are set.
|
|
#
|
|
# Requires: ssh key access to root@<host>. For full install (usbboot, PiShrink), host needs internet.
|
|
|
|
set -e
|
|
PROXMOX="${1:-root@10.130.60.224}"
|
|
# ROOTFS_STORAGE set later: from DEPLOY_ROOTFS_STORAGE (if in host list) or from interactive choice
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
|
LOG_FILE=""
|
|
if [[ -n "${DEPLOY_LOG:-}" ]]; then
|
|
LOG_FILE="$SCRIPT_DIR/deploy-$(date +%Y%m%d-%H%M%S).log"
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
echo "[$(date -Iseconds)] Logging to $LOG_FILE"
|
|
fi
|
|
|
|
log() { echo "[$(date -Iseconds)] $*"; }
|
|
|
|
# Optional: gather SSH key and LXC root password for setup inside deploy
|
|
# Default LXC root password (base64); override with DEPLOY_LXC_ROOT_PASSWORD. Not secure if repo is shared.
|
|
DEFAULT_LXC_PWD_B64="c2gxcGIweDE="
|
|
DEPLOY_SSH_KEY_B64=""
|
|
DEPLOY_LXC_PWD_B64=""
|
|
if [[ -n "${DEPLOY_LXC_ROOT_PASSWORD:-}" ]]; then
|
|
DEPLOY_LXC_PWD_B64=$(echo -n "$DEPLOY_LXC_ROOT_PASSWORD" | base64 -w 0 2>/dev/null || base64 2>/dev/null | tr -d '\n')
|
|
log "Will set LXC root password (from DEPLOY_LXC_ROOT_PASSWORD)."
|
|
else
|
|
DEPLOY_LXC_PWD_B64="$DEFAULT_LXC_PWD_B64"
|
|
log "Will set LXC root password (default)."
|
|
fi
|
|
KEY_FILE="${DEPLOY_LXC_SSH_KEY:-}"
|
|
if [[ -z "$KEY_FILE" ]]; then
|
|
for f in ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub; do
|
|
[[ -f "$f" ]] && { KEY_FILE="$f"; break; }
|
|
done
|
|
fi
|
|
if [[ -n "$KEY_FILE" && -f "$KEY_FILE" ]]; then
|
|
DEPLOY_SSH_KEY_B64=$(base64 -w 0 < "$KEY_FILE" 2>/dev/null || base64 < "$KEY_FILE" 2>/dev/null | tr -d '\n')
|
|
log "Will copy SSH key to LXC: $KEY_FILE"
|
|
fi
|
|
|
|
log "Deploying to $PROXMOX ..."
|
|
log "[1/5] Checking if cm4-provisioning container exists and listing storage ..."
|
|
REMOTE_CHECK=$(ssh "$PROXMOX" "bash -s" << 'REMOTECHECK'
|
|
LXC_HOSTNAME="cm4-provisioning"
|
|
LXC_EXISTS=0
|
|
for id in $(pct list 2>/dev/null | awk 'NR>1 {print $1}'); do
|
|
h=$(pct config "$id" 2>/dev/null | sed -n 's/^hostname: *//p')
|
|
if [[ "$h" == "$LXC_HOSTNAME" ]]; then LXC_EXISTS=1; break; fi
|
|
done
|
|
echo "LXC_EXISTS=$LXC_EXISTS"
|
|
pvesm status 2>/dev/null | awk 'NR>1 { sub(/^[ \t]+/, ""); if ($2!="pbs" && $3~/active/) print $1 }'
|
|
REMOTECHECK
|
|
)
|
|
|
|
first_line=
|
|
storages=()
|
|
while IFS= read -r line; do
|
|
if [[ -z "$first_line" ]]; then
|
|
first_line="$line"
|
|
else
|
|
[[ -n "$line" ]] && storages+=("$line")
|
|
fi
|
|
done <<< "$REMOTE_CHECK"
|
|
|
|
if [[ "$first_line" == "LXC_EXISTS=1" ]]; then
|
|
LXC_ALREADY_EXISTS=1
|
|
log "Container cm4-provisioning already exists; no storage selection needed."
|
|
if [[ ${#storages[@]} -eq 0 ]]; then
|
|
log "Error: no Proxmox storage found on host (needed for validation). Run on host: pvesm status"
|
|
exit 1
|
|
fi
|
|
ROOTFS_STORAGE="${storages[0]}"
|
|
log "Using storage $ROOTFS_STORAGE for validation only."
|
|
else
|
|
LXC_ALREADY_EXISTS=0
|
|
if [[ ${#storages[@]} -eq 0 ]]; then
|
|
log "Error: no Proxmox storage found on host (PBS excluded). Run on host: pvesm status"
|
|
exit 1
|
|
fi
|
|
# --- Ask user to select LXC rootfs storage only when creating new container ---
|
|
if [[ -n "${DEPLOY_ROOTFS_STORAGE:-}" ]]; then
|
|
for s in "${storages[@]}"; do
|
|
if [[ "$s" == "$DEPLOY_ROOTFS_STORAGE" ]]; then
|
|
ROOTFS_STORAGE="$DEPLOY_ROOTFS_STORAGE"
|
|
log "Using storage: $ROOTFS_STORAGE (from DEPLOY_ROOTFS_STORAGE)"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
if [[ -z "${ROOTFS_STORAGE:-}" ]]; then
|
|
echo ""
|
|
echo "Available storage on $PROXMOX (PBS excluded):"
|
|
for i in "${!storages[@]}"; do echo " $((i+1))) ${storages[i]}"; done
|
|
echo ""
|
|
if [[ ${#storages[@]} -eq 1 ]]; then
|
|
ROOTFS_STORAGE="${storages[0]}"
|
|
log "Using only available storage: $ROOTFS_STORAGE"
|
|
elif [[ ! -t 0 ]]; then
|
|
ROOTFS_STORAGE="${storages[0]}"
|
|
log "No TTY: using first storage $ROOTFS_STORAGE"
|
|
else
|
|
while true; do
|
|
read -r -p "Select storage (1-${#storages[@]}): " num
|
|
if [[ "$num" =~ ^[0-9]+$ ]] && [[ "$num" -ge 1 ]] && [[ "$num" -le ${#storages[@]} ]]; then
|
|
ROOTFS_STORAGE="${storages[num-1]}"
|
|
log "Using storage: $ROOTFS_STORAGE"
|
|
break
|
|
fi
|
|
echo "Invalid choice. Enter a number from 1 to ${#storages[@]}."
|
|
done
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
log "[2/5] Cleaning remote staging dir ..."
|
|
ssh "$PROXMOX" "rm -rf /tmp/emmc-provisioning-deploy"
|
|
log "[3/5] Rsync repo to $PROXMOX ..."
|
|
rsync -a "$REPO_DIR/" "$PROXMOX:/tmp/emmc-provisioning-deploy/" --exclude='.git' --exclude='scripts/deploy-to-proxmox.sh' --exclude='scripts/deploy-*.log'
|
|
|
|
log "[4/5] Running remote install (host + LXC) ..."
|
|
|
|
# Pass optional LXC SSH vars (base64), selected storage, and network (WAN/LAN bridge + subnet)
|
|
ssh "$PROXMOX" "ROOTFS_STORAGE='$ROOTFS_STORAGE' CM4_BACKUPS_HOST_PATH='${CM4_BACKUPS_HOST_PATH:-}' DEPLOY_SSH_KEY_B64='${DEPLOY_SSH_KEY_B64:-}' DEPLOY_LXC_PWD_B64='${DEPLOY_LXC_PWD_B64:-}' DEPLOY_LXC_WAN_BRIDGE='${DEPLOY_LXC_WAN_BRIDGE:-}' DEPLOY_LXC_WAN_IP='${DEPLOY_LXC_WAN_IP:-}' DEPLOY_LXC_LAN_BRIDGE='${DEPLOY_LXC_LAN_BRIDGE:-}' DEPLOY_LXC_LAN_SUBNET='${DEPLOY_LXC_LAN_SUBNET:-}' DEPLOY_LXC_NET1='${DEPLOY_LXC_NET1:-}'" bash -s << 'REMOTE'
|
|
set -e
|
|
DEPLOY=/tmp/emmc-provisioning-deploy
|
|
ROOTFS_STORAGE="${ROOTFS_STORAGE:?ROOTFS_STORAGE not set}"
|
|
BACKUPS_HOST_PATH="${CM4_BACKUPS_HOST_PATH:-}"
|
|
LXC_HOSTNAME="cm4-provisioning"
|
|
log() { echo "[$(date -Iseconds)] $*"; }
|
|
|
|
# Storage already chosen interactively; validate it exists on host (match by name + active, robust to column alignment)
|
|
if ! pvesm status 2>/dev/null | grep -w "$ROOTFS_STORAGE" | grep -q active; then
|
|
log "Error: storage $ROOTFS_STORAGE not found or not valid on host"
|
|
exit 1
|
|
fi
|
|
log "Using storage: $ROOTFS_STORAGE"
|
|
|
|
# --- Find existing LXC by hostname or use next available ID ---
|
|
CTID=""
|
|
for id in $(pct list 2>/dev/null | awk 'NR>1 {print $1}'); do
|
|
h=$(pct config "$id" 2>/dev/null | sed -n 's/^hostname: *//p')
|
|
if [[ "$h" == "$LXC_HOSTNAME" ]]; then
|
|
CTID="$id"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -n "$CTID" ]]; then
|
|
log "Found existing LXC $CTID (hostname: $LXC_HOSTNAME)."
|
|
else
|
|
MAX_ID=$(pct list 2>/dev/null | awk 'NR>1 {print $1}' | sort -n | tail -1)
|
|
[[ -z "$MAX_ID" ]] && MAX_ID=0
|
|
CTID=$((MAX_ID + 1))
|
|
log "Creating LXC $CTID ($LXC_HOSTNAME) (rootfs on ${ROOTFS_STORAGE})..."
|
|
VZTMPL_DIR=/var/lib/vz/template/cache
|
|
DEBIAN12_TMPL=$(ls "$VZTMPL_DIR"/debian-12-standard_*.tar.zst 2>/dev/null | head -1)
|
|
if [[ -z "$DEBIAN12_TMPL" ]]; then
|
|
log "Downloading Debian 12 LXC template..."
|
|
pveam download local debian-12-standard_12.12-1_amd64.tar.zst || pveam download local debian-12-standard_12.7-1_amd64.tar.zst
|
|
DEBIAN12_TMPL=$(ls "$VZTMPL_DIR"/debian-12-standard_*.tar.zst 2>/dev/null | head -1)
|
|
fi
|
|
[[ -z "$DEBIAN12_TMPL" ]] && { log "Error: no Debian 12 template found"; exit 1; }
|
|
TMPL_NAME=$(basename "$DEBIAN12_TMPL")
|
|
# WAN (eth0): bridge and IP from env; default vmbr0 + dhcp
|
|
WAN_BRIDGE="${DEPLOY_LXC_WAN_BRIDGE:-vmbr0}"
|
|
WAN_IP="${DEPLOY_LXC_WAN_IP:-dhcp}"
|
|
# LAN (eth1): optional; use DEPLOY_LXC_LAN_BRIDGE + DEPLOY_LXC_LAN_SUBNET, or legacy DEPLOY_LXC_NET1
|
|
NET1_OPT=""
|
|
if [[ -n "${DEPLOY_LXC_LAN_BRIDGE:-}" ]]; then
|
|
LAN_SUBNET="${DEPLOY_LXC_LAN_SUBNET:-10.20.50.1/24}"
|
|
NET1_OPT="--net1 name=eth1,bridge=${DEPLOY_LXC_LAN_BRIDGE},ip=${LAN_SUBNET}"
|
|
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP; eth1 LAN bridge=$DEPLOY_LXC_LAN_BRIDGE ip=$LAN_SUBNET"
|
|
elif [[ -n "${DEPLOY_LXC_NET1:-}" ]]; then
|
|
NET1_OPT="--net1 $DEPLOY_LXC_NET1"
|
|
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP; eth1 from DEPLOY_LXC_NET1"
|
|
else
|
|
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP (no LAN interface)"
|
|
fi
|
|
pct create "$CTID" "local:vztmpl/${TMPL_NAME}" \
|
|
--hostname "$LXC_HOSTNAME" --memory 1024 --swap 0 --cores 1 \
|
|
--rootfs "${ROOTFS_STORAGE}:8" --net0 name=eth0,bridge="$WAN_BRIDGE",ip="$WAN_IP" $NET1_OPT \
|
|
--unprivileged 0 --features nesting=1 -tag cm4-provisioning
|
|
mkdir -p /var/lib/cm4-provisioning
|
|
pct set "$CTID" -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning
|
|
log "LXC $CTID created and mount configured."
|
|
fi
|
|
|
|
# Optional: bind-mount host directory for backup images (skip if already mounted with same path)
|
|
if [[ -n "$BACKUPS_HOST_PATH" ]]; then
|
|
BACKUPS_PATH_NORM="${BACKUPS_HOST_PATH%/}"
|
|
mkdir -p "$BACKUPS_HOST_PATH"
|
|
CURRENT_MP1=$(pct config "$CTID" 2>/dev/null | sed -n 's/^mp1: *//p')
|
|
NEED_MOUNT=1
|
|
if [[ -n "$CURRENT_MP1" ]]; then
|
|
if [[ "$CURRENT_MP1" == *"mp=/var/lib/cm4-provisioning/backups"* ]] && { [[ "$CURRENT_MP1" == *"$BACKUPS_PATH_NORM"* ]] || [[ "$CURRENT_MP1" == *"$BACKUPS_HOST_PATH"* ]]; }; then
|
|
NEED_MOUNT=0
|
|
log "Backups mount already configured (host $BACKUPS_PATH_NORM), skipping."
|
|
fi
|
|
fi
|
|
if [[ "$NEED_MOUNT" -eq 1 ]]; then
|
|
pct stop "$CTID" 2>/dev/null || true
|
|
pct set "$CTID" -mp1 "$BACKUPS_HOST_PATH",mp=/var/lib/cm4-provisioning/backups
|
|
pct start "$CTID" 2>/dev/null || true
|
|
log "Backups mount: host $BACKUPS_HOST_PATH -> LXC $CTID /var/lib/cm4-provisioning/backups"
|
|
fi
|
|
fi
|
|
|
|
# --- Host: scripts, systemd, udev (always update so changes are applied) ---
|
|
HOST_PROV_EXISTS=0
|
|
[[ -f /opt/cm4-provisioning/flash-emmc-on-connect.sh ]] && HOST_PROV_EXISTS=1
|
|
if [[ "$HOST_PROV_EXISTS" -eq 1 ]]; then
|
|
log "Host: updating scripts and systemd units (already configured) ..."
|
|
else
|
|
log "Host: installing scripts and systemd units ..."
|
|
fi
|
|
mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning
|
|
cp "$DEPLOY/host/flash-emmc-on-connect.sh" /opt/cm4-provisioning/
|
|
chmod +x /opt/cm4-provisioning/flash-emmc-on-connect.sh
|
|
cp "$DEPLOY/host/build-cloudinit-image.sh" /opt/cm4-provisioning/
|
|
chmod +x /opt/cm4-provisioning/build-cloudinit-image.sh
|
|
cp "$DEPLOY/host/run-shrink-on-host.sh" /opt/cm4-provisioning/
|
|
chmod +x /opt/cm4-provisioning/run-shrink-on-host.sh
|
|
cp "$DEPLOY/scripts/fix-gadget-bootcode-on-host.sh" /opt/cm4-provisioning/ 2>/dev/null && chmod +x /opt/cm4-provisioning/fix-gadget-bootcode-on-host.sh || true
|
|
cp "$DEPLOY/host/cm4-flash-trigger.sh" /usr/local/bin/
|
|
chmod +x /usr/local/bin/cm4-flash-trigger.sh
|
|
cp "$DEPLOY/host/cm4-flash.service" /etc/systemd/system/
|
|
cp "$DEPLOY/host/cm4-build-cloudinit.path" /etc/systemd/system/
|
|
cp "$DEPLOY/host/cm4-build-cloudinit.service" /etc/systemd/system/
|
|
cp "$DEPLOY/host/cm4-shrink.path" /etc/systemd/system/
|
|
cp "$DEPLOY/host/cm4-shrink.service" /etc/systemd/system/
|
|
systemctl daemon-reload
|
|
systemctl enable --now cm4-build-cloudinit.path 2>/dev/null || true
|
|
systemctl enable --now cm4-shrink.path 2>/dev/null || true
|
|
cp "$DEPLOY/host/89-cm4-boot-mode-permissions.rules" /etc/udev/rules.d/ 2>/dev/null || true
|
|
cp "$DEPLOY/host/90-cm4-boot-mode.rules" /etc/udev/rules.d/
|
|
udevadm control --reload-rules 2>/dev/null || true
|
|
|
|
log "Host: env and dirs ..."
|
|
cat > /opt/cm4-provisioning/env << 'ENV'
|
|
GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img
|
|
RPIBOOT_DIR=/opt/usbboot
|
|
EMMC_SIZE_BYTES=8589934592
|
|
ENV
|
|
[[ -n "$BACKUPS_HOST_PATH" ]] && echo "BACKUPS_DIR=$BACKUPS_HOST_PATH" >> /opt/cm4-provisioning/env
|
|
touch /etc/cm4-provisioning/enabled
|
|
mkdir -p /var/lib/cm4-provisioning/backups /var/lib/cm4-provisioning/cloudinit-images /var/lib/cm4-provisioning/portal-files /var/lib/cm4-provisioning/download-cache
|
|
[[ -n "$BACKUPS_HOST_PATH" ]] && mkdir -p "$BACKUPS_HOST_PATH"
|
|
grep -q "CLOUDINIT_IMAGES_DIR" /opt/cm4-provisioning/env || echo "CLOUDINIT_IMAGES_DIR=/var/lib/cm4-provisioning/cloudinit-images" >> /opt/cm4-provisioning/env
|
|
grep -q "CM4_DOWNLOAD_CACHE_DIR" /opt/cm4-provisioning/env || echo "CM4_DOWNLOAD_CACHE_DIR=/var/lib/cm4-provisioning/download-cache" >> /opt/cm4-provisioning/env
|
|
|
|
# --- Host: ensure xz-utils (and file) for cloud-init image build (decompress .img.xz) ---
|
|
if command -v xz >/dev/null 2>&1; then
|
|
log "Host: xz-utils already present, skipping."
|
|
else
|
|
log "Host: installing xz-utils and file (required for cloud-init image decompress)..."
|
|
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq xz-utils file 2>/dev/null || log "Warning: could not install xz-utils (no internet?). Cloud-init build will fail at decompress until you run: apt install -y xz-utils file"
|
|
fi
|
|
|
|
# --- Host: install usbboot (rpiboot) only if not already present ---
|
|
if [[ -x /opt/usbboot/rpiboot ]] || [[ -f /opt/usbboot/rpiboot ]]; then
|
|
log "Host: usbboot already installed at /opt/usbboot/rpiboot, skipping."
|
|
else
|
|
log "Host: installing usbboot (rpiboot)..."
|
|
if bash "$DEPLOY/scripts/install-usbboot-on-host.sh" 2>&1; then
|
|
log "Host: usbboot installed at /opt/usbboot/rpiboot"
|
|
else
|
|
log "Warning: usbboot install failed (e.g. no internet). USB flash/backup will not work until you run: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh"
|
|
fi
|
|
fi
|
|
|
|
# --- Host: install PiShrink only if not already present ---
|
|
if [[ -x /usr/local/bin/pishrink.sh ]] || [[ -f /usr/local/bin/pishrink.sh ]]; then
|
|
log "Host: PiShrink already installed, skipping."
|
|
grep -q "SHRINK_BACKUP" /opt/cm4-provisioning/env || echo "SHRINK_BACKUP=1" >> /opt/cm4-provisioning/env
|
|
grep -q "PISHRINK_COMPRESS" /opt/cm4-provisioning/env || echo "PISHRINK_COMPRESS=xz" >> /opt/cm4-provisioning/env
|
|
else
|
|
log "Host: installing PiShrink..."
|
|
if bash "$DEPLOY/scripts/install-pishrink-on-host.sh" 2>&1; then
|
|
log "Host: PiShrink installed"
|
|
grep -q "SHRINK_BACKUP" /opt/cm4-provisioning/env || echo "SHRINK_BACKUP=1" >> /opt/cm4-provisioning/env
|
|
grep -q "PISHRINK_COMPRESS" /opt/cm4-provisioning/env || echo "PISHRINK_COMPRESS=xz" >> /opt/cm4-provisioning/env
|
|
else
|
|
log "Warning: PiShrink install failed (e.g. no internet). Dashboard Shrink/Compress will report 'PiShrink not installed' until you run: bash /tmp/emmc-provisioning-deploy/scripts/install-pishrink-on-host.sh"
|
|
fi
|
|
fi
|
|
|
|
# --- Start LXC if stopped ---
|
|
log "Starting LXC $CTID if stopped ..."
|
|
pct start "$CTID" 2>/dev/null || true
|
|
|
|
# --- LXC: write lan-subnet.conf when LAN bridge/subnet is set (so dnsmasq/NAT/toggle use same subnet) ---
|
|
LAN_SUBNET_FOR_CONF="${DEPLOY_LXC_LAN_SUBNET:-}"
|
|
[[ -z "$LAN_SUBNET_FOR_CONF" && -n "${DEPLOY_LXC_LAN_BRIDGE:-}" ]] && LAN_SUBNET_FOR_CONF="10.20.50.1/24"
|
|
if [[ -n "$LAN_SUBNET_FOR_CONF" ]]; then
|
|
if [[ "$LAN_SUBNET_FOR_CONF" =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/([0-9]+)$ ]]; then
|
|
LAN_GW="${BASH_REMATCH[1]}"
|
|
PREFIX="${BASH_REMATCH[2]}"
|
|
BASE_3="${LAN_GW%.*}"
|
|
LAN_CIDR="${BASE_3}.0/${PREFIX}"
|
|
DHCP_RANGE_START="${BASE_3}.100"
|
|
DHCP_RANGE_END="${BASE_3}.200"
|
|
pct exec "$CTID" -- bash -c "mkdir -p /opt/cm4-provisioning && echo 'LAN_GW=$LAN_GW' > /opt/cm4-provisioning/lan-subnet.conf && echo 'LAN_CIDR=$LAN_CIDR' >> /opt/cm4-provisioning/lan-subnet.conf && echo 'DHCP_RANGE_START=$DHCP_RANGE_START' >> /opt/cm4-provisioning/lan-subnet.conf && echo 'DHCP_RANGE_END=$DHCP_RANGE_END' >> /opt/cm4-provisioning/lan-subnet.conf"
|
|
log "LXC: wrote /opt/cm4-provisioning/lan-subnet.conf (LAN_GW=$LAN_GW, LAN_CIDR=$LAN_CIDR, DHCP ${DHCP_RANGE_START}-${DHCP_RANGE_END})"
|
|
else
|
|
log "Warning: DEPLOY_LXC_LAN_SUBNET=$LAN_SUBNET_FOR_CONF not in form A.B.C.D/PREFIX; skipping lan-subnet.conf"
|
|
fi
|
|
fi
|
|
|
|
# --- LXC: flash scripts (for reference; actual flash runs on host) ---
|
|
log "LXC: installing flash scripts ..."
|
|
pct exec "$CTID" -- mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning
|
|
pct push "$CTID" "$DEPLOY/host/flash-emmc-on-connect.sh" /opt/cm4-provisioning/flash-emmc-on-connect.sh
|
|
pct exec "$CTID" -- chmod +x /opt/cm4-provisioning/flash-emmc-on-connect.sh
|
|
pct push "$CTID" "$DEPLOY/host/cm4-flash-trigger.sh" /usr/local/bin/cm4-flash-trigger.sh
|
|
pct exec "$CTID" -- chmod +x /usr/local/bin/cm4-flash-trigger.sh
|
|
pct exec "$CTID" -- bash -c 'echo -e "GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img\nRPIBOOT_DIR=/opt/usbboot\nEMMC_SIZE_BYTES=8589934592" > /opt/cm4-provisioning/env'
|
|
|
|
# --- LXC: dashboard (all files) ---
|
|
log "LXC: installing dashboard ..."
|
|
pct exec "$CTID" -- mkdir -p /opt/cm4-provisioning/dashboard/templates
|
|
pct push "$CTID" "$DEPLOY/dashboard/app.py" /opt/cm4-provisioning/dashboard/app.py
|
|
pct push "$CTID" "$DEPLOY/dashboard/templates/home.html" /opt/cm4-provisioning/dashboard/templates/home.html
|
|
pct push "$CTID" "$DEPLOY/dashboard/templates/login.html" /opt/cm4-provisioning/dashboard/templates/login.html
|
|
pct push "$CTID" "$DEPLOY/dashboard/templates/admin.html" /opt/cm4-provisioning/dashboard/templates/admin.html
|
|
pct push "$CTID" "$DEPLOY/dashboard/cm4-dashboard.service" /opt/cm4-provisioning/dashboard/cm4-dashboard.service
|
|
# Dashboard secret for sessions (create once so logins persist across restarts)
|
|
pct exec "$CTID" -- bash -c '[[ -f /opt/cm4-provisioning/dashboard.env ]] || echo "CM4_DASHBOARD_SECRET_KEY=$(openssl rand -hex 24 2>/dev/null || head -c 24 /dev/urandom | xxd -p)" > /opt/cm4-provisioning/dashboard.env'
|
|
|
|
# --- LXC: Flask and systemd (skip apt install if flask already present) ---
|
|
if pct exec "$CTID" -- dpkg -l python3-flask 2>/dev/null | grep -q '^ii'; then
|
|
log "LXC: python3-flask already installed, skipping apt install."
|
|
else
|
|
log "LXC: installing python3-flask ..."
|
|
pct exec "$CTID" -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-flask python3-werkzeug'
|
|
fi
|
|
pct exec "$CTID" -- cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/
|
|
pct exec "$CTID" -- systemctl daemon-reload
|
|
pct exec "$CTID" -- systemctl enable --now cm4-dashboard
|
|
pct exec "$CTID" -- systemctl restart cm4-dashboard
|
|
log "LXC: cm4-dashboard enabled and restarted (new code loaded)."
|
|
|
|
# --- LXC: optional SSH (root password + SSH key from deploy env) ---
|
|
if [[ -n "${DEPLOY_SSH_KEY_B64:-}" ]] || [[ -n "${DEPLOY_LXC_PWD_B64:-}" ]]; then
|
|
log "LXC: configuring SSH (root login, password, authorized_keys)..."
|
|
if pct exec "$CTID" -- dpkg -l openssh-server 2>/dev/null | grep -q '^ii'; then
|
|
log "LXC: openssh-server already installed, skipping apt install."
|
|
else
|
|
pct exec "$CTID" -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openssh-server 2>/dev/null' || true
|
|
fi
|
|
pct exec "$CTID" -- bash -c 'systemctl enable ssh 2>/dev/null; systemctl start ssh 2>/dev/null' || true
|
|
pct exec "$CTID" -- bash -c 'sed -i "s/^#*PermitRootLogin.*/PermitRootLogin yes/" /etc/ssh/sshd_config 2>/dev/null; grep -q "^PermitRootLogin" /etc/ssh/sshd_config || echo "PermitRootLogin yes" >> /etc/ssh/sshd_config; systemctl restart ssh 2>/dev/null || systemctl restart sshd 2>/dev/null' || true
|
|
if [[ -n "${DEPLOY_LXC_PWD_B64:-}" ]]; then
|
|
PWD_RAW=$(echo "$DEPLOY_LXC_PWD_B64" | base64 -d 2>/dev/null)
|
|
echo "root:$PWD_RAW" | pct exec "$CTID" -- chpasswd 2>/dev/null && log "LXC: root password set." || true
|
|
fi
|
|
if [[ -n "${DEPLOY_SSH_KEY_B64:-}" ]]; then
|
|
echo "$DEPLOY_SSH_KEY_B64" | base64 -d 2>/dev/null | pct exec "$CTID" -- bash -c "mkdir -p /root/.ssh; chmod 700 /root/.ssh; cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys" 2>/dev/null && log "LXC: SSH key added to /root/.ssh/authorized_keys." || true
|
|
fi
|
|
LXC_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
|
[[ -n "$LXC_IP" ]] && log "LXC SSH: ssh root@$LXC_IP"
|
|
fi
|
|
# Always capture LXC IP for final summary (write so local script can read it)
|
|
LXC_IP=$(pct exec "$CTID" -- hostname -I 2>/dev/null | awk '{print $1}')
|
|
echo "${LXC_IP:-}" > "$DEPLOY/lxc_ip.txt"
|
|
|
|
log "Deploy done on remote. LXC ID: $CTID"
|
|
# Heredoc terminator (must be at column 1, no leading space/tab)
|
|
REMOTE
|
|
|
|
# Read LXC IP written by remote (container hostname -I)
|
|
LXC_IP=$(ssh "$PROXMOX" "cat /tmp/emmc-provisioning-deploy/lxc_ip.txt 2>/dev/null" | tr -d '\n\r')
|
|
|
|
log "[5/5] Deploy finished."
|
|
echo ""
|
|
echo "=== Deploy complete ==="
|
|
echo "Host and LXC are fully set up: usbboot (rpiboot), PiShrink, dashboard, systemd, udev."
|
|
[[ -n "$LXC_IP" ]] && echo " LXC IP: $LXC_IP"
|
|
echo ""
|
|
echo "--- Only remaining step (manual) ---"
|
|
echo " Add a golden image for Deploy (writing image to device):"
|
|
echo " • Dashboard: open http://${LXC_IP:-<LXC-IP>}:5000 → Build cloud-init image → then Set as golden"
|
|
echo " • Or copy your image: scp your-image.img $PROXMOX:/var/lib/cm4-provisioning/golden.img"
|
|
echo " Backup (read from device) works without golden.img."
|
|
echo ""
|
|
echo "--- You have ---"
|
|
echo " - Dashboard: http://${LXC_IP:-<LXC-IP>}:5000"
|
|
[[ -n "${DEPLOY_LXC_ROOT_PASSWORD:-}" || -n "${DEPLOY_SSH_KEY_B64:-}" ]] && [[ -n "$LXC_IP" ]] && echo " - LXC SSH: ssh root@$LXC_IP (password and/or key were set)"
|
|
[[ -n "${DEPLOY_LXC_ROOT_PASSWORD:-}" || -n "${DEPLOY_SSH_KEY_B64:-}" ]] && [[ -z "$LXC_IP" ]] && echo " - LXC SSH: ssh root@<LXC-IP> (password and/or key were set)"
|
|
[[ -n "${CM4_BACKUPS_HOST_PATH:-}" ]] && echo " - Backups on host: $CM4_BACKUPS_HOST_PATH"
|
|
if [[ -n "$LOG_FILE" ]]; then
|
|
echo " - Log: $LOG_FILE"
|
|
fi
|