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

<message>Add a new step-by-step guide for deploying the CM4 eMMC provisioning service on a new Proxmox instance, enhancing clarity for users. Update existing documentation to reflect changes in network configuration options, including the introduction of LAN subnet settings for DHCP and TFTP. Modify cloud-init scripts to ensure proper management of DNS settings and improve the handling of network interfaces. Additionally, enhance the toggle script for network boot to dynamically read the LAN gateway from configuration files, streamlining the setup process and improving user experience.
This commit is contained in:
nearxos
2026-03-03 08:24:18 +02:00
parent fe72619931
commit c5e418eabc
15 changed files with 500 additions and 33 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
gnss-bootstrap-20260223-215010.img.xz
gnss-bootstrap-20260223-215010.img.xz.bak

View File

@@ -13,9 +13,10 @@ Revisions are tracked project-wide; see repo root **README.md** and `scripts/bum
emmc-provisioning/
├── README.md ← You are here
├── docs/ Documentation
│ ├── DEPLOY-NEW-PROXMOX.md Step-by-step: deploy to a new Proxmox instance
│ ├── EMMC-PROVISIONING-GUIDE.md Full setup and usage
│ ├── NETWORK-BOOT-LXC.md Network boot (PXE/dnsmasq) and LXC
│ ├── PROXMOX-LXC-DEPLOYMENT.md Proxmox LXC + host setup
│ ├── PROXMOX-LXC-DEPLOYMENT.md Proxmox LXC + host setup (reference)
│ └── PORTAL_STYLING_GUIDE.md Dashboard UI styling reference
├── host/ Scripts that run on the provisioning host (Proxmox host)
│ ├── flash-emmc-on-connect.sh rpiboot + wait for Backup/Deploy choice, then dd
@@ -53,6 +54,7 @@ emmc-provisioning/
## Quick start
1. **Read** [docs/EMMC-PROVISIONING-GUIDE.md](docs/EMMC-PROVISIONING-GUIDE.md) for setup and usage.
2. **Proxmox:** Use [scripts/deploy-to-proxmox.sh](scripts/deploy-to-proxmox.sh) to deploy to a Proxmox host; see [docs/PROXMOX-LXC-DEPLOYMENT.md](docs/PROXMOX-LXC-DEPLOYMENT.md).
3. **Manual host:** Copy scripts from `host/` to the host and install the udev rule (see the guide).
4. Put **golden.img** in `/var/lib/cm4-provisioning/` (or your configured path). When a device is detected (USB or network), the **dashboard** asks **Backup** or **Deploy**.
2. **Deploy to a new Proxmox:** Follow [docs/DEPLOY-NEW-PROXMOX.md](docs/DEPLOY-NEW-PROXMOX.md) for clear step-by-step instructions.
3. **Proxmox reference:** [scripts/deploy-to-proxmox.sh](scripts/deploy-to-proxmox.sh) and [docs/PROXMOX-LXC-DEPLOYMENT.md](docs/PROXMOX-LXC-DEPLOYMENT.md) for options, layout, and troubleshooting.
4. **Manual host:** Copy scripts from `host/` to the host and install the udev rule (see the guide).
5. Put **golden.img** in `/var/lib/cm4-provisioning/` (or your configured path). When a device is detected (USB or network), the **dashboard** asks **Backup** or **Deploy**.

View File

@@ -9,6 +9,12 @@
package_update: true
package_upgrade: false
# Keep /etc/hosts in sync with hostname (from meta-data or set below)
manage_etc_hosts: true
# DNS is managed by systemd-resolved; we do not overwrite /etc/resolv.conf
manage_resolv_conf: false
packages:
- curl
@@ -19,11 +25,76 @@ write_files:
PasswordAuthentication yes
PermitRootLogin no
# Push current DHCP DNS into systemd-resolved (for dhcpcd/dhclient when NM doesn't feed resolved).
# With no args: discover DNS from lease or resolvectl and push to resolved for default IF.
# NetworkManager feeds resolved automatically; this covers first boot and non-NM setups.
- path: /usr/local/bin/update-resolv-from-dhcp.sh
content: |
#!/bin/sh
# Push DHCP DNS to systemd-resolved so resolv.conf (stub) uses it.
IF="${IFACE:-$(ip -o -4 route show to default 2>/dev/null | awk '{print $5}' | head -1)}"
[ -z "$IF" ] && exit 0
DNS=""
if [ -s /run/systemd/resolve/resolv.conf ]; then
DNS=$(grep -E '^nameserver\s+' /run/systemd/resolve/resolv.conf | awk '{print $2}' | tr '\n' ' ')
fi
if [ -z "$DNS" ]; then
DNS=$(resolvectl dns "$IF" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | tr '\n' ' ')
fi
if [ -z "$DNS" ]; then
LEASE=$(ls /var/lib/dhcp/dhclient.*.leases 2>/dev/null | head -1)
[ -n "$LEASE" ] && DNS=$(grep -oP 'option domain-name-servers \K[^;]+' "$LEASE" 2>/dev/null | tr ',' '\n' | tr -d ' ' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | tr '\n' ' ')
fi
[ -n "$DNS" ] && resolvectl dns "$IF" $DNS
permissions: '0755'
# dhclient: feed systemd-resolved on every lease acquire/renew (DHCP provides new_domain_name_servers)
- path: /etc/dhcp/dhclient-exit-hooks.d/zzz-update-resolv-conf
content: |
#!/bin/sh
# Run by dhclient on exit; push DHCP DNS into systemd-resolved.
[ -z "$new_domain_name_servers" ] && exit 0
[ -z "$interface" ] && exit 0
resolvectl dns "$interface" $new_domain_name_servers
permissions: '0755'
# NetworkManager: resolved is fed by NM by default; this only runs our script as fallback (e.g. if resolved started late).
- path: /etc/NetworkManager/dispatcher.d/99-update-resolv-from-dhcp
content: |
#!/bin/sh
[ "$2" = "up" ] || [ "$2" = "dhcp4-change" ] || exit 0
export IFACE="$1"
/usr/local/bin/update-resolv-from-dhcp.sh
permissions: '0755'
# Tell NetworkManager to send DHCP DNS to systemd-resolved (so every DHCP update is applied).
- path: /etc/NetworkManager/conf.d/99-use-resolved.conf
content: |
[main]
dns=systemd-resolved
rc-manager=unmanaged
# Fallback: push DHCP DNS to resolved once when network is up (e.g. dhcpcd-only or first boot).
- path: /etc/systemd/system/update-resolv-from-dhcp.service
content: |
[Unit]
Description=Push DHCP DNS to systemd-resolved
After=network-online.target systemd-resolved.service
WantedBy=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/update-resolv-from-dhcp.sh
RemainAfterExit=yes
runcmd:
# Ensure hostname resolves (avoids "sudo: unable to resolve host" when meta-data sets hostname)
- |
H="$(hostname)"
grep -q "127.0.1.1.*$H" /etc/hosts || echo "127.0.1.1 $H" >> /etc/hosts
# Use systemd-resolved for DNS; /etc/resolv.conf -> stub so all lookups go through resolved (DHCP DNS applied by NM/hooks).
- systemctl enable systemd-resolved.service
- systemctl start systemd-resolved.service
- rm -f /etc/resolv.conf && ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
# Push current DHCP DNS into resolved once at first boot (in case NM hasn't applied yet).
- /usr/local/bin/update-resolv-from-dhcp.sh
- systemctl enable update-resolv-from-dhcp.service
- systemctl enable ssh
- systemctl start ssh
# Download and run bootstrap script (edit URL to match your file server)

View File

@@ -0,0 +1,152 @@
# Deploy CM4 eMMC Provisioning to a New Proxmox Instance
Step-by-step guide to deploy the provisioning service (host + LXC) on a **new** Proxmox server. For redeploy/update and troubleshooting, see [PROXMOX-LXC-DEPLOYMENT.md](PROXMOX-LXC-DEPLOYMENT.md).
---
## Prerequisites (before running the deploy script)
| Requirement | Details |
|-------------|---------|
| **Proxmox host** | A Proxmox VE node (new or existing) where you want the service. |
| **SSH as root** | You must be able to run `ssh root@YOUR_PROXMOX_HOST` with **key-based auth** (no password prompt). |
| **Proxmox storage** | At least one active storage (e.g. `local` or `local-lvm`). Check on the host: `pvesm status`. |
| **Host internet** (recommended) | Needed so the deploy script can download the Debian 12 LXC template (if missing), and install **usbboot** and **PiShrink** on the host. Without internet, deploy still runs but you must install usbboot and PiShrink manually later. |
**Optional (set before deploy):**
- `DEPLOY_ROOTFS_STORAGE=local-lvm` — Skip interactive storage choice when creating the LXC.
- `DEPLOY_LXC_ROOT_PASSWORD=yourpassword` — Set LXC root password and enable SSH.
- `DEPLOY_LXC_SSH_KEY=/path/to/pub` — Copy this key into the LXC (default: `~/.ssh/id_ed25519.pub` or `id_rsa.pub`).
- `CM4_BACKUPS_HOST_PATH=/mnt/storage/cm4-backups` — Store backups on this host path (create the directory on the host if needed).
- **Network (WAN/LAN):**
`DEPLOY_LXC_WAN_BRIDGE=vmbr0` (default), `DEPLOY_LXC_WAN_IP=dhcp` (default),
`DEPLOY_LXC_LAN_BRIDGE=vmbr1`, `DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24` — To add eth1 as provisioning LAN with a custom subnet.
---
## Step 1: Run the deploy script
From your **workstation** (where the repo is cloned), run:
```bash
cd /path/to/reTerminal\ DM4
./emmc-provisioning/scripts/deploy-to-proxmox.sh root@YOUR_PROXMOX_HOST
```
Replace `YOUR_PROXMOX_HOST` with the Proxmox hostname or IP (e.g. `10.20.30.40`).
**Example with options:**
```bash
DEPLOY_ROOTFS_STORAGE=local-lvm \
DEPLOY_LXC_ROOT_PASSWORD='YourSecurePassword' \
DEPLOY_LXC_LAN_BRIDGE=vmbr1 \
DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24 \
./emmc-provisioning/scripts/deploy-to-proxmox.sh root@10.20.30.40
```
- On **first run**, the script will ask you to choose LXC rootfs storage (unless `DEPLOY_ROOTFS_STORAGE` is set). It then creates the LXC, installs host scripts, udev, systemd units, and the dashboard in the LXC.
- The script prints the **LXC IP** at the end. Note it for the next steps (or get it with:
`ssh root@YOUR_PROXMOX_HOST "pct exec \$(pct list -no-header -output vmid,name | awk '\''\$2==\"cm4-provisioning\"{print \$1}'\'') -- hostname -I"`).
---
## Step 2: Install usbboot on the host (if host had no internet during deploy)
USB flash/backup needs **rpiboot** on the Proxmox **host**. If the deploy log said usbboot install failed or was skipped:
**From your workstation:**
```bash
scp emmc-provisioning/scripts/install-usbboot-on-host.sh root@YOUR_PROXMOX_HOST:/tmp/
ssh root@YOUR_PROXMOX_HOST "bash /tmp/install-usbboot-on-host.sh"
```
**Or on the Proxmox host** (if `/tmp/emmc-provisioning-deploy` is still there):
```bash
ssh root@YOUR_PROXMOX_HOST
bash /tmp/emmc-provisioning-deploy/scripts/install-usbboot-on-host.sh
```
---
## Step 3: Add a golden image (required for Deploy)
To **write** an image to a device (Deploy), the host must have a **golden image** at `/var/lib/cm4-provisioning/golden.img`. Backup (read from device) works without it.
**Option A — From the dashboard**
1. Open **http://&lt;LXC-IP&gt;:5000** (use the LXC IP from the deploy output).
2. Build a cloud-init image or upload/set an existing backup as golden (see dashboard Admin).
**Option B — Copy an image from your machine**
```bash
scp /path/to/your-golden.img root@YOUR_PROXMOX_HOST:/var/lib/cm4-provisioning/golden.img
```
---
## Step 4: (Optional) SSH into the LXC
If you set `DEPLOY_LXC_ROOT_PASSWORD` or had a default SSH key, you can already run:
```bash
ssh root@<LXC-IP>
```
Otherwise, enable root SSH and add your key:
```bash
./emmc-provisioning/scripts/setup-lxc-ssh.sh root@YOUR_PROXMOX_HOST
# Or with password: ROOT_PASSWORD='YourPassword' ./emmc-provisioning/scripts/setup-lxc-ssh.sh root@YOUR_PROXMOX_HOST ~/.ssh/id_ed25519.pub
```
---
## Step 5: (Optional) Network boot (DHCP + TFTP on eth1)
Only if you deployed with **`DEPLOY_LXC_LAN_BRIDGE`** (and optionally `DEPLOY_LXC_LAN_SUBNET`) and want to offer network boot to devices on that LAN:
```bash
./emmc-provisioning/scripts/setup-network-boot-on-lxc.sh root@<LXC-IP>
```
See [NETWORK-BOOT-LXC.md](NETWORK-BOOT-LXC.md) for details.
---
## Step 6: (Optional) Install PiShrink on the host
If the deploy log said PiShrink install failed (e.g. no internet), and you want **Shrink/Compress** in the dashboard to work:
```bash
ssh root@YOUR_PROXMOX_HOST "bash /tmp/emmc-provisioning-deploy/scripts/install-pishrink-on-host.sh"
```
Or from your machine (stream the script): use the same pattern as in [PROXMOX-LXC-DEPLOYMENT.md](PROXMOX-LXC-DEPLOYMENT.md) for `install-pishrink-on-host.sh`.
---
## Summary checklist
| Step | Action | Required? |
|------|--------|------------|
| 1 | Run `deploy-to-proxmox.sh root@YOUR_PROXMOX_HOST` | **Yes** |
| 2 | Install usbboot on host (if deploy couldnt) | For USB flash/backup |
| 3 | Add `golden.img` for Deploy | For Deploy only |
| 4 | SSH to LXC (or use setup-lxc-ssh.sh) | Optional |
| 5 | Run setup-network-boot-on-lxc.sh (if using eth1 LAN) | Optional |
| 6 | Install PiShrink on host (if deploy couldnt) | For Shrink/Compress |
**After deployment:**
- **Dashboard:** http://&lt;LXC-IP&gt;:5000
- **Golden image path (host and LXC):** `/var/lib/cm4-provisioning/golden.img`
- **Disable auto-flash:** `ssh root@YOUR_PROXMOX_HOST "rm /etc/cm4-provisioning/enabled"`
- **Enable again:** `ssh root@YOUR_PROXMOX_HOST "touch /etc/cm4-provisioning/enabled"`
Full reference: [PROXMOX-LXC-DEPLOYMENT.md](PROXMOX-LXC-DEPLOYMENT.md).

View File

@@ -1,4 +1,4 @@
# How to edit cloud-init files on the device before capturing the image
# How to edit cloud-init files on the device or on an image file
The cloud-init **NoCloud** files live on the **boot partition**. On the running device they are at:
@@ -77,6 +77,34 @@ They are owned by **root** and need **sudo** to edit.
---
## Method 3: Edit on an image file (.img or .img.xz)
To edit cloud-init **inside a disk image** (e.g. `gnss-bootstrap-20260223-215010.img.xz`) without booting the device:
1. **Run the helper script** (requires `sudo` for loop device and mount):
```bash
cd /path/to/reTerminal-DM4
./emmc-provisioning/scripts/edit-cloudinit-on-image.sh gnss-bootstrap-20260223-215010.img.xz
```
2. The script will:
- Decompress the `.img.xz` (if needed; allow ~3GB free space and a minute or two)
- Attach the image with `losetup` and mount the **boot** (FAT32) partition
- Open your `$EDITOR` (or `nano`) on `user-data`, `meta-data`, and `network-config`
- After you save and exit, unmount and recompress, **overwriting** the original `.img.xz`
3. **Back up the image** before running if you want to keep the original:
```bash
cp gnss-bootstrap-20260223-215010.img.xz gnss-bootstrap-20260223-215010.img.xz.bak
```
4. **Options:**
- `--no-recompress` — Leave the image decompressed after editing (do not overwrite the `.img.xz`).
- `--replace-with-repo` — Copy `user-data`, `meta-data`, and `network-config` from `emmc-provisioning/cloud-init/` into the image before opening the editor (useful to start from repo templates).
---
## What to edit (typical)
- **meta-data**

View File

@@ -48,7 +48,13 @@ The script will:
## Proxmox: adding eth1 to the LXC
If you create the container by hand or want a second interface:
Use the deploy script with **`DEPLOY_LXC_LAN_BRIDGE`** and **`DEPLOY_LXC_LAN_SUBNET`** so the LXC is created with eth1 (LAN) from the start:
```bash
DEPLOY_LXC_LAN_BRIDGE=vmbr1 DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24 ./emmc-provisioning/scripts/deploy-to-proxmox.sh root@YOUR_PROXMOX_HOST
```
Or add a second interface to an existing container by hand:
1. On the **Proxmox host**, add a second network device to the container, e.g.:
```bash
@@ -65,6 +71,22 @@ If you create the container by hand or want a second interface:
Your current LXC already has eth0 (10.130.60.141) and eth1 (10.20.50.1); the setup script only adds DHCP, TFTP, and NAT.
### Changing the LAN subnet
When you deploy with **`DEPLOY_LXC_LAN_SUBNET`** (e.g. `10.100.1.1/24`), the deploy script writes **`/opt/cm4-provisioning/lan-subnet.conf`** inside the LXC with `LAN_GW`, `LAN_CIDR`, and the DHCP range. All LXC services use this file:
- **dnsmasq** (DHCP range and, via the toggle script, TFTP next-server)
- **nftables/iptables** (NAT source subnet)
- **toggle-network-boot-dhcp.sh** (option 66/67 next-server)
So changing `DEPLOY_LXC_LAN_SUBNET` and **re-running the deploy script** updates `lan-subnet.conf`. To apply the new subnet to dnsmasq and NAT, **re-run the setup script** after redeploying:
```bash
./emmc-provisioning/scripts/setup-network-boot-on-lxc.sh root@<LXC-IP>
```
Then run **toggle enable** again if you use network boot: `ssh root@<LXC-IP> /opt/cm4-provisioning/toggle-network-boot-dhcp.sh enable`
## After setup: reTerminal network boot
1. Set the reTerminal **boot order** to try eMMC first, then network (e.g. `BOOT_ORDER=0xf21`): use the dashboard **Update EEPROM** when the device is connected via USB boot, or set manually (usbboot recovery / `rpi-eeprom-config` on device). Not set by first-boot.

View File

@@ -2,6 +2,8 @@
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.
**New deployment:** For a clear step-by-step guide to deploy on a **new** Proxmox instance, see **[DEPLOY-NEW-PROXMOX.md](DEPLOY-NEW-PROXMOX.md)**.
## One-command deploy
From your repo, a single run deploys **all** host and LXC files (scripts, systemd units, udev, dashboard):
@@ -12,6 +14,8 @@ From your repo, a single run deploys **all** host and LXC files (scripts, system
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>`).
**Network (WAN / LAN):** `DEPLOY_LXC_WAN_BRIDGE=vmbr0` (default), `DEPLOY_LXC_WAN_IP=dhcp` (default or e.g. `192.168.1.10/24`), `DEPLOY_LXC_LAN_BRIDGE=vmbr1` (if set, add eth1 as LAN), `DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24` (LXC IP on LAN; default `10.20.50.1/24` when LAN bridge is set). Example: `DEPLOY_LXC_WAN_BRIDGE=vmbr0 DEPLOY_LXC_LAN_BRIDGE=vmbr1 DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24 ./scripts/deploy-to-proxmox.sh root@host`.
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

View File

@@ -2,10 +2,12 @@
Config files for the **provisioning LXC** when using **eth1** as a provisioning LAN (DHCP + TFTP for network boot, NAT for internet).
**LAN subnet:** When you deploy with `DEPLOY_LXC_LAN_SUBNET` (e.g. `10.100.1.1/24`), the deploy script writes `/opt/cm4-provisioning/lan-subnet.conf` inside the LXC with `LAN_GW`, `LAN_CIDR`, and `DHCP_RANGE_START`/`DHCP_RANGE_END`. The setup script and toggle script read this file so dnsmasq, NAT, and PXE options all use the same subnet. If the file is missing, defaults are `10.20.50.1/24` and `10.20.50.100``10.20.50.200`.
| File | Purpose |
|------|--------|
| **dnsmasq-network-boot.conf** | dnsmasq: DHCP + TFTP on eth1 only. Copied to `/etc/dnsmasq.d/` by `scripts/setup-network-boot-on-lxc.sh`. |
| **nft-nat-lan.conf** | nftables NAT so 10.20.50.0/24 uses eth0 for internet. Applied by the setup script to `/etc/nftables.d/nat-lan.conf`. |
| **dnsmasq-network-boot.conf** | Template: dnsmasq DHCP + TFTP on eth1. Setup script writes `/etc/dnsmasq.d/network-boot.conf` using values from `lan-subnet.conf`. |
| **nft-nat-lan.conf** | Template: nftables NAT for LAN→WAN. Setup script writes `/etc/nftables.d/nat-lan.conf` using `LAN_CIDR` from `lan-subnet.conf`. |
Setup is done by running (from your machine):

View File

@@ -1,5 +1,6 @@
# PXE/network-boot DHCP options (option 66 = next-server, 67 = boot file).
# When this file is present, dnsmasq advertises network boot; when removed, devices get DHCP only and boot from local storage.
# Toggle with: /opt/cm4-provisioning/toggle-network-boot-dhcp.sh enable|disable
# Template; toggle script writes the real next-server from /opt/cm4-provisioning/lan-subnet.conf (LAN_GW).
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf

View File

@@ -1,12 +1,14 @@
# dnsmasq: DHCP + TFTP on eth1 only (provisioning LAN).
# Install to /etc/dnsmasq.d/network-boot.conf on the LXC.
# Restrict to eth1 so we don't interfere with host/other DHCP.
# When using setup-network-boot-on-lxc.sh, the actual subnet and DHCP range
# come from /opt/cm4-provisioning/lan-subnet.conf (written by deploy-to-proxmox.sh).
# Listen only on eth1 (provisioning LAN)
interface=eth1
bind-interfaces
# DHCP range for devices on eth1 (adjust if you use a different subnet)
# DHCP range for devices on eth1 (template; setup script uses lan-subnet.conf)
dhcp-range=10.20.50.100,10.20.50.200,12h
# TFTP for Raspberry Pi / CM4 network boot

View File

@@ -1,6 +1,6 @@
# nftables: NAT for LAN (eth1) so clients use WAN (eth0) for internet.
# Load with: nft -f /etc/nftables.d/nat-lan.conf
# Or use the inline rules in setup-network-boot-on-lxc.sh (no separate file dependency).
# When using setup-network-boot-on-lxc.sh, the subnet is taken from /opt/cm4-provisioning/lan-subnet.conf (LAN_CIDR).
table ip nat {
chain postrouting {

View File

@@ -3,11 +3,19 @@
# When disabled, TFTP is stopped and no boot server is advertised; DHCP still runs.
# Usage: toggle-network-boot-dhcp.sh enable | disable | status
# Run as root. Install to /opt/cm4-provisioning/toggle-network-boot-dhcp.sh
# LAN gateway for TFTP/next-server is read from /opt/cm4-provisioning/lan-subnet.conf (written by deploy-to-proxmox.sh).
set -e
PXE_CONF="/etc/dnsmasq.d/network-boot-pxe.conf"
MAIN_CONF="/etc/dnsmasq.d/network-boot.conf"
LAN_CONF="/opt/cm4-provisioning/lan-subnet.conf"
if [[ -f "$LAN_CONF" ]]; then
source "$LAN_CONF"
else
LAN_GW="10.20.50.1"
fi
# Remove enable-tftp / tftp-root from main config if present (legacy; these belong in PXE conf)
cleanup_main_conf() {
if [ -f "$MAIN_CONF" ] && grep -q 'enable-tftp\|tftp-root' "$MAIN_CONF" 2>/dev/null; then
@@ -18,19 +26,19 @@ cleanup_main_conf() {
case "${1:-}" in
enable)
cleanup_main_conf
cat > "$PXE_CONF" << 'EOF'
cat > "$PXE_CONF" << EOF
# PXE/network boot ENABLED - managed by toggle-network-boot-dhcp.sh
# TFTP server (only active when network boot is enabled)
enable-tftp
tftp-root=/srv/tftpboot
# BOOTP fields (siaddr = TFTP server, filename = boot file)
dhcp-boot=start4cd.elf,,10.20.50.1
dhcp-boot=start4cd.elf,,${LAN_GW}
# DHCP options 66/67 (some PXE clients prefer these)
dhcp-option=66,10.20.50.1
dhcp-option=66,${LAN_GW}
dhcp-option=67,start4cd.elf
EOF
systemctl restart dnsmasq 2>/dev/null || service dnsmasq restart 2>/dev/null || true
echo "Network boot enabled."
echo "Network boot enabled (TFTP next-server: $LAN_GW)."
;;
disable)
cleanup_main_conf

View File

@@ -23,6 +23,12 @@
# DEPLOY_LXC_ROOT_PASSWORD=secret — set root password in LXC and enable SSH
# DEPLOY_LXC_SSH_KEY=/path/to/pub — copy this key to LXC root (default: ~/.ssh/id_ed25519.pub or id_rsa.pub)
# DEPLOY_LOG=1 — also log to deploy-YYYYMMDD-HHMMSS.log
# DEPLOY_LXC_WAN_BRIDGE=vmbr0 — Proxmox bridge for WAN (eth0); default vmbr0
# DEPLOY_LXC_WAN_IP=dhcp — WAN address: dhcp (default) or static e.g. 192.168.1.10/24
# DEPLOY_LXC_LAN_BRIDGE=vmbr1 — If set, add eth1 as LAN on this bridge (e.g. provisioning / network-boot)
# DEPLOY_LXC_LAN_SUBNET=10.20.50.1/24 — LXC IP on LAN (gateway); used only if DEPLOY_LXC_LAN_BRIDGE is set; default 10.20.50.1/24
#
# Legacy: DEPLOY_LXC_NET1="name=eth1,bridge=vmbr1,ip=10.20.50.1/24" still works; overridden by DEPLOY_LXC_LAN_BRIDGE + DEPLOY_LXC_LAN_SUBNET if both are set.
#
# Requires: ssh key access to root@<host>. For full install (usbboot, PiShrink), host needs internet.
@@ -144,8 +150,8 @@ rsync -a "$REPO_DIR/" "$PROXMOX:/tmp/emmc-provisioning-deploy/" --exclude='.git'
log "[4/5] Running remote install (host + LXC) ..."
# Pass optional LXC SSH vars (base64) and selected storage
ssh "$PROXMOX" "ROOTFS_STORAGE='$ROOTFS_STORAGE' CM4_BACKUPS_HOST_PATH='${CM4_BACKUPS_HOST_PATH:-}' DEPLOY_SSH_KEY_B64='${DEPLOY_SSH_KEY_B64:-}' DEPLOY_LXC_PWD_B64='${DEPLOY_LXC_PWD_B64:-}'" bash -s << 'REMOTE'
# Pass optional LXC SSH vars (base64), selected storage, and network (WAN/LAN bridge + subnet)
ssh "$PROXMOX" "ROOTFS_STORAGE='$ROOTFS_STORAGE' CM4_BACKUPS_HOST_PATH='${CM4_BACKUPS_HOST_PATH:-}' DEPLOY_SSH_KEY_B64='${DEPLOY_SSH_KEY_B64:-}' DEPLOY_LXC_PWD_B64='${DEPLOY_LXC_PWD_B64:-}' DEPLOY_LXC_WAN_BRIDGE='${DEPLOY_LXC_WAN_BRIDGE:-}' DEPLOY_LXC_WAN_IP='${DEPLOY_LXC_WAN_IP:-}' DEPLOY_LXC_LAN_BRIDGE='${DEPLOY_LXC_LAN_BRIDGE:-}' DEPLOY_LXC_LAN_SUBNET='${DEPLOY_LXC_LAN_SUBNET:-}' DEPLOY_LXC_NET1='${DEPLOY_LXC_NET1:-}'" bash -s << 'REMOTE'
set -e
DEPLOY=/tmp/emmc-provisioning-deploy
ROOTFS_STORAGE="${ROOTFS_STORAGE:?ROOTFS_STORAGE not set}"
@@ -185,14 +191,24 @@ else
fi
[[ -z "$DEBIAN12_TMPL" ]] && { log "Error: no Debian 12 template found"; exit 1; }
TMPL_NAME=$(basename "$DEBIAN12_TMPL")
# Optional: add eth1 for network-boot LAN (DHCP+TFTP). Set DEPLOY_LXC_NET1 e.g. "name=eth1,bridge=vmbr1,ip=10.20.50.1/24"
# WAN (eth0): bridge and IP from env; default vmbr0 + dhcp
WAN_BRIDGE="${DEPLOY_LXC_WAN_BRIDGE:-vmbr0}"
WAN_IP="${DEPLOY_LXC_WAN_IP:-dhcp}"
# LAN (eth1): optional; use DEPLOY_LXC_LAN_BRIDGE + DEPLOY_LXC_LAN_SUBNET, or legacy DEPLOY_LXC_NET1
NET1_OPT=""
if [[ -n "${DEPLOY_LXC_NET1:-}" ]]; then
if [[ -n "${DEPLOY_LXC_LAN_BRIDGE:-}" ]]; then
LAN_SUBNET="${DEPLOY_LXC_LAN_SUBNET:-10.20.50.1/24}"
NET1_OPT="--net1 name=eth1,bridge=${DEPLOY_LXC_LAN_BRIDGE},ip=${LAN_SUBNET}"
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP; eth1 LAN bridge=$DEPLOY_LXC_LAN_BRIDGE ip=$LAN_SUBNET"
elif [[ -n "${DEPLOY_LXC_NET1:-}" ]]; then
NET1_OPT="--net1 $DEPLOY_LXC_NET1"
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP; eth1 from DEPLOY_LXC_NET1"
else
log "LXC network: eth0 WAN bridge=$WAN_BRIDGE ip=$WAN_IP (no LAN interface)"
fi
pct create "$CTID" "local:vztmpl/${TMPL_NAME}" \
--hostname "$LXC_HOSTNAME" --memory 1024 --swap 0 --cores 1 \
--rootfs "${ROOTFS_STORAGE}:8" --net0 name=eth0,bridge=vmbr0,ip=dhcp $NET1_OPT \
--rootfs "${ROOTFS_STORAGE}:8" --net0 name=eth0,bridge="$WAN_BRIDGE",ip="$WAN_IP" $NET1_OPT \
--unprivileged 0 --features nesting=1 -tag cm4-provisioning
mkdir -p /var/lib/cm4-provisioning
pct set "$CTID" -mp0 /var/lib/cm4-provisioning,mp=/var/lib/cm4-provisioning
@@ -302,6 +318,24 @@ fi
log "Starting LXC $CTID if stopped ..."
pct start "$CTID" 2>/dev/null || true
# --- LXC: write lan-subnet.conf when LAN bridge/subnet is set (so dnsmasq/NAT/toggle use same subnet) ---
LAN_SUBNET_FOR_CONF="${DEPLOY_LXC_LAN_SUBNET:-}"
[[ -z "$LAN_SUBNET_FOR_CONF" && -n "${DEPLOY_LXC_LAN_BRIDGE:-}" ]] && LAN_SUBNET_FOR_CONF="10.20.50.1/24"
if [[ -n "$LAN_SUBNET_FOR_CONF" ]]; then
if [[ "$LAN_SUBNET_FOR_CONF" =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/([0-9]+)$ ]]; then
LAN_GW="${BASH_REMATCH[1]}"
PREFIX="${BASH_REMATCH[2]}"
BASE_3="${LAN_GW%.*}"
LAN_CIDR="${BASE_3}.0/${PREFIX}"
DHCP_RANGE_START="${BASE_3}.100"
DHCP_RANGE_END="${BASE_3}.200"
pct exec "$CTID" -- bash -c "mkdir -p /opt/cm4-provisioning && echo 'LAN_GW=$LAN_GW' > /opt/cm4-provisioning/lan-subnet.conf && echo 'LAN_CIDR=$LAN_CIDR' >> /opt/cm4-provisioning/lan-subnet.conf && echo 'DHCP_RANGE_START=$DHCP_RANGE_START' >> /opt/cm4-provisioning/lan-subnet.conf && echo 'DHCP_RANGE_END=$DHCP_RANGE_END' >> /opt/cm4-provisioning/lan-subnet.conf"
log "LXC: wrote /opt/cm4-provisioning/lan-subnet.conf (LAN_GW=$LAN_GW, LAN_CIDR=$LAN_CIDR, DHCP ${DHCP_RANGE_START}-${DHCP_RANGE_END})"
else
log "Warning: DEPLOY_LXC_LAN_SUBNET=$LAN_SUBNET_FOR_CONF not in form A.B.C.D/PREFIX; skipping lan-subnet.conf"
fi
fi
# --- LXC: flash scripts (for reference; actual flash runs on host) ---
log "LXC: installing flash scripts ..."
pct exec "$CTID" -- mkdir -p /opt/cm4-provisioning /etc/cm4-provisioning

View File

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

View File

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