Remove obsolete audio and buzzer control documentation files, including detailed guides and HTML interfaces, to streamline the repository and eliminate redundancy. This cleanup enhances maintainability and focuses on essential resources for the reTerminal DM4 audio and buzzer functionalities.

This commit is contained in:
nearxos
2026-02-20 15:39:39 +02:00
parent 9656771d5a
commit 58d9144752
101 changed files with 80 additions and 193 deletions

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Build usbboot (rpiboot) on THIS machine (e.g. Fedora) and deploy to the Proxmox host.
# Use this when the host has no internet. Requires: dnf (Fedora) or apt (Debian/Ubuntu), git, ssh to host.
# Usage: ./build-and-deploy-usbboot-to-host.sh [proxmox_host]
# Example: ./build-and-deploy-usbboot-to-host.sh root@10.130.60.224
set -e
PROXMOX="${1:-root@10.130.60.224}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="/tmp/usbboot-build-$$"
cleanup() { rm -rf "$BUILD_DIR"; }
trap cleanup EXIT
echo "[$(date -Iseconds)] Building usbboot for host $PROXMOX ..."
# Install build deps (Fedora or Debian/Ubuntu)
if command -v dnf &>/dev/null; then
echo "Installing build deps (dnf)..."
sudo dnf install -y git libusb1-devel pkg-config glibc-devel gcc gcc-c++ make
elif command -v apt-get &>/dev/null; then
echo "Installing build deps (apt)..."
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y git libusb-1.0-0-dev pkg-config build-essential
else
echo "Error: need dnf or apt-get to install dependencies."
exit 1
fi
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
echo "Cloning usbboot (with submodules)..."
git clone --recurse-submodules --shallow-submodules --depth=1 https://github.com/raspberrypi/usbboot
cd usbboot
echo "Building..."
make
# Deploy: binary + gadget dir(s) to host /opt/usbboot
echo "[$(date -Iseconds)] Deploying to $PROXMOX:/opt/usbboot ..."
ssh "$PROXMOX" "mkdir -p /opt/usbboot"
rsync -a "$BUILD_DIR/usbboot/rpiboot" "$PROXMOX:/opt/usbboot/"
# CM4 needs mass-storage-gadget (64-bit); copy whichever exists
for dir in mass-storage-gadget64 mass-storage-gadget; do
if [[ -d "$BUILD_DIR/usbboot/$dir" ]]; then
rsync -a "$BUILD_DIR/usbboot/$dir/" "$PROXMOX:/opt/usbboot/$dir/"
echo " Copied $dir/"
fi
done
ssh "$PROXMOX" "chmod +x /opt/usbboot/rpiboot"
# Verify gadget has boot files (rpiboot needs bootfiles.bin or bootcode*.bin)
if ! ssh "$PROXMOX" "test -f /opt/usbboot/mass-storage-gadget64/bootfiles.bin || test -f /opt/usbboot/mass-storage-gadget64/boot.img" 2>/dev/null; then
echo "Warning: mass-storage-gadget64 may be missing boot files. If rpiboot fails with 'No bootcode files found', run: ./populate-gadget-on-host.sh $PROXMOX"
fi
echo "[$(date -Iseconds)] usbboot deployed to $PROXMOX:/opt/usbboot"
echo "Ensure the host flash script runs rpiboot with: -d /opt/usbboot/mass-storage-gadget64 (or mass-storage-gadget)."

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Run on the Proxmox HOST (where USB is connected) to verify the reTerminal in boot mode is seen.
# Usage: ssh root@10.130.60.224 'bash -s' < scripts/check-usb-on-host.sh
# Or copy to host and run: ./check-usb-on-host.sh
echo "=== CM4 boot-mode USB (2b8e = RPi, 0a5c:2711 = Broadcom BCM2711) ==="
lsusb | grep -E "2b8e|0a5c" || echo "None found. Connect reTerminal in boot mode (eMMC disable jumper) and use the USB slave port."
echo ""
echo "=== All USB devices ==="
lsusb
echo ""
echo "=== Provisioning status ==="
cat /var/lib/cm4-provisioning/status.json 2>/dev/null || echo "No status.json (script has not run yet)."
echo ""
echo "=== Last flash log ==="
tail -15 /var/lib/cm4-provisioning/flash.log 2>/dev/null || echo "No flash.log"
echo ""
echo "=== udev rule and rpiboot ==="
test -f /etc/udev/rules.d/90-cm4-boot-mode.rules && echo "90-cm4-boot-mode.rules: present" || echo "90-cm4-boot-mode.rules: MISSING"
test -x /opt/usbboot/rpiboot && echo "rpiboot: present" || echo "rpiboot: MISSING"
test -f /etc/cm4-provisioning/enabled && echo "enabled: yes" || echo "enabled: no (provisioning disabled)"

View File

@@ -0,0 +1,24 @@
[2026-02-18T09:57:49+02:00] Logging to /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-095749.log
[2026-02-18T09:57:49+02:00] Deploying to root@10.130.60.224 ...
[2026-02-18T09:57:49+02:00] [1/4] Cleaning remote staging dir ...
[2026-02-18T09:57:50+02:00] [2/4] Rsync repo to root@10.130.60.224 ...
[2026-02-18T09:57:50+02:00] [3/4] Running remote install (host + LXC) ...
[2026-02-18T08:01:21+00:00] LXC 201 already exists.
[2026-02-18T08:01:21+00:00] Host: installing scripts and udev ...
[2026-02-18T08:01:22+00:00] Host: env and dirs ...
[2026-02-18T08:01:22+00:00] Starting LXC 201 if stopped ...
[2026-02-18T08:01:25+00:00] LXC: installing flash scripts ...
failed to create file: /opt/cm4-provisioning/: Is a directory
[2026-02-18T08:01:45+00:00] LXC: installing dashboard ...
failed to create file: /opt/cm4-provisioning/dashboard/: Is a directory
failed to create file: /opt/cm4-provisioning/dashboard/templates/: Is a directory
[2026-02-18T08:01:59+00:00] Deploy done (remote).
Next: Install usbboot on host when online: ssh <host> 'bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh'
Next: Enable dashboard in LXC 201: pct exec 201 -- bash -c 'apt-get install -y python3-flask; cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/; systemctl daemon-reload; systemctl enable --now cm4-dashboard'
failed to create file: /opt/cm4-provisioning/dashboard/: Is a directory
[2026-02-18T09:58:31+02:00] [4/4] Deploy finished.
Done. Put golden.img in /var/lib/cm4-provisioning/ on the host (or scp to LXC 201 at /var/lib/cm4-provisioning/).
When the host has internet, run on the host: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh
Dashboard: install flask in LXC 201 and enable cm4-dashboard.service (see docs/PROXMOX-LXC-DEPLOYMENT.md).
Log written to: /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-095749.log

View File

@@ -0,0 +1,20 @@
[2026-02-18T09:58:59+02:00] Logging to /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-095859.log
[2026-02-18T09:58:59+02:00] Deploying to root@10.130.60.224 ...
[2026-02-18T09:58:59+02:00] [1/4] Cleaning remote staging dir ...
[2026-02-18T09:59:00+02:00] [2/4] Rsync repo to root@10.130.60.224 ...
[2026-02-18T09:59:00+02:00] [3/4] Running remote install (host + LXC) ...
[2026-02-18T08:02:32+00:00] LXC 201 already exists.
[2026-02-18T08:02:32+00:00] Host: installing scripts and udev ...
[2026-02-18T08:02:32+00:00] Host: env and dirs ...
[2026-02-18T08:02:32+00:00] Starting LXC 201 if stopped ...
[2026-02-18T08:02:35+00:00] LXC: installing flash scripts ...
[2026-02-18T08:02:55+00:00] LXC: installing dashboard ...
[2026-02-18T08:03:09+00:00] Deploy done (remote).
Next: Install usbboot on host when online: ssh <host> 'bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh'
Next: Enable dashboard in LXC 201: pct exec 201 -- bash -c 'apt-get install -y python3-flask; cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/; systemctl daemon-reload; systemctl enable --now cm4-dashboard'
[2026-02-18T09:59:41+02:00] [4/4] Deploy finished.
Done. Put golden.img in /var/lib/cm4-provisioning/ on the host (or scp to LXC 201 at /var/lib/cm4-provisioning/).
When the host has internet, run on the host: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh
Dashboard: install flask in LXC 201 and enable cm4-dashboard.service (see docs/PROXMOX-LXC-DEPLOYMENT.md).
Log written to: /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-095859.log

View File

@@ -0,0 +1,20 @@
[2026-02-18T10:11:19+02:00] Logging to /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-101119.log
[2026-02-18T10:11:19+02:00] Deploying to root@10.130.60.224 ...
[2026-02-18T10:11:19+02:00] [1/4] Cleaning remote staging dir ...
[2026-02-18T10:11:20+02:00] [2/4] Rsync repo to root@10.130.60.224 ...
[2026-02-18T10:11:21+02:00] [3/4] Running remote install (host + LXC) ...
[2026-02-18T08:14:52+00:00] LXC 201 already exists.
[2026-02-18T08:14:52+00:00] Host: installing scripts and udev ...
[2026-02-18T08:14:52+00:00] Host: env and dirs ...
[2026-02-18T08:14:52+00:00] Starting LXC 201 if stopped ...
[2026-02-18T08:14:56+00:00] LXC: installing flash scripts ...
[2026-02-18T08:15:16+00:00] LXC: installing dashboard ...
[2026-02-18T08:15:30+00:00] Deploy done (remote).
Next: Install usbboot on host when online: ssh <host> 'bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh'
Next: Enable dashboard in LXC 201: pct exec 201 -- bash -c 'apt-get install -y python3-flask; cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/; systemctl daemon-reload; systemctl enable --now cm4-dashboard'
[2026-02-18T10:12:02+02:00] [4/4] Deploy finished.
Done. Put golden.img in /var/lib/cm4-provisioning/ on the host (or scp to LXC 201 at /var/lib/cm4-provisioning/).
When the host has internet, run on the host: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh
Dashboard: install flask in LXC 201 and enable cm4-dashboard.service (see docs/PROXMOX-LXC-DEPLOYMENT.md).
Log written to: /home/nearxos/Projects/reTerminal DM4/chromium-setup/emmc-provisioning/scripts/deploy-20260218-101119.log

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Deploy only the dashboard to the LXC by IP (no Proxmox host needed).
# Uses rsync so all files (app, templates, service file, etc.) stay in sync.
# Usage: ./deploy-dashboard-to-lxc.sh [user@lxc_ip]
# Example: ./deploy-dashboard-to-lxc.sh root@10.130.60.119
set -e
LXC="${1:-root@10.130.60.119}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
DASHBOARD_DIR="$REPO_DIR/dashboard"
REMOTE_DIR="/opt/cm4-provisioning/dashboard"
if [[ ! -d "$DASHBOARD_DIR" ]]; then
echo "Error: dashboard dir not found: $DASHBOARD_DIR"
exit 1
fi
echo "Deploying dashboard to $LXC ($REMOTE_DIR) ..."
# Ensure remote has rsync (Debian/Ubuntu LXC)
ssh "$LXC" "command -v rsync >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y rsync)"
# Sync entire dashboard: app, templates, service file, README, requirements, etc.
rsync -avz --delete \
--exclude='.git' \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.env' \
"$DASHBOARD_DIR/" \
"$LXC:$REMOTE_DIR/"
echo "Installing systemd unit and restarting ..."
ssh "$LXC" "cp $REMOTE_DIR/cm4-dashboard.service /etc/systemd/system/ && systemctl daemon-reload && systemctl restart cm4-dashboard && systemctl is-active --quiet cm4-dashboard && echo 'Dashboard restarted and running.'"
echo "Done. Dashboard at http://$(echo "$LXC" | cut -d@ -f2):5000"

View File

@@ -0,0 +1,387 @@
#!/usr/bin/env bash
# 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
#
# 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) 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'
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")
# 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"
NET1_OPT=""
if [[ -n "${DEPLOY_LXC_NET1:-}" ]]; then
NET1_OPT="--net1 $DEPLOY_LXC_NET1"
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 \
--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: 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

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Run on the Proxmox host (as root) when rpiboot fails with "No 'bootcode' files found" / "rpiboot gadget empty".
# Cause: mass-storage-gadget64 has no real boot files (broken symlinks or Git LFS not pulled).
# This script removes broken symlinks and extracts bootcode4.bin from the installed rpiboot binary.
#
# On host: bash fix-gadget-bootcode-on-host.sh
# From your machine: ssh root@HOST 'bash -s' < emmc-provisioning/scripts/fix-gadget-bootcode-on-host.sh
set -e
GADGET="${1:-/opt/usbboot/mass-storage-gadget64}"
RPIBOOT="${2:-/opt/usbboot/rpiboot}"
[[ -d "$GADGET" ]] || { echo "Error: $GADGET not found"; exit 1; }
[[ -x "$RPIBOOT" ]] || { echo "Error: $RPIBOOT not found (install usbboot first: install-usbboot-on-host.sh)"; exit 1; }
# Ensure readelf and objdump are available (binutils)
for cmd in readelf objdump; do
if ! command -v "$cmd" &>/dev/null; then
echo "Installing binutils (required for $cmd)..."
apt-get update -qq && apt-get install -y -qq binutils 2>/dev/null || {
echo "Error: $cmd not found. Install binutils: apt-get install -y binutils"
exit 1
}
break
fi
done
# Remove broken symlinks in gadget dir so we can replace with real boot file
for f in bootfiles.bin bootcode.bin bootcode4.bin bootcode5.bin; do
if [[ -L "$GADGET/$f" ]] && ! [[ -f "$GADGET/$f" ]]; then
rm -f "$GADGET/$f"
echo "Removed broken symlink $GADGET/$f"
fi
done
# If we already have a valid boot file, done
if [[ -f "$GADGET/bootcode4.bin" ]] || [[ -f "$GADGET/bootfiles.bin" ]]; then
echo "Already has bootcode4.bin or valid bootfiles.bin. OK."
exit 0
fi
# Get .data section file offset and address from ELF
readelf -S "$RPIBOOT" | grep -q "\.data" || { echo "No .data section"; exit 1; }
DATA_OFF=$(readelf -S "$RPIBOOT" | awk '/\.data\s/ { print "0x"$5; exit }')
DATA_ADDR=$(readelf -S "$RPIBOOT" | awk '/\.data\s/ { print "0x"$4; exit }')
LEN_SYM=$(objdump -t "$RPIBOOT" | awk '/msd_bootcode4_bin_len/ { print $1; exit }')
BIN_SYM=$(objdump -t "$RPIBOOT" | awk '/msd_bootcode4_bin\s/ { print $1; exit }')
[[ -n "$LEN_SYM" && -n "$BIN_SYM" ]] || { echo "msd_bootcode4_bin symbols not found in rpiboot"; exit 1; }
LEN_ADDR=$((0x$LEN_SYM))
BIN_ADDR=$((0x$BIN_SYM))
LEN_OFF=$((DATA_OFF + LEN_ADDR - DATA_ADDR))
BIN_OFF=$((DATA_OFF + BIN_ADDR - DATA_ADDR))
LEN=$(dd if="$RPIBOOT" bs=1 skip=$LEN_OFF count=4 2>/dev/null | od -An -t u4 | tr -d ' ')
dd if="$RPIBOOT" of="$GADGET/bootcode4.bin" bs=1 skip=$BIN_OFF count=$LEN 2>/dev/null
echo "Wrote $GADGET/bootcode4.bin ($LEN bytes). Verify: $RPIBOOT -d $GADGET -V"

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Run on the provisioning HOST (root) to install PiShrink and dependencies.
# Enables shrinking backups in flash-emmc-on-connect.sh when SHRINK_BACKUP=1.
# PiShrink: https://github.com/Drewsif/PiShrink
#
# Offline install (when host has no internet):
# 1. On a machine with internet: curl -Lo pishrink.sh https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
# 2. scp pishrink.sh root@HOST:/tmp/
# 3. On host: bash install-pishrink-on-host.sh /tmp/pishrink.sh
#
# Optional: PISHRINK_URL=... or pass path to local pishrink.sh as first argument.
set -e
PISHRINK_URL="${PISHRINK_URL:-https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh}"
LOCAL_PISHRINK="$1"
echo "Installing PiShrink dependencies (parted, e2fsprogs, xz-utils, ...)..."
if apt-get update 2>/dev/null && apt-get install -y wget parted gzip pigz xz-utils udev e2fsprogs 2>/dev/null; then
echo "Dependencies installed."
else
echo "Warning: apt install failed (e.g. host has no internet). Skipping packages."
echo "PiShrink needs: parted, e2fsprogs; optional: gzip/pigz, xz-utils. Install them manually if missing."
for cmd in parted resize2fs; do
if ! command -v "$cmd" &>/dev/null; then
echo " Missing: $cmd — install with apt when the host has network, or use a local mirror."
fi
done
fi
if [[ -n "$LOCAL_PISHRINK" && -f "$LOCAL_PISHRINK" ]]; then
echo "Using local PiShrink script: $LOCAL_PISHRINK"
cp "$LOCAL_PISHRINK" /usr/local/bin/pishrink.sh
chmod +x /usr/local/bin/pishrink.sh
echo "PiShrink installed at /usr/local/bin/pishrink.sh"
elif command -v wget &>/dev/null && wget -q -O /usr/local/bin/pishrink.sh "$PISHRINK_URL" 2>/dev/null; then
chmod +x /usr/local/bin/pishrink.sh
echo "PiShrink installed at /usr/local/bin/pishrink.sh"
else
echo "Could not download PiShrink (no wget or no network)."
echo ""
echo "Offline install:"
echo " 1. On a machine WITH internet run:"
echo " curl -Lo pishrink.sh $PISHRINK_URL"
echo " 2. Copy to host:"
echo " scp pishrink.sh root@YOUR_HOST:/tmp/"
echo " 3. On the host run:"
echo " bash install-pishrink-on-host.sh /tmp/pishrink.sh"
exit 1
fi
echo ""
echo "To shrink backups automatically, add to /opt/cm4-provisioning/env:"
echo " SHRINK_BACKUP=1"
echo " # optional: PISHRINK_COMPRESS=gz or PISHRINK_COMPRESS=xz"

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Run on the Proxmox HOST (root) when the host has internet.
# Builds usbboot (rpiboot) and installs to /opt/usbboot so the auto-flash can run.
# If the host has no internet, run build-and-deploy-usbboot-to-host.sh from your Fedora machine instead.
set -e
apt-get update
apt-get install -y libusb-1.0-0-dev git pkg-config build-essential
cd /tmp
rm -rf usbboot
git clone --recurse-submodules --shallow-submodules --depth=1 https://github.com/raspberrypi/usbboot
cd usbboot
make
mkdir -p /opt/usbboot
cp rpiboot /opt/usbboot/
# Copy gadget dir(s) so rpiboot -d works
for dir in mass-storage-gadget64 mass-storage-gadget; do
[[ -d "$dir" ]] && cp -a "$dir" /opt/usbboot/
done
echo "usbboot installed at /opt/usbboot/rpiboot"
# If rpiboot later fails with "No bootcode files" (broken symlinks in gadget), fix-gadget repairs it.
# Run it now; prefer copy in /opt (from deploy) or in deploy dir.
for fix in /opt/cm4-provisioning/fix-gadget-bootcode-on-host.sh /tmp/emmc-provisioning-deploy/scripts/fix-gadget-bootcode-on-host.sh; do
if [[ -f "$fix" ]]; then
echo "Running fix-gadget-bootcode-on-host.sh to ensure gadget has valid boot files..."
bash "$fix" || true
break
fi
done

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Run on the Proxmox HOST to see provisioning status, USB, flash job, and logs.
# Usage: ./monitor-from-host.sh or: ssh root@10.130.60.224 'bash -s' < scripts/monitor-from-host.sh
PROV=/var/lib/cm4-provisioning
echo "═══════════════════════════════════════════════════════════════"
echo " CM4 provisioning host monitor"
echo "═══════════════════════════════════════════════════════════════"
echo ""
echo "--- USB (CM4 boot mode: 2b8e or 0a5c:2711) ---"
lsusb | grep -E "2b8e|0a5c" || echo " No CM4 boot device seen."
echo ""
echo "--- Current status (dashboard reads this) ---"
if [[ -f "$PROV/status.json" ]]; then
cat "$PROV/status.json" | python3 -m json.tool 2>/dev/null || cat "$PROV/status.json"
else
echo " No status.json (no run yet)."
fi
echo ""
echo "--- Flash job (systemd unit) ---"
systemctl show cm4-flash-once --property=ActiveState,SubState,ExecMainPID 2>/dev/null || echo " Unit not run yet."
echo ""
echo "--- Last 20 lines of flash.log ---"
tail -20 "$PROV/flash.log" 2>/dev/null || echo " No flash.log"
echo ""
echo "--- Provisioning dir ---"
ls -la "$PROV/" 2>/dev/null || echo " Dir missing."
echo ""
echo "--- Block devices (eMMC appears here after rpiboot) ---"
lsblk -d -o NAME,SIZE,MODEL,TRAN 2>/dev/null | head -15
echo ""
echo "--- Config ---"
echo " enabled: $([ -f /etc/cm4-provisioning/enabled ] && echo yes || echo no)"
echo " rpiboot: $([ -x /opt/usbboot/rpiboot ] && echo /opt/usbboot/rpiboot || echo MISSING)"
echo " golden: $([ -f $PROV/golden.img ] && echo present || echo not set)"
echo " backups: $(ls "$PROV/backups" 2>/dev/null | wc -l) file(s)"

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Populate /opt/usbboot/mass-storage-gadget64 on the Proxmox host (no rpiboot build).
# Use when rpiboot works but you see "No 'bootcode' files found" — the gadget dir is empty.
# Usage: ./populate-gadget-on-host.sh [proxmox_host]
# Example: ./populate-gadget-on-host.sh root@10.130.60.224
set -e
PROXMOX="${1:-root@10.130.60.224}"
BUILD_DIR="/tmp/usbboot-gadget-$$"
cleanup() { rm -rf "$BUILD_DIR"; }
trap cleanup EXIT
echo "[$(date -Iseconds)] Cloning usbboot to fetch mass-storage-gadget64 ..."
mkdir -p "$BUILD_DIR"
git clone --depth=1 https://github.com/raspberrypi/usbboot "$BUILD_DIR/usbboot"
GADGET="$BUILD_DIR/usbboot/mass-storage-gadget64"
if [[ ! -d "$GADGET" ]]; then
echo "Error: mass-storage-gadget64 not found in clone."
exit 1
fi
# rpiboot needs at least bootfiles.bin or bootcode*.bin
if [[ ! -f "$GADGET/bootfiles.bin" && ! -f "$GADGET/boot.img" ]]; then
echo "Warning: clone has no bootfiles.bin or boot.img. Trying git lfs pull..."
(cd "$BUILD_DIR/usbboot" && git lfs pull 2>/dev/null) || true
fi
if [[ ! -f "$GADGET/bootfiles.bin" && ! -f "$GADGET/boot.img" ]]; then
echo "Error: mass-storage-gadget64 still has no boot files. Install git-lfs and retry, or run build-and-deploy-usbboot-to-host.sh for a full deploy."
exit 1
fi
echo "[$(date -Iseconds)] Syncing mass-storage-gadget64 to $PROXMOX:/opt/usbboot/ ..."
ssh "$PROXMOX" "mkdir -p /opt/usbboot"
rsync -a "$GADGET/" "$PROXMOX:/opt/usbboot/mass-storage-gadget64/"
echo "Done. Verify with: ssh $PROXMOX 'ls -la /opt/usbboot/mass-storage-gadget64/'"

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Populate /srv/tftpboot with Raspberry Pi 4 / CM4 boot files from the official firmware repo.
# Run inside the LXC (as root), or from your machine: ./populate-tftpboot-from-git.sh root@10.130.60.141
# Requires: curl or wget, tar; the LXC must have internet (eth0).
set -e
TARGET="${1:-}"
FIRMWARE_URL="https://github.com/raspberrypi/firmware/archive/refs/heads/master.tar.gz"
TFTP_ROOT="${TFTP_ROOT:-/srv/tftpboot}"
do_populate() {
echo "Populating $TFTP_ROOT from Raspberry Pi firmware (GitHub) ..."
mkdir -p "$TFTP_ROOT"
if [[ -f "$TFTP_ROOT/start4cd.elf" ]]; then
echo "start4cd.elf already present; skipping download (remove it to re-fetch)."
return 0
fi
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
echo "Installing curl ..."
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl
fi
local tmpdir
tmpdir=$(mktemp -d)
trap "rm -rf $tmpdir" EXIT
if command -v curl >/dev/null 2>&1; then
curl -sL "$FIRMWARE_URL" -o "$tmpdir/firmware.tar.gz"
else
wget -q -O "$tmpdir/firmware.tar.gz" "$FIRMWARE_URL"
fi
tar xzf "$tmpdir/firmware.tar.gz" -C "$tmpdir"
if [[ ! -d "$tmpdir/firmware-master/boot" ]]; then
echo "Error: boot folder not found in archive"
exit 1
fi
cp -a "$tmpdir/firmware-master/boot/." "$TFTP_ROOT/"
echo "Copied boot files to $TFTP_ROOT ($(ls "$TFTP_ROOT" | wc -l) items)."
ls -la "$TFTP_ROOT"/start4*.elf "$TFTP_ROOT"/fixup4*.dat "$TFTP_ROOT"/config.txt 2>/dev/null || true
}
if [[ -n "$TARGET" ]]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
scp "$SCRIPT_DIR/populate-tftpboot-from-git.sh" "$TARGET:/tmp/populate-tftpboot.sh"
ssh "$TARGET" "bash /tmp/populate-tftpboot.sh"
exit 0
fi
do_populate

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# Enable root SSH login on the cm4-provisioning LXC and add your SSH key.
# Finds the container by hostname "cm4-provisioning" on the host, or use CTID=id to override.
# Usage:
# ./setup-lxc-ssh.sh [proxmox_host] [ssh_public_key_file]
# ROOT_PASSWORD='yourpassword' ./setup-lxc-ssh.sh [proxmox_host] [ssh_public_key_file]
# CTID=202 ./setup-lxc-ssh.sh root@10.130.60.224 # force a specific container ID
#
# If ssh_public_key_file is omitted, uses ~/.ssh/id_ed25519.pub or ~/.ssh/id_rsa.pub.
set -e
PROXMOX="${1:-root@10.130.60.224}"
KEY_FILE="${2:-}"
CTID="${CTID:-}"
# Find public key
if [[ -z "$KEY_FILE" ]]; then
for f in ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub; do
if [[ -f "$f" ]]; then
KEY_FILE="$f"
break
fi
done
fi
if [[ -z "$KEY_FILE" || ! -f "$KEY_FILE" ]]; then
echo "No SSH public key found. Usage: $0 [proxmox_host] [ssh_public_key_file]"
exit 1
fi
KEY_CONTENT=$(cat "$KEY_FILE")
ROOT_PASSWORD="${ROOT_PASSWORD:-}"
echo "Using key from: $KEY_FILE"
echo "Configuring LXC (cm4-provisioning) on $PROXMOX (enable SSH, root login, add key)..."
ssh "$PROXMOX" "CTID='$CTID' KEY_CONTENT='$(echo "$KEY_CONTENT" | sed "s/'/'\\\\''/g")' ROOT_PASSWORD='$(echo "$ROOT_PASSWORD" | sed "s/'/'\\\\''/g")'" bash -s << 'REMOTE'
set -e
# Resolve CTID by hostname if not provided
if [[ -z "$CTID" ]]; then
CTID=$(pct list -no-header -output vmid,name 2>/dev/null | awk '$2=="cm4-provisioning"{print $1}' | head -1)
fi
if [[ -z "$CTID" ]]; then
echo "Error: no container with hostname cm4-provisioning found. Set CTID=id and re-run."
exit 1
fi
echo "Using LXC ID: $CTID"
# Ensure container is running
pct start $CTID 2>/dev/null || true
sleep 2
# Install openssh-server if missing, enable and start
pct exec $CTID -- bash -c 'apt-get update -qq && apt-get install -y -qq openssh-server 2>/dev/null; systemctl enable ssh 2>/dev/null; systemctl start ssh 2>/dev/null' || true
# Enable root login via password and/or public key
pct exec $CTID -- bash -c '
sed -i "s/^#*PermitRootLogin.*/PermitRootLogin yes/" /etc/ssh/sshd_config 2>/dev/null || true
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
'
# Set root password if provided (pass via stdin so no quoting in -c)
if [[ -n "$ROOT_PASSWORD" ]]; then
echo "root:$ROOT_PASSWORD" | pct exec $CTID -- chpasswd
echo "Root password set."
fi
# Add SSH key to root (pass key via stdin to avoid quoting issues)
echo "$KEY_CONTENT" | pct exec $CTID -- bash -c "mkdir -p /root/.ssh; chmod 700 /root/.ssh; cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"
echo "SSH key added to /root/.ssh/authorized_keys"
# Show IP for convenience
IP=$(pct exec $CTID -- hostname -I 2>/dev/null | awk '{print $1}')
echo "Done. Connect with: ssh root@$IP"
REMOTE

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env bash
# Setup network boot on the provisioning LXC: DHCP + TFTP on eth1, NAT so LAN uses eth0 for internet.
# Run inside the LXC (as root), or from your machine: ./setup-network-boot-on-lxc.sh root@10.130.60.141
# When run with ssh target, rsyncs lxc/ and runs this script inside the container.
set -e
TARGET="${1:-}"
if [[ -n "$TARGET" ]]; then
# Run remotely: sync lxc/ and script, then execute inside LXC
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
echo "Syncing lxc config and script to $TARGET ..."
rsync -a "$REPO_DIR/lxc/" "$TARGET:/tmp/cm4-network-boot-lxc/" --exclude='.git'
scp "$SCRIPT_DIR/setup-network-boot-on-lxc.sh" "$TARGET:/tmp/cm4-network-boot-lxc/setup.sh"
ssh "$TARGET" "bash /tmp/cm4-network-boot-lxc/setup.sh"
echo "Done."
exit 0
fi
# --- Running inside the LXC from here ---
echo "Configuring network boot (DHCP + TFTP on eth1, NAT via eth0) ..."
# 1) Install dnsmasq
if ! command -v dnsmasq >/dev/null 2>&1; then
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq dnsmasq
fi
# 2) dnsmasq config for eth1 only (DHCP + TFTP)
mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/network-boot.conf << 'DNSMASQ'
# DHCP + TFTP on eth1 only (provisioning LAN)
interface=eth1
bind-interfaces
dhcp-range=10.20.50.100,10.20.50.200,12h
enable-tftp
tftp-root=/srv/tftpboot
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf
log-dhcp
log-queries
port=0
DNSMASQ
# 3) TFTP root: fetch Raspberry Pi 4 boot files from GitHub if missing
mkdir -p /srv/tftpboot
if [[ ! -f /srv/tftpboot/start4cd.elf ]]; then
echo "Fetching Raspberry Pi firmware boot files from GitHub ..."
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl
fi
tmpdir=$(mktemp -d)
trap "rm -rf $tmpdir" EXIT
if command -v curl >/dev/null 2>&1; then
curl -sL "https://github.com/raspberrypi/firmware/archive/refs/heads/master.tar.gz" -o "$tmpdir/firmware.tar.gz"
else
wget -q -O "$tmpdir/firmware.tar.gz" "https://github.com/raspberrypi/firmware/archive/refs/heads/master.tar.gz"
fi
tar xzf "$tmpdir/firmware.tar.gz" -C "$tmpdir"
cp -a "$tmpdir/firmware-master/boot/." /srv/tftpboot/
rm -rf "$tmpdir"
echo "Copied RPi boot files to /srv/tftpboot"
else
echo "TFTP root already has boot files (start4cd.elf present), skipping fetch."
fi
# 4) IP forwarding (LAN clients use WAN)
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)
if command -v nft >/dev/null 2>&1; then
mkdir -p /etc/nftables.d
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
}
}
NFT
if ! nft list table ip nat 2>/dev/null | grep -q postrouting; then
nft -f /etc/nftables.d/nat-lan.conf
fi
# Ensure main config includes our drop-in (Debian)
if [[ -f /etc/nftables.conf ]] && ! grep -q 'nftables.d/nat-lan' /etc/nftables.conf 2>/dev/null; then
echo 'include "/etc/nftables.d/nat-lan.conf"' >> /etc/nftables.conf
fi
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
echo "NAT rule added (iptables)."
fi
# 6) Enable and start dnsmasq
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 " - TFTP root: /srv/tftpboot (RPi boot files from GitHub)"

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Sync portal (file server) content from the repo to the LXC.
# Updates /var/lib/cm4-provisioning/portal-files/ so first-boot and the
# dashboard /files/ serve the same scripts and assets as in the repo.
# Usage: ./sync-portal-files-to-lxc.sh [user@lxc_ip]
# Example: ./sync-portal-files-to-lxc.sh root@10.130.60.141
set -e
LXC="${1:-root@10.130.60.141}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
CLOUDINIT_DIR="$REPO_DIR/cloud-init"
REMOTE_PORTAL="/var/lib/cm4-provisioning/portal-files"
REMOTE_FIRST_BOOT="${REMOTE_PORTAL}/first-boot"
if [[ ! -d "$CLOUDINIT_DIR" ]]; then
echo "Error: cloud-init dir not found: $CLOUDINIT_DIR"
exit 1
fi
echo "Syncing portal files to $LXC ($REMOTE_PORTAL) ..."
ssh "$LXC" "command -v rsync >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y rsync)"
ssh "$LXC" "mkdir -p $REMOTE_FIRST_BOOT"
# first-boot.sh at portal root (cloud-init downloads it by URL, not from first-boot/ subfolder)
rsync -avz "$CLOUDINIT_DIR/first-boot.sh" "$LXC:$REMOTE_PORTAL/"
# config-files/* (includes chromium-kiosk.desktop) → portal-files/first-boot/
rsync -avz --exclude='README.md' \
"$CLOUDINIT_DIR/config-files/" \
"$LXC:$REMOTE_FIRST_BOOT/"
# start-chromium.sh → portal-files/first-boot/
rsync -avz "$CLOUDINIT_DIR/start-chromium.sh" "$LXC:$REMOTE_FIRST_BOOT/"
# plymouth-custom/* (custom.plymouth, custom.script, splash.png if present) → portal-files/first-boot/
rsync -avz \
"$CLOUDINIT_DIR/files-from-guard/plymouth-custom/" \
"$LXC:$REMOTE_FIRST_BOOT/"
# one-shot scripts from cloud-init root → portal-files/first-boot/ (wallpaper set in first-boot via labwc autostart)
rsync -avz \
"$CLOUDINIT_DIR/set-rotation-once.sh" \
"$LXC:$REMOTE_FIRST_BOOT/"
echo "Done. Portal files at http://$(echo "$LXC" | cut -d@ -f2):5000/files/"

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Run on the Proxmox host (root@10.130.60.224) to diagnose cloud-init build failures.
# Usage: scp this script to the host, then: bash troubleshoot-cloudinit-build.sh
set -e
echo "=== Cloud-init build troubleshoot ==="
echo ""
echo "1. xz available?"
if command -v xz >/dev/null 2>&1; then
xz --version | head -1
else
echo " NOT FOUND. Install: apt install -y xz-utils"
fi
echo ""
echo "2. /tmp space (need several GB for decompress)?"
df -h /tmp
echo ""
echo "3. Provisioning dir and last request?"
PROV_DIR="${CM4_PROVISIONING_DIR:-/var/lib/cm4-provisioning}"
[[ -f /opt/cm4-provisioning/env ]] && source /opt/cm4-provisioning/env
echo " PROV_DIR=$PROV_DIR"
if [[ -f "$PROV_DIR/build_cloudinit_request.json" ]]; then
echo " Last request URL: $(python3 -c "import json; print(json.load(open('$PROV_DIR/build_cloudinit_request.json')).get('url','?'))" 2>/dev/null || echo '?')"
else
echo " No request file (build may have already run)."
fi
echo ""
echo "4. Last build status?"
if [[ -f "$PROV_DIR/build_cloudinit_status.json" ]]; then
cat "$PROV_DIR/build_cloudinit_status.json" | python3 -m json.tool 2>/dev/null || cat "$PROV_DIR/build_cloudinit_status.json"
else
echo " No status file."
fi
echo ""
TEST_DIR=$(mktemp -d)
trap "rm -rf $TEST_DIR" EXIT
echo "5. Quick xz decompress test?"
echo "test content" > "$TEST_DIR/f.txt"
xz -k "$TEST_DIR/f.txt" 2>/dev/null && xz -d -k -f "$TEST_DIR/f.txt.xz" 2>/dev/null && echo " OK" || echo " FAILED"
echo ""
echo "Done. If xz is missing, run on host: apt update && apt install -y xz-utils"