Update provisioning documentation and scripts for improved Proxmox deployment</message>

<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.
This commit is contained in:
nearxos
2026-03-03 08:24:18 +02:00
parent fe72619931
commit c5e418eabc
15 changed files with 500 additions and 33 deletions

View File

@@ -23,6 +23,12 @@
# 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.
@@ -144,8 +150,8 @@ rsync -a "$REPO_DIR/" "$PROXMOX:/tmp/emmc-provisioning-deploy/" --exclude='.git'
log "[4/5] Running remote install (host + LXC) ..."
# Pass optional LXC SSH vars (base64) and selected storage
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:-}'" bash -s << 'REMOTE'
# 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}"
@@ -185,14 +191,24 @@ else
fi
[[ -z "$DEBIAN12_TMPL" ]] && { log "Error: no Debian 12 template found"; exit 1; }
TMPL_NAME=$(basename "$DEBIAN12_TMPL")
# Optional: add eth1 for network-boot LAN (DHCP+TFTP). Set DEPLOY_LXC_NET1 e.g. "name=eth1,bridge=vmbr1,ip=10.20.50.1/24"
# 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_NET1:-}" ]]; then
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=vmbr0,ip=dhcp $NET1_OPT \
--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
@@ -302,6 +318,24 @@ fi
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

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
# Mount a Raspberry Pi OS .img or .img.xz, edit cloud-init NoCloud files on the boot
# partition, then unmount and (if .img.xz) recompress. Requires sudo for loop/mount.
#
# Usage:
# ./edit-cloudinit-on-image.sh <path-to-image.img.xz>
# ./edit-cloudinit-on-image.sh <path-to-image.img>
#
# Options:
# --no-recompress If image was .img.xz, leave decompressed .img and do not overwrite
# --replace-with-repo Copy user-data, meta-data, network-config from repo before editing
#
# Example:
# ./edit-cloudinit-on-image.sh /path/to/gnss-bootstrap-20260223-215010.img.xz
#
# Backup the image before running if you want to keep the original.
set -e
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
CLOUDINIT_SRC="$REPO_ROOT/emmc-provisioning/cloud-init"
NO_RECOMPRESS=""
REPLACE_WITH_REPO=""
while [[ $# -gt 0 ]]; do
case "$1" in
--no-recompress) NO_RECOMPRESS=1; shift ;;
--replace-with-repo) REPLACE_WITH_REPO=1; shift ;;
-*) echo "Unknown option: $1"; exit 1 ;;
*) break ;;
esac
done
IMAGE_IN="$1"
[[ -n "$IMAGE_IN" && -f "$IMAGE_IN" ]] || {
echo "Usage: $0 [--no-recompress] [--replace-with-repo] <path-to-image.img.xz|.img>"
echo "Example: $0 gnss-bootstrap-20260223-215010.img.xz"
exit 1
}
IMAGE_IN="$(realpath "$IMAGE_IN")"
WORK_DIR=""
IMG_FILE=""
ORIGINAL_XZ=""
cleanup() {
if [[ -n "$MNT" && -d "$MNT" ]]; then
sudo umount "$MNT" 2>/dev/null || true
fi
if [[ -n "$LOOP" && -b "$LOOP" ]]; then
sudo losetup -d "$LOOP" 2>/dev/null || true
fi
if [[ -n "$WORK_DIR" && -d "$WORK_DIR" ]]; then
if [[ -n "$NO_RECOMPRESS" && -n "$ORIGINAL_XZ" ]]; then
echo "Work dir left at: $WORK_DIR"
else
rm -rf "$WORK_DIR"
fi
fi
}
trap cleanup EXIT
if [[ "$IMAGE_IN" == *.img.xz ]]; then
ORIGINAL_XZ="$IMAGE_IN"
echo "Decompressing $(basename "$ORIGINAL_XZ") (this may take a minute and needs ~3GB free)…"
WORK_DIR=$(mktemp -d -p "${TMPDIR:-/tmp}" edit-cloudinit.XXXXXX)
IMG_FILE="$WORK_DIR/image.img"
xz -T 0 -d -k -f -c "$ORIGINAL_XZ" > "$IMG_FILE"
else
WORK_DIR=$(mktemp -d -p "${TMPDIR:-/tmp}" edit-cloudinit.XXXXXX)
IMG_FILE="$IMAGE_IN"
fi
echo "Attaching image and mounting boot partition…"
LOOP=$(sudo losetup -f --show -P "$IMG_FILE")
# Force kernel to scan partition table so /dev/loopNp1, p2 etc. appear
sudo partprobe "$LOOP" 2>/dev/null || true
sleep 0.5
boot_part="${LOOP}p1"
[[ -b "$boot_part" ]] || boot_part="${LOOP}p2"
[[ -b "$boot_part" ]] || {
echo "Boot partition not found on image. Partitions on image:"
ls -la "${LOOP}"p* 2>/dev/null || true
sudo fdisk -l "$IMG_FILE" 2>/dev/null || true
exit 1
}
MNT="$WORK_DIR/mnt"
mkdir -p "$MNT"
sudo mount "$boot_part" "$MNT"
if [[ -n "$REPLACE_WITH_REPO" && -d "$CLOUDINIT_SRC" ]]; then
echo "Copying cloud-init files from repo into boot partition…"
for f in user-data meta-data network-config; do
if [[ -f "$CLOUDINIT_SRC/$f" ]]; then
sudo cp "$CLOUDINIT_SRC/$f" "$MNT/$f"
elif [[ -f "$CLOUDINIT_SRC/$f.bootstrap" ]]; then
sudo cp "$CLOUDINIT_SRC/$f.bootstrap" "$MNT/$f"
fi
done
fi
echo ""
echo "Boot partition is mounted at: $MNT"
echo "Cloud-init files to edit:"
echo " $MNT/user-data"
echo " $MNT/meta-data"
echo " $MNT/network-config"
echo ""
EDITOR="${EDITOR:-nano}"
echo "Opening editor ($EDITOR). Save and exit when done."
read -r -p "Press Enter to open $EDITOR on these files…"
sudo "$EDITOR" "$MNT/user-data" "$MNT/meta-data" "$MNT/network-config"
echo "Unmounting…"
sudo umount "$MNT"
MNT=""
sudo losetup -d "$LOOP"
LOOP=""
if [[ -n "$ORIGINAL_XZ" && -z "$NO_RECOMPRESS" ]]; then
echo "Recompressing to $(basename "$ORIGINAL_XZ")"
xz -T 0 -z -f -k "$IMG_FILE"
mv -f "${IMG_FILE}.xz" "$ORIGINAL_XZ"
echo "Done. Updated: $ORIGINAL_XZ"
rm -rf "$WORK_DIR"
WORK_DIR=""
elif [[ -n "$NO_RECOMPRESS" && -n "$ORIGINAL_XZ" ]]; then
echo "Left decompressed image at: $IMG_FILE"
echo "Recompress manually: xz -z -k \"$IMG_FILE\""
fi

View File

@@ -25,7 +25,17 @@ if [[ -n "$TARGET" ]]; then
fi
# --- Running inside the LXC from here ---
echo "Configuring network boot (DHCP + TFTP on eth1, NAT via eth0) ..."
# LAN subnet: use /opt/cm4-provisioning/lan-subnet.conf (written by deploy-to-proxmox.sh when DEPLOY_LXC_LAN_SUBNET is set)
LAN_CONF="/opt/cm4-provisioning/lan-subnet.conf"
if [[ -f "$LAN_CONF" ]]; then
source "$LAN_CONF"
else
LAN_GW="10.20.50.1"
LAN_CIDR="10.20.50.0/24"
DHCP_RANGE_START="10.20.50.100"
DHCP_RANGE_END="10.20.50.200"
fi
echo "Configuring network boot (DHCP + TFTP on eth1, NAT via eth0) — LAN $LAN_CIDR (gateway $LAN_GW), DHCP ${DHCP_RANGE_START}-${DHCP_RANGE_END} ..."
# 1) Install dnsmasq
if ! command -v dnsmasq >/dev/null 2>&1; then
@@ -34,12 +44,12 @@ fi
# 2) dnsmasq config for eth1 only (DHCP + TFTP); PXE options in network-boot-pxe.conf (toggle with toggle-network-boot-dhcp.sh)
mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/network-boot.conf << 'DNSMASQ'
cat > /etc/dnsmasq.d/network-boot.conf << DNSMASQ
# DHCP on eth1 only (provisioning LAN)
# TFTP and PXE options are in network-boot-pxe.conf, controlled by toggle-network-boot-dhcp.sh
interface=eth1
bind-interfaces
dhcp-range=10.20.50.100,10.20.50.200,12h
dhcp-range=${DHCP_RANGE_START},${DHCP_RANGE_END},12h
log-dhcp
log-queries
port=0
@@ -89,14 +99,14 @@ fi
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-cm4-network-boot.conf
sysctl -p /etc/sysctl.d/99-cm4-network-boot.conf 2>/dev/null || sysctl -w net.ipv4.ip_forward=1
# 5) NAT: 10.20.50.0/24 -> eth0 (masquerade)
# 5) NAT: LAN subnet -> eth0 (masquerade)
if command -v nft >/dev/null 2>&1; then
mkdir -p /etc/nftables.d
cat > /etc/nftables.d/nat-lan.conf << 'NFT'
cat > /etc/nftables.d/nat-lan.conf << NFT
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.20.50.0/24 oifname "eth0" masquerade
ip saddr ${LAN_CIDR} oifname "eth0" masquerade
}
}
NFT
@@ -110,8 +120,8 @@ NFT
echo "NAT rule added (nftables) and saved to /etc/nftables.d/nat-lan.conf"
else
# Fallback iptables
iptables -t nat -C POSTROUTING -s 10.20.50.0/24 -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s 10.20.50.0/24 -o eth0 -j MASQUERADE
iptables -t nat -C POSTROUTING -s "${LAN_CIDR}" -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s "${LAN_CIDR}" -o eth0 -j MASQUERADE
echo "NAT rule added (iptables)."
fi
@@ -120,6 +130,6 @@ systemctl enable dnsmasq
systemctl restart dnsmasq
echo "Network boot setup done."
echo " - DHCP + TFTP on eth1 (10.20.50.1), range 10.20.50.100-200"
echo " - NAT: 10.20.50.0/24 -> eth0 (internet)"
echo " - DHCP + TFTP on eth1 ($LAN_GW), range ${DHCP_RANGE_START}-${DHCP_RANGE_END}"
echo " - NAT: ${LAN_CIDR} -> eth0 (internet)"
echo " - TFTP root: /srv/tftpboot (RPi boot files; initrd.img if provided)"