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 │ ├── cm4-flash-trigger.sh Called by udev; starts the flash job
│ └── 90-cm4-boot-mode.rules udev rule (USB vendor 2b8e) │ └── 90-cm4-boot-mode.rules udev rule (USB vendor 2b8e)
├── scripts/ Deployment and one-off scripts ├── 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 │ └── install-usbboot-on-host.sh Build and install rpiboot on the host
├── dashboard/ Flask web UI (runs in LXC or standalone) ├── dashboard/ Flask web UI (runs in LXC or standalone)
│ ├── app.py │ ├── app.py

View File

@@ -1,38 +1,53 @@
# CM4 eMMC provisioning on Proxmox (LXC + host) # 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 ## What is deployed
| Where | What | | 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` | | **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 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) | | **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). 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`: 1. **LXC** (hostname `cm4-provisioning`, ID = found by hostname or next free):
- Hostname: `cm4-provisioning`
- Debian 12, 1 GB RAM, 8 GB rootfs - Debian 12, 1 GB RAM, 8 GB rootfs
- Bind mount: host `/var/lib/cm4-provisioning` → container `/var/lib/cm4-provisioning` - 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**: 2. **On the host**:
- `/opt/cm4-provisioning/flash-emmc-on-connect.sh` flash script - `/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 - `/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 - `/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) - `/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**: 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`):
- Same scripts in `/opt/cm4-provisioning/` and env (for reference/backup) - 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) - 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.
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 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): - **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 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: Then connect with `ssh root@<LXC-IP>` (script prints the IP). To get the LXC IP:
`ssh root@10.130.60.224 "pct exec 201 -- hostname -I"` `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:** - **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 ### 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 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) ### 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): - **From the LXC** (e.g. after copying the image into the container elsewhere first):
```bash ```bash
pct exec 201 -- ls -la /var/lib/cm4-provisioning/ pct exec <CTID> -- ls -la /var/lib/cm4-provisioning/
# Copy to that path inside the container; it's the same as the host path. # Replace <CTID> with the ID of the cm4-provisioning container (pct list).
``` ```
### 5. Run the provisioning dashboard (optional) ### 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`). 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 ```bash
# Copy dashboard into the container (from host, if you have the repo there) # Copy dashboard into the container (from host, if you have the repo there)
# Or from your workstation: # Or from your workstation:
# rsync -a chromium-setup/emmc-provisioning/dashboard/ root@10.130.60.224:/tmp/dashboard/ # 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 apt-get update && apt-get install -y python3-flask
mkdir -p /opt/cm4-provisioning/dashboard/templates mkdir -p /opt/cm4-provisioning/dashboard/templates
# Copy app.py, templates/index.html, cm4-dashboard.service into the container (see dashboard/README.md) # 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 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 ### 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 | | 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) | | Golden image | `/var/lib/cm4-provisioning/golden.img` (host and LXC see the same file) |
| Flash runs on | Proxmox **host** (udev + rpiboot + dd) | | 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 | | 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 #!/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] # Usage: ./deploy-to-proxmox.sh [proxmox_host]
# Example: ./deploy-to-proxmox.sh root@10.130.60.224 # Example: ./deploy-to-proxmox.sh root@10.20.30.152
# 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 # Optional env:
# Requires: ssh key access to root@<host> # DEPLOY_ROOTFS_STORAGE=local-lvm — LXC rootfs storage (default: local-lvm)
# Logging: set DEPLOY_LOG=1 to also write to deploy-YYYYMMDD-HHMMSS.log in the script dir. # 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 set -e
PROXMOX="${1:-root@10.130.60.224}" PROXMOX="${1:-root@10.130.60.224}"
@@ -21,24 +32,56 @@ fi
log() { echo "[$(date -Iseconds)] $*"; } log() { echo "[$(date -Iseconds)] $*"; }
log "Deploying to $PROXMOX ..." # Optional: gather SSH key and LXC root password for setup inside deploy
# Use a clean staging dir (remove if present so we never write into a symlink or bad state) DEPLOY_SSH_KEY_B64=""
log "[1/4] Cleaning remote staging dir ..." DEPLOY_LXC_PWD_B64=""
ssh "$PROXMOX" "rm -rf /tmp/emmc-provisioning-deploy" if [[ -n "${DEPLOY_LXC_ROOT_PASSWORD:-}" ]]; then
log "[2/4] Rsync repo to $PROXMOX ..." DEPLOY_LXC_PWD_B64=$(echo -n "$DEPLOY_LXC_ROOT_PASSWORD" | base64 -w 0 2>/dev/null || base64 2>/dev/null | tr -d '\n')
rsync -a "$REPO_DIR/" "$PROXMOX:/tmp/emmc-provisioning-deploy/" --exclude='.git' --exclude='scripts/deploy-to-proxmox.sh' fi
log "[3/4] Running remote install (host + LXC) ..." 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 set -e
DEPLOY=/tmp/emmc-provisioning-deploy DEPLOY=/tmp/emmc-provisioning-deploy
ROOTFS_STORAGE="${ROOTFS_STORAGE:-local-lvm}" ROOTFS_STORAGE="${ROOTFS_STORAGE:-local-lvm}"
BACKUPS_HOST_PATH="${CM4_BACKUPS_HOST_PATH:-}" BACKUPS_HOST_PATH="${CM4_BACKUPS_HOST_PATH:-}"
LXC_HOSTNAME="cm4-provisioning"
log() { echo "[$(date -Iseconds)] $*"; } log() { echo "[$(date -Iseconds)] $*"; }
# Ensure LXC 201 exists (create if not) # --- Find existing LXC by hostname or use next available ID ---
if ! pct status 201 &>/dev/null; then CTID=""
# Use Debian 12 template: prefer one in cache, else download latest 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 VZTMPL_DIR=/var/lib/vz/template/cache
DEBIAN12_TMPL=$(ls "$VZTMPL_DIR"/debian-12-standard_*.tar.zst 2>/dev/null | head -1) DEBIAN12_TMPL=$(ls "$VZTMPL_DIR"/debian-12-standard_*.tar.zst 2>/dev/null | head -1)
if [[ -z "$DEBIAN12_TMPL" ]]; then 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 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) DEBIAN12_TMPL=$(ls "$VZTMPL_DIR"/debian-12-standard_*.tar.zst 2>/dev/null | head -1)
fi fi
[[ -z "$DEBIAN12_TMPL" ]] && { log "Error: no Debian 12 template found"; exit 1; }
TMPL_NAME=$(basename "$DEBIAN12_TMPL") TMPL_NAME=$(basename "$DEBIAN12_TMPL")
log "Creating LXC 201 (cm4-provisioning) (rootfs on ${ROOTFS_STORAGE}, template ${TMPL_NAME})..." pct create "$CTID" "local:vztmpl/${TMPL_NAME}" \
pct create 201 "local:vztmpl/${TMPL_NAME}" \ --hostname "$LXC_HOSTNAME" --memory 1024 --swap 0 --cores 1 \
--hostname cm4-provisioning --memory 1024 --swap 0 --cores 1 \
--rootfs "${ROOTFS_STORAGE}:8" --net0 name=eth0,bridge=vmbr0,ip=dhcp \ --rootfs "${ROOTFS_STORAGE}:8" --net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 0 --features nesting=1 -tag cm4-provisioning --unprivileged 0 --features nesting=1 -tag cm4-provisioning
mkdir -p /var/lib/cm4-provisioning mkdir -p /var/lib/cm4-provisioning
pct set 201 -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning pct set "$CTID" -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning
log "LXC 201 created and mount configured." log "LXC $CTID created and mount configured."
else
log "LXC 201 already exists."
fi 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 if [[ -n "$BACKUPS_HOST_PATH" ]]; then
mkdir -p "$BACKUPS_HOST_PATH" mkdir -p "$BACKUPS_HOST_PATH"
pct stop 201 2>/dev/null || true pct stop "$CTID" 2>/dev/null || true
pct set 201 -mp1 "$BACKUPS_HOST_PATH",mp=/var/lib/cm4-provisioning/backups pct set "$CTID" -mp1 "$BACKUPS_HOST_PATH",mp=/var/lib/cm4-provisioning/backups
pct start 201 2>/dev/null || true pct start "$CTID" 2>/dev/null || true
log "Backups mount: host $BACKUPS_HOST_PATH -> LXC /var/lib/cm4-provisioning/backups" log "Backups mount: host $BACKUPS_HOST_PATH -> LXC $CTID /var/lib/cm4-provisioning/backups"
fi fi
# Host: install scripts and udev (from host/) # --- Host: scripts, systemd, udev ---
log "Host: installing scripts and udev ..." log "Host: installing scripts and systemd units ..."
mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning
cp "$DEPLOY/host/flash-emmc-on-connect.sh" /opt/cm4-provisioning/ cp "$DEPLOY/host/flash-emmc-on-connect.sh" /opt/cm4-provisioning/
chmod +x /opt/cm4-provisioning/flash-emmc-on-connect.sh 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 chmod +x /opt/cm4-provisioning/build-cloudinit-image.sh
cp "$DEPLOY/host/run-shrink-on-host.sh" /opt/cm4-provisioning/ cp "$DEPLOY/host/run-shrink-on-host.sh" /opt/cm4-provisioning/
chmod +x /opt/cm4-provisioning/run-shrink-on-host.sh 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/ cp "$DEPLOY/host/cm4-flash-trigger.sh" /usr/local/bin/
chmod +x /usr/local/bin/cm4-flash-trigger.sh chmod +x /usr/local/bin/cm4-flash-trigger.sh
cp "$DEPLOY/host/cm4-flash.service" /etc/systemd/system/ 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 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/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/ 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 ..." log "Host: env and dirs ..."
cat > /opt/cm4-provisioning/env << 'ENV' cat > /opt/cm4-provisioning/env << 'ENV'
GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img GOLDEN_IMAGE=/var/lib/cm4-provisioning/golden.img
@@ -101,44 +144,86 @@ touch /etc/cm4-provisioning/enabled
mkdir -p /var/lib/cm4-provisioning/backups mkdir -p /var/lib/cm4-provisioning/backups
[[ -n "$BACKUPS_HOST_PATH" ]] && mkdir -p "$BACKUPS_HOST_PATH" [[ -n "$BACKUPS_HOST_PATH" ]] && mkdir -p "$BACKUPS_HOST_PATH"
# Start LXC if stopped # --- Host: install usbboot (rpiboot) so USB flash/backup works ---
log "Starting LXC 201 if stopped ..." log "Host: installing usbboot (rpiboot)..."
pct start 201 2>/dev/null || true 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 ..." log "LXC: installing flash scripts ..."
pct exec 201 -- mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning pct exec "$CTID" -- 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 push "$CTID" "$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 exec "$CTID" -- 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 push "$CTID" "$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 "$CTID" -- 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" -- 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 ..." log "LXC: installing dashboard ..."
pct exec 201 -- mkdir -p /opt/cm4-provisioning/dashboard/templates pct exec "$CTID" -- mkdir -p /opt/cm4-provisioning/dashboard/templates
pct push 201 "$DEPLOY/dashboard/app.py" /opt/cm4-provisioning/dashboard/app.py pct push "$CTID" "$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 "$CTID" "$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 push "$CTID" "$DEPLOY/dashboard/cm4-dashboard.service" /opt/cm4-provisioning/dashboard/cm4-dashboard.service
# LXC: install Flask and enable dashboard service # --- LXC: Flask and systemd ---
log "LXC: installing python3-flask and enabling cm4-dashboard service ..." log "LXC: installing python3-flask and enabling cm4-dashboard ..."
pct exec 201 -- bash -c 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-flask' pct exec "$CTID" -- 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 "$CTID" -- cp /opt/cm4-provisioning/dashboard/cm4-dashboard.service /etc/systemd/system/
pct exec 201 -- systemctl daemon-reload pct exec "$CTID" -- systemctl daemon-reload
pct exec 201 -- systemctl enable --now cm4-dashboard pct exec "$CTID" -- systemctl enable --now cm4-dashboard
log "LXC: cm4-dashboard enabled and started." log "LXC: cm4-dashboard enabled and started."
log "Deploy done (remote)." # --- LXC: optional SSH (root password + SSH key from deploy env) ---
echo "Next: Install usbboot on host when online: ssh <host> 'bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh'" 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 REMOTE
log "[4/4] Deploy finished." log "[5/5] Deploy finished."
echo "" echo ""
echo "Done. Put golden.img in /var/lib/cm4-provisioning/ on the host (or scp to LXC 201 at /var/lib/cm4-provisioning/)." echo "=== Deploy complete ==="
[[ -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 "Host and LXC are fully set up: usbboot (rpiboot), PiShrink, dashboard, systemd, udev."
echo "When the host has internet, run on the host: bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh" echo ""
echo "Dashboard: install flask in LXC 201 and enable cm4-dashboard.service (see docs/PROXMOX-LXC-DEPLOYMENT.md)." 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 if [[ -n "$LOG_FILE" ]]; then
echo "Log written to: $LOG_FILE" echo " - Log: $LOG_FILE"
fi fi

View File

@@ -18,3 +18,13 @@ for dir in mass-storage-gadget64 mass-storage-gadget; do
[[ -d "$dir" ]] && cp -a "$dir" /opt/usbboot/ [[ -d "$dir" ]] && cp -a "$dir" /opt/usbboot/
done done
echo "usbboot installed at /opt/usbboot/rpiboot" 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 #!/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: # Usage:
# ./setup-lxc-ssh.sh [proxmox_host] [ssh_public_key_file] # ./setup-lxc-ssh.sh [proxmox_host] [ssh_public_key_file]
# ROOT_PASSWORD='yourpassword' ./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
# 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
# #
# If ssh_public_key_file is omitted, uses ~/.ssh/id_ed25519.pub or ~/.ssh/id_rsa.pub. # If ssh_public_key_file is omitted, uses ~/.ssh/id_ed25519.pub or ~/.ssh/id_rsa.pub.
set -e set -e
PROXMOX="${1:-root@10.130.60.224}" PROXMOX="${1:-root@10.130.60.224}"
KEY_FILE="${2:-}" KEY_FILE="${2:-}"
CTID="${CTID:-201}" CTID="${CTID:-}"
# Find public key # Find public key
if [[ -z "$KEY_FILE" ]]; then if [[ -z "$KEY_FILE" ]]; then
@@ -34,39 +31,45 @@ KEY_CONTENT=$(cat "$KEY_FILE")
ROOT_PASSWORD="${ROOT_PASSWORD:-}" ROOT_PASSWORD="${ROOT_PASSWORD:-}"
echo "Using key from: $KEY_FILE" 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 set -e
CTID="$CTID" # Resolve CTID by hostname if not provided
KEY_CONTENT='$(echo "$KEY_CONTENT" | sed "s/'/'\\\\''/g")' if [[ -z "$CTID" ]]; then
ROOT_PASSWORD='$(echo "$ROOT_PASSWORD" | sed "s/'/'\\\\''/g")' 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 # Ensure container is running
pct start \$CTID 2>/dev/null || true pct start $CTID 2>/dev/null || true
sleep 2 sleep 2
# Install openssh-server if missing, enable and start # 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 # 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 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 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 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) # Set root password if provided (pass via stdin so no quoting in -c)
if [[ -n "\$ROOT_PASSWORD" ]]; then if [[ -n "$ROOT_PASSWORD" ]]; then
echo "root:\$ROOT_PASSWORD" | pct exec \$CTID -- chpasswd echo "root:$ROOT_PASSWORD" | pct exec $CTID -- chpasswd
echo "Root password set." echo "Root password set."
fi fi
# Add SSH key to root (pass key via stdin to avoid quoting issues) # 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" echo "SSH key added to /root/.ssh/authorized_keys"
# Show IP for convenience # Show IP for convenience
IP=\$(pct exec \$CTID -- hostname -I 2>/dev/null | awk '{print \$1}') IP=$(pct exec $CTID -- hostname -I 2>/dev/null | awk '{print $1}')
echo "Done. Connect with: ssh root@\$IP" echo "Done. Connect with: ssh root@$IP"
REMOTE REMOTE