Update eMMC provisioning documentation and deployment scripts: clarify one-command deploy process, enhance deployment layout details, and improve SSH setup instructions for LXC containers. Add functionality to dynamically find LXC by hostname and streamline backup directory configuration.

This commit is contained in:
nearxos
2026-02-19 11:59:25 +02:00
parent a3661df8c2
commit 5afb194daf
5 changed files with 223 additions and 108 deletions

View File

@@ -18,7 +18,7 @@ emmc-provisioning/
│ ├── cm4-flash-trigger.sh Called by udev; starts the flash job
│ └── 90-cm4-boot-mode.rules udev rule (USB vendor 2b8e)
├── scripts/ Deployment and one-off scripts
│ ├── deploy-to-proxmox.sh Deploy to Proxmox host + LXC 201
│ ├── deploy-to-proxmox.sh One-command deploy to Proxmox host + LXC (by hostname)
│ └── install-usbboot-on-host.sh Build and install rpiboot on the host
├── dashboard/ Flask web UI (runs in LXC or standalone)
│ ├── app.py

View File

@@ -1,38 +1,53 @@
# CM4 eMMC provisioning on Proxmox (LXC + host)
The auto-flash **runs on the Proxmox host** (where the USB device appears). The **LXC** holds the same scripts and shares the **golden image** directory with the host so you can manage the image from the container.
The auto-flash **runs on the Proxmox host** (where the USB device appears). The **LXC** holds the dashboard and shares the **golden image** directory with the host.
## One-command deploy
From your repo, a single run deploys **all** host and LXC files (scripts, systemd units, udev, dashboard):
```bash
./chromium-setup/emmc-provisioning/scripts/deploy-to-proxmox.sh root@YOUR_PROXMOX_HOST
```
Optional env: `CM4_BACKUPS_HOST_PATH=/path`, `DEPLOY_ROOTFS_STORAGE=local-lvm`, `DEPLOY_LXC_ROOT_PASSWORD=secret` (set root password in LXC and enable SSH), `DEPLOY_LXC_SSH_KEY=/path/to/pub` (default: `~/.ssh/id_ed25519.pub` or `id_rsa.pub` — copied to LXC root so you can `ssh root@<LXC-IP>`).
The script **finds the container by hostname `cm4-provisioning`** (any existing ID). If none exists, it **creates a new LXC with the next available ID**. So you can redeploy repeatedly without assuming a fixed ID like 201.
## What is deployed
| Where | What |
|-------|-----|
| **Proxmox host** | udev rule, trigger script, flash script, rpiboot (after you run the install script), `/var/lib/cm4-provisioning/` (golden image dir), `/etc/cm4-provisioning/enabled` |
| **LXC 201 (cm4-provisioning)** | Same scripts in `/opt/cm4-provisioning/`, same env; `/var/lib/cm4-provisioning/` is a **bind mount** from the host (shared storage for the golden image) |
| **Proxmox host** | udev rule, trigger script, flash script, build-cloudinit and run-shrink scripts, systemd path units (build + shrink), `/var/lib/cm4-provisioning/`, `/etc/cm4-provisioning/enabled` |
| **LXC (hostname cm4-provisioning)** | Dashboard (Flask) in `/opt/cm4-provisioning/dashboard/`; `/var/lib/cm4-provisioning/` is a **bind mount** from the host (shared storage for golden image and backups) |
When you plug the reTerminal in boot mode into the **host**, udev on the host runs the flash (rpiboot + dd). The golden image is read from `/var/lib/cm4-provisioning/golden.img` on the host (same path visible in the LXC).
---
## Deployment that was done
## Deployment layout (after running the deploy script)
1. **LXC 201** created on Proxmox `10.130.60.224`:
- Hostname: `cm4-provisioning`
1. **LXC** (hostname `cm4-provisioning`, ID = found by hostname or next free):
- Debian 12, 1 GB RAM, 8 GB rootfs
- Bind mount: host `/var/lib/cm4-provisioning` → container `/var/lib/cm4-provisioning`
- Optional second mount: `CM4_BACKUPS_HOST_PATH` → container `/var/lib/cm4-provisioning/backups`
2. **On the host**:
- `/opt/cm4-provisioning/flash-emmc-on-connect.sh` flash script
- `/opt/cm4-provisioning/build-cloudinit-image.sh` build cloud-init image (triggered by path unit)
- `/opt/cm4-provisioning/run-shrink-on-host.sh` PiShrink for dashboard Shrink/Compress
- `/usr/local/bin/cm4-flash-trigger.sh` started by udev
- `/etc/udev/rules.d/90-cm4-boot-mode.rules` run trigger when USB vendor `2b8e` is added
- `/opt/cm4-provisioning/env` `GOLDEN_IMAGE`, `RPIBOOT_DIR`, `EMMC_SIZE_BYTES`
- `/opt/cm4-provisioning/env` `GOLDEN_IMAGE`, `RPIBOOT_DIR`, `EMMC_SIZE_BYTES` (and `BACKUPS_DIR` if `CM4_BACKUPS_HOST_PATH` set)
- `/etc/cm4-provisioning/enabled` safety switch (remove to disable auto-flash)
- systemd: `cm4-build-cloudinit.path` + `.service`, `cm4-shrink.path` + `.service`
- `/opt/cm4-provisioning/fix-gadget-bootcode-on-host.sh` used by `install-usbboot-on-host.sh` after building usbboot (fixes "rpiboot gadget empty" when gadget has broken symlinks)
3. **Inside LXC 201**:
- Same scripts in `/opt/cm4-provisioning/` and env (for reference/backup)
- Golden image path: `/var/lib/cm4-provisioning/golden.img` (bind-mounted from host)
- **Dashboard** (optional): Flask app in `/opt/cm4-provisioning/dashboard/` to monitor deployment and show connection steps; see below.
3. **Inside the LXC** (use `pct exec <CTID> -- ...` where `<CTID>` is the ID of the container with hostname `cm4-provisioning`; get it with `pct list`):
- Dashboard: Flask app in `/opt/cm4-provisioning/dashboard/` (monitor deployment, backup list, build cloud-init, set golden).
- Golden image path: `/var/lib/cm4-provisioning/golden.img` (bind-mounted from host).
4. **usbboot (rpiboot)** was **not** built on the host (no outbound DNS during deploy). You must install it when the host has internet.
4. **usbboot (rpiboot)** is **not** installed by the deploy script. Install it when the host has internet (see below).
---
@@ -55,11 +70,13 @@ ssh root@10.130.60.224
bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh
```
This installs dependencies, clones usbboot, builds it, and copies `rpiboot` to `/opt/usbboot/`.
This installs dependencies, clones usbboot, builds it, and copies `rpiboot` to `/opt/usbboot/`. It then runs **fix-gadget-bootcode-on-host.sh** if present (from deploy), so the gadget has valid boot files and rpiboot does not fail with "No bootcode files found".
### 2. Enable root SSH and add your SSH key to LXC 201
### 2. Enable root SSH and add your SSH key to the LXC
No root password is set by default. To log in as root over SSH:
If you deployed with **`DEPLOY_LXC_ROOT_PASSWORD`** and/or a default SSH key (**`~/.ssh/id_ed25519.pub`** or **`id_rsa.pub`**), the LXC already has SSH enabled, root password set, and your key in `/root/.ssh/authorized_keys` — you can **skip** to `ssh root@<LXC-IP>` (get IP from deploy output or `pct exec <CTID> -- hostname -I`).
Otherwise, to enable root SSH and add a key:
- **Option A Use the setup script (recommended):** From your machine (with SSH key and optional password):
@@ -71,11 +88,11 @@ No root password is set by default. To log in as root over SSH:
ROOT_PASSWORD='YourPassword' ./chromium-setup/emmc-provisioning/scripts/setup-lxc-ssh.sh root@10.130.60.224 ~/.ssh/id_ed25519.pub
```
Then connect with `ssh root@<LXC-IP>` (script prints the IP). Get the IP anytime with:
`ssh root@10.130.60.224 "pct exec 201 -- hostname -I"`
Then connect with `ssh root@<LXC-IP>` (script prints the IP). To get the LXC IP:
`ssh root@HOST "CID=\$(pct list -no-header -output vmid,name | awk '\''\$2==\"cm4-provisioning\"{print \$1}'\''); pct exec \$CID -- hostname -I"`
- **Option B Manual:**
`ssh root@10.130.60.224` then `pct exec 201 -- bash` to get a shell in the container. Run `apt-get install -y openssh-server`, edit `/etc/ssh/sshd_config` to set `PermitRootLogin yes`, run `passwd` to set root password, add your key to `/root/.ssh/authorized_keys`, and restart `ssh`.
`ssh root@HOST`, then `pct exec <CTID> -- bash` (use the container ID from `pct list` for hostname cm4-provisioning). Install openssh-server, set `PermitRootLogin yes`, set root password, add your key, restart ssh.
### 3. (Optional) Store backup images on a host directory
@@ -87,7 +104,7 @@ To keep backup images on a specific host path (e.g. a large disk or NFS mount) i
CM4_BACKUPS_HOST_PATH=/mnt/storage/cm4-backups ./chromium-setup/emmc-provisioning/scripts/deploy-to-proxmox.sh root@10.130.60.224
```
Create `/mnt/storage/cm4-backups` (or your path) on the host first if it doesnt exist; the deploy script will create it if possible. To add or change the backup mount on an already-deployed host, set `CM4_BACKUPS_HOST_PATH` and run the deploy script again, then on the host add `BACKUPS_DIR=<path>` to `/opt/cm4-provisioning/env` and add the bind mount (see deploy script for the `pct set 201 -mp1 ...` step).
Create `/mnt/storage/cm4-backups` (or your path) on the host first if it doesnt exist; the deploy script will create it if possible. To add or change the backup mount, set `CM4_BACKUPS_HOST_PATH` and run the deploy script again (it reuses the container by hostname and updates the bind mount).
### 4. Put the golden image on the host (or in the LXC)
@@ -100,23 +117,23 @@ The image must be at **`/var/lib/cm4-provisioning/golden.img`** on the **host**.
- **From the LXC** (e.g. after copying the image into the container elsewhere first):
```bash
pct exec 201 -- ls -la /var/lib/cm4-provisioning/
# Copy to that path inside the container; it's the same as the host path.
pct exec <CTID> -- ls -la /var/lib/cm4-provisioning/
# Replace <CTID> with the ID of the cm4-provisioning container (pct list).
```
### 5. Run the provisioning dashboard (optional)
The dashboard shows **connection steps** and **live deployment status** (idle / connecting / flashing / done / error) and a recent flash log. It reads the same `status.json` and `flash.log` that the hosts flash script writes (via the bind-mounted `/var/lib/cm4-provisioning`).
**Inside LXC 201:**
**Inside the LXC (pct exec <CTID> -- bash):**
```bash
# Copy dashboard into the container (from host, if you have the repo there)
# Or from your workstation:
# rsync -a chromium-setup/emmc-provisioning/dashboard/ root@10.130.60.224:/tmp/dashboard/
# ssh root@10.130.60.224 "pct push 201 /tmp/dashboard/app.py /opt/cm4-provisioning/dashboard/ && pct push 201 /tmp/dashboard/cm4-dashboard.service /opt/cm4-provisioning/dashboard/ && pct exec 201 -- mkdir -p /opt/cm4-provisioning/dashboard/templates && ..."
# Or re-run deploy-to-proxmox.sh to push the latest dashboard files.
# Inside the LXC (pct exec 201 -- bash):
# Inside the LXC (pct exec <CTID> -- bash):
apt-get update && apt-get install -y python3-flask
mkdir -p /opt/cm4-provisioning/dashboard/templates
# Copy app.py, templates/index.html, cm4-dashboard.service into the container (see dashboard/README.md)
@@ -126,7 +143,7 @@ systemctl daemon-reload
systemctl enable --now cm4-dashboard
```
Then open **http://&lt;LXC-201-IP&gt;:5000** (get the IP with `pct exec 201 -- hostname -I`). If the LXC is on a private network, set up port forwarding on the Proxmox host or use a reverse proxy so you can reach the dashboard from your browser.
Then open **http://&lt;LXC-IP&gt;:5000** (get the IP with `pct exec <CTID> -- hostname -I`). If the LXC is on a private network, set up port forwarding on the Proxmox host or use a reverse proxy.
### 6. Optional: disable or enable auto-flash
@@ -221,7 +238,7 @@ That script syncs the repo to the host and reinstalls scripts on both the host a
| Item | Location |
|------|----------|
| LXC | 201, hostname `cm4-provisioning`, Proxmox `10.130.60.224` |
| LXC | Hostname `cm4-provisioning` (ID from `pct list`), on your Proxmox host |
| Golden image | `/var/lib/cm4-provisioning/golden.img` (host and LXC see the same file) |
| Flash runs on | Proxmox **host** (udev + rpiboot + dd) |
| Build rpiboot on host | Run `scripts/install-usbboot-on-host.sh` on the host when it has internet |

View File

@@ -1,11 +1,22 @@
#!/usr/bin/env bash
# Deploy CM4 eMMC provisioning to a Proxmox host (creates LXC 201, installs scripts on host and in LXC).
# 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.
#
# 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.130.60.224
# Optional: DEPLOY_ROOTFS_STORAGE=local-lvm (or local-zfs, etc.) — storage for LXC rootfs
# Optional: CM4_BACKUPS_HOST_PATH=/mnt/storage/cm4-backups — host dir for backup images; bind-mounted into LXC so images are stored on the host
# Requires: ssh key access to root@<host>
# Logging: set DEPLOY_LOG=1 to also write to deploy-YYYYMMDD-HHMMSS.log in the script dir.
# Example: ./deploy-to-proxmox.sh root@10.20.30.152
#
# Optional env:
# DEPLOY_ROOTFS_STORAGE=local-lvm — LXC rootfs storage (default: local-lvm)
# 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}"
@@ -21,24 +32,56 @@ fi
log() { echo "[$(date -Iseconds)] $*"; }
log "Deploying to $PROXMOX ..."
# Use a clean staging dir (remove if present so we never write into a symlink or bad state)
log "[1/4] Cleaning remote staging dir ..."
ssh "$PROXMOX" "rm -rf /tmp/emmc-provisioning-deploy"
log "[2/4] Rsync repo to $PROXMOX ..."
rsync -a "$REPO_DIR/" "$PROXMOX:/tmp/emmc-provisioning-deploy/" --exclude='.git' --exclude='scripts/deploy-to-proxmox.sh'
log "[3/4] Running remote install (host + LXC) ..."
# Optional: gather SSH key and LXC root password for setup inside deploy
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')
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
[[ -n "$DEPLOY_LXC_ROOT_PASSWORD" ]] && log "Will set LXC root password (DEPLOY_LXC_ROOT_PASSWORD)."
ssh "$PROXMOX" "ROOTFS_STORAGE='$ROOTFS_STORAGE' CM4_BACKUPS_HOST_PATH='${CM4_BACKUPS_HOST_PATH:-}'" bash -s << 'REMOTE'
log "Deploying to $PROXMOX ..."
log "[1/5] Cleaning remote staging dir ..."
ssh "$PROXMOX" "rm -rf /tmp/emmc-provisioning-deploy"
log "[2/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 "[3/5] Running remote install (host + LXC) ..."
# Pass optional LXC SSH vars (base64) so remote can set password and add key
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:-local-lvm}"
BACKUPS_HOST_PATH="${CM4_BACKUPS_HOST_PATH:-}"
LXC_HOSTNAME="cm4-provisioning"
log() { echo "[$(date -Iseconds)] $*"; }
# Ensure LXC 201 exists (create if not)
if ! pct status 201 &>/dev/null; then
# Use Debian 12 template: prefer one in cache, else download latest
# --- 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
@@ -46,30 +89,28 @@ if ! pct status 201 &>/dev/null; then
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")
log "Creating LXC 201 (cm4-provisioning) (rootfs on ${ROOTFS_STORAGE}, template ${TMPL_NAME})..."
pct create 201 "local:vztmpl/${TMPL_NAME}" \
--hostname cm4-provisioning --memory 1024 --swap 0 --cores 1 \
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 \
--unprivileged 0 --features nesting=1 -tag cm4-provisioning
mkdir -p /var/lib/cm4-provisioning
pct set 201 -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning
log "LXC 201 created and mount configured."
else
log "LXC 201 already exists."
pct set "$CTID" -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning
log "LXC $CTID created and mount configured."
fi
# Optional: bind-mount a host directory for backup images (so they are stored on the host, not LXC rootfs)
# Optional: bind-mount host directory for backup images
if [[ -n "$BACKUPS_HOST_PATH" ]]; then
mkdir -p "$BACKUPS_HOST_PATH"
pct stop 201 2>/dev/null || true
pct set 201 -mp1 "$BACKUPS_HOST_PATH",mp=/var/lib/cm4-provisioning/backups
pct start 201 2>/dev/null || true
log "Backups mount: host $BACKUPS_HOST_PATH -> LXC /var/lib/cm4-provisioning/backups"
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
# Host: install scripts and udev (from host/)
log "Host: installing scripts and udev ..."
# --- Host: scripts, systemd, udev ---
log "Host: installing scripts and systemd units ..."
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
@@ -77,6 +118,7 @@ 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/
@@ -89,7 +131,8 @@ 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
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
@@ -101,44 +144,86 @@ touch /etc/cm4-provisioning/enabled
mkdir -p /var/lib/cm4-provisioning/backups
[[ -n "$BACKUPS_HOST_PATH" ]] && mkdir -p "$BACKUPS_HOST_PATH"
# Start LXC if stopped
log "Starting LXC 201 if stopped ..."
pct start 201 2>/dev/null || true
# --- Host: install usbboot (rpiboot) so USB flash/backup works ---
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
# LXC: install scripts (from host/)
# --- Host: install PiShrink so dashboard Shrink/Compress work ---
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
# --- 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 201 -- mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning
pct push 201 "$DEPLOY/host/flash-emmc-on-connect.sh" /opt/cm4-provisioning/flash-emmc-on-connect.sh
pct exec 201 -- chmod +x /opt/cm4-provisioning/flash-emmc-on-connect.sh
pct push 201 "$DEPLOY/host/cm4-flash-trigger.sh" /usr/local/bin/cm4-flash-trigger.sh
pct exec 201 -- chmod +x /usr/local/bin/cm4-flash-trigger.sh
pct exec 201 -- bash -c 'echo -e "GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img\nRPIBOOT_DIR=/opt/usbboot\nEMMC_SIZE_BYTES=8589934592" > /opt/cm4-provisioning/env'
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: install dashboard
# --- LXC: dashboard (all files) ---
log "LXC: installing dashboard ..."
pct exec 201 -- mkdir -p /opt/cm4-provisioning/dashboard/templates
pct push 201 "$DEPLOY/dashboard/app.py" /opt/cm4-provisioning/dashboard/app.py
pct push 201 "$DEPLOY/dashboard/templates/index.html" /opt/cm4-provisioning/dashboard/templates/index.html
pct push 201 "$DEPLOY/dashboard/cm4-dashboard.service" /opt/cm4-provisioning/dashboard/cm4-dashboard.service
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/index.html" /opt/cm4-provisioning/dashboard/templates/index.html
pct push "$CTID" "$DEPLOY/dashboard/cm4-dashboard.service" /opt/cm4-provisioning/dashboard/cm4-dashboard.service
# LXC: install Flask and enable dashboard service
log "LXC: installing python3-flask and enabling cm4-dashboard service ..."
pct exec 201 -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-flask'
pct exec 201 -- cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/
pct exec 201 -- systemctl daemon-reload
pct exec 201 -- systemctl enable --now cm4-dashboard
# --- LXC: Flask and systemd ---
log "LXC: installing python3-flask and enabling cm4-dashboard ..."
pct exec "$CTID" -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-flask'
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
log "LXC: cm4-dashboard enabled and started."
log "Deploy done (remote)."
echo "Next: Install usbboot on host when online: ssh <host> 'bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh'"
# --- 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)..."
pct exec "$CTID" -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openssh-server 2>/dev/null; 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" ]] && echo "LXC SSH: ssh root@$LXC_IP"
fi
log "Deploy done (remote). LXC ID: $CTID"
REMOTE
log "[4/4] Deploy finished."
log "[5/5] Deploy finished."
echo ""
echo "Done. Put golden.img in /var/lib/cm4-provisioning/ on the host (or scp to LXC 201 at /var/lib/cm4-provisioning/)."
[[ -n "${CM4_BACKUPS_HOST_PATH:-}" ]] && echo "Backup images are stored on the host at: $CM4_BACKUPS_HOST_PATH (bind-mounted into LXC at /var/lib/cm4-provisioning/backups)."
echo "When the host has internet, run on the host: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh"
echo "Dashboard: install flask in LXC 201 and enable cm4-dashboard.service (see docs/PROXMOX-LXC-DEPLOYMENT.md)."
echo "=== Deploy complete ==="
echo "Host and LXC are fully set up: usbboot (rpiboot), PiShrink, dashboard, systemd, udev."
echo ""
echo "--- Only remaining step (manual) ---"
echo " Add a golden image for Deploy (writing image to device):"
echo " • Dashboard: open http://<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>:5000 (LXC IP: from Proxmox UI, or on host: pct exec <CTID> -- hostname -I)"
[[ -n "${DEPLOY_LXC_ROOT_PASSWORD:-}" || -n "${DEPLOY_SSH_KEY_B64:-}" ]] && 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 written to: $LOG_FILE"
echo " - Log: $LOG_FILE"
fi

View File

@@ -18,3 +18,13 @@ 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

@@ -1,20 +1,17 @@
#!/usr/bin/env bash
# Enable root SSH login on LXC 201 (cm4-provisioning) and add your SSH key.
# 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]
#
# Examples:
# ./setup-lxc-ssh.sh root@10.130.60.224
# ./setup-lxc-ssh.sh root@10.130.60.224 ~/.ssh/id_ed25519.pub
# ROOT_PASSWORD='MySecurePass' ./setup-lxc-ssh.sh root@10.130.60.224
# 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:-201}"
CTID="${CTID:-}"
# Find public key
if [[ -z "$KEY_FILE" ]]; then
@@ -34,39 +31,45 @@ KEY_CONTENT=$(cat "$KEY_FILE")
ROOT_PASSWORD="${ROOT_PASSWORD:-}"
echo "Using key from: $KEY_FILE"
echo "Configuring LXC $CTID on $PROXMOX (enable SSH, root login, add key)..."
echo "Configuring LXC (cm4-provisioning) on $PROXMOX (enable SSH, root login, add key)..."
ssh "$PROXMOX" bash -s << REMOTE
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
CTID="$CTID"
KEY_CONTENT='$(echo "$KEY_CONTENT" | sed "s/'/'\\\\''/g")'
ROOT_PASSWORD='$(echo "$ROOT_PASSWORD" | sed "s/'/'\\\\''/g")'
# 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
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
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 '
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
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 "$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"
IP=$(pct exec $CTID -- hostname -I 2>/dev/null | awk '{print $1}')
echo "Done. Connect with: ssh root@$IP"
REMOTE