<message>Add new documentation files for device DNS management via DHCP and dnsmasq configuration. Update cloud-init scripts to ensure proper handling of /etc/resolv.conf and DNS settings, allowing for seamless integration with file.server. Modify existing scripts to support dynamic LAN subnet configuration and improve overall network boot functionality. These changes enhance user experience and streamline the setup process for the CM4 eMMC provisioning service.
434 lines
23 KiB
Bash
Executable File
434 lines
23 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
|
|
# DEPLOY_EMMC_SIZE_GB=32 — optional: eMMC size in GB (used only when multiple new devices appear; default 32). Detection is dynamic — single new device is used regardless of size.
|
|
#
|
|
# 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, network (WAN/LAN), and eMMC size
|
|
EMMC_GB="${DEPLOY_EMMC_SIZE_GB:-32}"
|
|
EMMC_SIZE_BYTES=$(( EMMC_GB * 1024 * 1024 * 1024 ))
|
|
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:-}' EMMC_SIZE_BYTES='$EMMC_SIZE_BYTES' EMMC_GB='$EMMC_GB'" 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)."
|
|
pct set "$CTID" -nameserver 8.8.8.8
|
|
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
|
|
pct set "$CTID" -nameserver 8.8.8.8
|
|
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 (DNS 8.8.8.8)."
|
|
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 (EMMC ${EMMC_GB:-32}GB = $EMMC_SIZE_BYTES bytes) ..."
|
|
cat > /opt/cm4-provisioning/env << ENV
|
|
GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img
|
|
RPIBOOT_DIR=/opt/usbboot
|
|
EMMC_SIZE_BYTES=${EMMC_SIZE_BYTES:-34359738368}
|
|
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"
|
|
echo "$LAN_GW" > "$DEPLOY/lxc_lan_ip.txt"
|
|
log "LXC: wrote /opt/cm4-provisioning/lan-subnet.conf (LAN_GW=$LAN_GW); dashboard will be reachable on LAN at http://${LAN_GW}:5000"
|
|
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=$EMMC_SIZE_BYTES' > /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/templates/portal_files.html" /opt/cm4-provisioning/dashboard/templates/portal_files.html
|
|
pct push "$CTID" "$DEPLOY/dashboard/templates/cloudinit_build.html" /opt/cm4-provisioning/dashboard/templates/cloudinit_build.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 and optional LAN IP written by remote
|
|
LXC_IP=$(ssh "$PROXMOX" "cat /tmp/emmc-provisioning-deploy/lxc_ip.txt 2>/dev/null" | tr -d '\n\r')
|
|
LXC_LAN_IP=$(ssh "$PROXMOX" "cat /tmp/emmc-provisioning-deploy/lxc_lan_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 (WAN): $LXC_IP"
|
|
[[ -n "$LXC_LAN_IP" ]] && echo " LXC IP (LAN): $LXC_LAN_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 (WAN): http://${LXC_IP:-<LXC-IP>}:5000"
|
|
[[ -n "$LXC_LAN_IP" ]] && echo " - Dashboard (LAN): http://${LXC_LAN_IP}:5000 (use from devices on provisioning LAN)"
|
|
[[ -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
|