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:
56
emmc-provisioning/scripts/build-and-deploy-usbboot-to-host.sh
Executable file
56
emmc-provisioning/scripts/build-and-deploy-usbboot-to-host.sh
Executable 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)."
|
||||
21
emmc-provisioning/scripts/check-usb-on-host.sh
Normal file
21
emmc-provisioning/scripts/check-usb-on-host.sh
Normal 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)"
|
||||
24
emmc-provisioning/scripts/deploy-20260218-095749.log
Normal file
24
emmc-provisioning/scripts/deploy-20260218-095749.log
Normal 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
|
||||
20
emmc-provisioning/scripts/deploy-20260218-095859.log
Normal file
20
emmc-provisioning/scripts/deploy-20260218-095859.log
Normal 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
|
||||
20
emmc-provisioning/scripts/deploy-20260218-101119.log
Normal file
20
emmc-provisioning/scripts/deploy-20260218-101119.log
Normal 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
|
||||
35
emmc-provisioning/scripts/deploy-dashboard-to-lxc.sh
Executable file
35
emmc-provisioning/scripts/deploy-dashboard-to-lxc.sh
Executable 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"
|
||||
387
emmc-provisioning/scripts/deploy-to-proxmox.sh
Executable file
387
emmc-provisioning/scripts/deploy-to-proxmox.sh
Executable 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
|
||||
56
emmc-provisioning/scripts/fix-gadget-bootcode-on-host.sh
Normal file
56
emmc-provisioning/scripts/fix-gadget-bootcode-on-host.sh
Normal 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"
|
||||
54
emmc-provisioning/scripts/install-pishrink-on-host.sh
Normal file
54
emmc-provisioning/scripts/install-pishrink-on-host.sh
Normal 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"
|
||||
30
emmc-provisioning/scripts/install-usbboot-on-host.sh
Normal file
30
emmc-provisioning/scripts/install-usbboot-on-host.sh
Normal 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
|
||||
44
emmc-provisioning/scripts/monitor-from-host.sh
Executable file
44
emmc-provisioning/scripts/monitor-from-host.sh
Executable 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)"
|
||||
35
emmc-provisioning/scripts/populate-gadget-on-host.sh
Executable file
35
emmc-provisioning/scripts/populate-gadget-on-host.sh
Executable 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/'"
|
||||
47
emmc-provisioning/scripts/populate-tftpboot-from-git.sh
Executable file
47
emmc-provisioning/scripts/populate-tftpboot-from-git.sh
Executable 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
|
||||
75
emmc-provisioning/scripts/setup-lxc-ssh.sh
Executable file
75
emmc-provisioning/scripts/setup-lxc-ssh.sh
Executable 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
|
||||
104
emmc-provisioning/scripts/setup-network-boot-on-lxc.sh
Executable file
104
emmc-provisioning/scripts/setup-network-boot-on-lxc.sh
Executable 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)"
|
||||
46
emmc-provisioning/scripts/sync-portal-files-to-lxc.sh
Executable file
46
emmc-provisioning/scripts/sync-portal-files-to-lxc.sh
Executable 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/"
|
||||
46
emmc-provisioning/scripts/troubleshoot-cloudinit-build.sh
Normal file
46
emmc-provisioning/scripts/troubleshoot-cloudinit-build.sh
Normal 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"
|
||||
Reference in New Issue
Block a user