Add DHCP network boot management to API and UI

Enhance the dashboard API with new endpoints for managing DHCP network boot options, allowing devices to enable or disable network boot via POST requests. Update the device action handling to include a 'reboot' action, specifically for network devices. Modify the home.html template to display the current state of network boot and provide a button for disabling it. Update provisioning scripts to disable network boot after deployment or backup completion, ensuring devices boot from eMMC on the next startup. Improve user feedback and error handling throughout the changes.
This commit is contained in:
nearxos
2026-02-20 17:05:38 +02:00
parent 66ad3b0a39
commit 7e1bf8a4c2
11 changed files with 165 additions and 23 deletions

View File

@@ -10,6 +10,7 @@ import os
import re import re
import shutil import shutil
import sqlite3 import sqlite3
import subprocess
import time import time
import urllib.request import urllib.request
from functools import wraps from functools import wraps
@@ -40,6 +41,7 @@ SHRINK_STATUS_FILE = Path(os.environ.get("CM4_SHRINK_STATUS_FILE", str(BASE_DIR
CLOUDINIT_TEMPLATES_FILE = Path(os.environ.get("CM4_CLOUDINIT_TEMPLATES_FILE", str(BASE_DIR / "cloudinit_templates.json"))) CLOUDINIT_TEMPLATES_FILE = Path(os.environ.get("CM4_CLOUDINIT_TEMPLATES_FILE", str(BASE_DIR / "cloudinit_templates.json")))
PORTAL_DESCRIPTIONS_FILE = Path(os.environ.get("CM4_PORTAL_DESCRIPTIONS_FILE", str(BASE_DIR / "portal_descriptions.json"))) PORTAL_DESCRIPTIONS_FILE = Path(os.environ.get("CM4_PORTAL_DESCRIPTIONS_FILE", str(BASE_DIR / "portal_descriptions.json")))
DB_PATH = Path(os.environ.get("CM4_DASHBOARD_DB", str(BASE_DIR / "dashboard.db"))) DB_PATH = Path(os.environ.get("CM4_DASHBOARD_DB", str(BASE_DIR / "dashboard.db")))
TOGGLE_NETWORK_BOOT_SCRIPT = os.environ.get("CM4_TOGGLE_NETWORK_BOOT_SCRIPT", "/opt/cm4-provisioning/toggle-network-boot-dhcp.sh")
# --- Database (admin users + activity logs) --- # --- Database (admin users + activity logs) ---
@@ -489,8 +491,10 @@ def api_device_action():
body = request.get_json(force=True, silent=True) or {} body = request.get_json(force=True, silent=True) or {}
source = (body.get("source") or "").strip().lower() source = (body.get("source") or "").strip().lower()
action = (body.get("action") or "").strip().lower() action = (body.get("action") or "").strip().lower()
if action not in ("backup", "deploy"): if action not in ("backup", "deploy", "reboot"):
return jsonify({"ok": False, "error": "action must be 'backup' or 'deploy'"}), 400 return jsonify({"ok": False, "error": "action must be 'backup', 'deploy', or 'reboot'"}), 400
if action == "reboot" and source != "network":
return jsonify({"ok": False, "error": "'reboot' is only for network devices"}), 400
if source == "usb": if source == "usb":
try: try:
os.makedirs(os.path.dirname(ACTION_REQUEST_FILE) or ".", exist_ok=True) os.makedirs(os.path.dirname(ACTION_REQUEST_FILE) or ".", exist_ok=True)
@@ -543,6 +547,59 @@ def api_register_device():
return jsonify({"ok": True, "message": "registered"}) return jsonify({"ok": True, "message": "registered"})
def _dhcp_network_boot_run(cmd):
"""Run toggle script with enable|disable|status. Returns (ok, output_or_error)."""
if not os.path.isfile(TOGGLE_NETWORK_BOOT_SCRIPT) or not os.access(TOGGLE_NETWORK_BOOT_SCRIPT, os.X_OK):
return False, "Toggle script not installed"
try:
out = subprocess.run(
[TOGGLE_NETWORK_BOOT_SCRIPT, cmd],
capture_output=True,
text=True,
timeout=10,
)
if out.returncode != 0:
return False, (out.stderr or out.stdout or "script failed").strip()
return True, (out.stdout or "").strip()
except subprocess.TimeoutExpired:
return False, "Timeout"
except Exception as e:
return False, str(e)
@app.route("/api/dhcp-network-boot", methods=["GET"])
def api_dhcp_network_boot_get():
"""Return whether DHCP network-boot options (66/67) are enabled."""
ok, out = _dhcp_network_boot_run("status")
if not ok:
return jsonify({"enabled": None, "error": out}), 200
return jsonify({"enabled": out.strip().lower() == "enabled"})
@app.route("/api/dhcp-network-boot", methods=["POST"])
def api_dhcp_network_boot_post():
"""Enable or disable DHCP network-boot options (DHCP server keeps running). Body: { \"enabled\": true|false }."""
body = request.get_json(force=True, silent=True) or {}
enabled = body.get("enabled")
if enabled is None:
return jsonify({"ok": False, "error": "enabled required (true|false)"}), 400
cmd = "enable" if enabled else "disable"
ok, out = _dhcp_network_boot_run(cmd)
if not ok:
return jsonify({"ok": False, "error": out}), 500
return jsonify({"ok": True, "enabled": enabled})
@app.route("/api/action-done", methods=["POST"])
def api_action_done():
"""Called by a device when deploy or backup has completed. Disables DHCP network-boot so the device boots from eMMC next time."""
mac = (request.args.get("mac") or request.get_json(silent=True) or {}).get("mac", "")
ok, _ = _dhcp_network_boot_run("disable")
if not ok:
return jsonify({"ok": False, "error": "Could not disable DHCP network boot"}), 500
return jsonify({"ok": True, "message": "Network boot disabled; device will boot from eMMC on next boot"})
@app.route("/api/device-action-poll") @app.route("/api/device-action-poll")
def api_device_action_poll(): def api_device_action_poll():
"""Network device polls this to get its assigned action (deploy/backup) and URL.""" """Network device polls this to get its assigned action (deploy/backup) and URL."""
@@ -558,6 +615,8 @@ def api_device_action_poll():
return jsonify({"action": "deploy", "url": f"{base}/api/golden-image"}) return jsonify({"action": "deploy", "url": f"{base}/api/golden-image"})
if action == "backup": if action == "backup":
return jsonify({"action": "backup", "upload_url": f"{base}/api/backup-upload?mac={mac}"}) return jsonify({"action": "backup", "upload_url": f"{base}/api/backup-upload?mac={mac}"})
if action == "reboot":
return jsonify({"action": "reboot"})
return jsonify({"action": "wait"}) return jsonify({"action": "wait"})
return jsonify({"action": "wait"}) return jsonify({"action": "wait"})

View File

@@ -104,6 +104,7 @@
<div class="card"> <div class="card">
<h2 class="card-title">Capture or deploy</h2> <h2 class="card-title">Capture or deploy</h2>
<p id="dhcpNetbootWrap" class="status-row" style="margin-bottom: 0.5rem; font-size: 0.85rem;"><span class="text-dim">Network boot (DHCP):</span> <span id="dhcpNetbootState"></span> <button type="button" id="dhcpNetbootDisableBtn" class="btn btn-outline btn-sm" style="display:none;">Disable network boot</button></p>
<p id="shrinkOptionWrap" style="display:none; margin-bottom: 0.5rem; font-size: 0.8rem;"><label><input type="checkbox" id="shrinkAfterBackup" /> Shrink after backup</label></p> <p id="shrinkOptionWrap" style="display:none; margin-bottom: 0.5rem; font-size: 0.8rem;"><label><input type="checkbox" id="shrinkAfterBackup" /> Shrink after backup</label></p>
<div id="pendingDevices"></div> <div id="pendingDevices"></div>
<p id="noPending" class="empty-msg" style="display:none;">No device connected. Use USB boot mode or register over network.</p> <p id="noPending" class="empty-msg" style="display:none;">No device connected. Use USB boot mode or register over network.</p>
@@ -182,7 +183,7 @@
hasAny = true; hasAny = true;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'device-item'; el.className = 'device-item';
el.innerHTML = '<div class="device-desc">' + escapeHtml(d.ip || '') + ' · ' + escapeHtml(d.mac || '') + '</div><div><button type="button" class="btn btn-outline btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="backup">Backup</button> <button type="button" class="btn btn-primary btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="deploy">Deploy</button></div>'; el.innerHTML = '<div class="device-desc">' + escapeHtml(d.ip || '') + ' · ' + escapeHtml(d.mac || '') + '</div><div><button type="button" class="btn btn-outline btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="backup">Backup</button> <button type="button" class="btn btn-primary btn-sm" data-source="network" data-mac="' + escapeHtml(d.mac || '') + '" data-action="deploy">Deploy</button> <button type="button" class="btn btn-outline btn-sm btn-disable-netboot" title="Stop advertising network boot via DHCP so devices boot from eMMC">Disable network boot</button></div>';
container.appendChild(el); container.appendChild(el);
}); });
noPending.style.display = hasAny ? 'none' : 'block'; noPending.style.display = hasAny ? 'none' : 'block';
@@ -193,7 +194,15 @@
if (body.action === 'backup' && document.getElementById('shrinkAfterBackup').checked) body.shrink = true; if (body.action === 'backup' && document.getElementById('shrinkAfterBackup').checked) body.shrink = true;
fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(data) { if (data.ok) { fetchPending(); fetchStatus(); } else alert(data.error || 'Failed'); }) .then(function(data) { if (data.ok) { fetchPending(); fetchStatus(); fetchDhcpNetboot(); } else alert(data.error || 'Failed'); })
.catch(function() { alert('Request failed'); });
};
});
container.querySelectorAll('button.btn-disable-netboot').forEach(function(btn) {
btn.onclick = function() {
fetch('/api/dhcp-network-boot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: false }) })
.then(function(r) { return r.json(); })
.then(function(data) { if (data.ok) { fetchDhcpNetboot(); } else alert(data.error || 'Failed'); })
.catch(function() { alert('Request failed'); }); .catch(function() { alert('Request failed'); });
}; };
}); });
@@ -213,12 +222,28 @@
el.innerHTML = t; el.innerHTML = t;
}).catch(function(){ document.getElementById('goldenInfo').textContent = 'Could not load.'; }); }).catch(function(){ document.getElementById('goldenInfo').textContent = 'Could not load.'; });
} }
function fetchDhcpNetboot() {
fetch('/api/dhcp-network-boot').then(function(r){ return r.json(); }).then(function(d){
const stateEl = document.getElementById('dhcpNetbootState');
const btn = document.getElementById('dhcpNetbootDisableBtn');
if (d.error) { stateEl.textContent = '—'; if (btn) btn.style.display = 'none'; return; }
stateEl.textContent = d.enabled ? 'on' : 'off';
if (btn) btn.style.display = d.enabled ? 'inline-block' : 'none';
}).catch(function(){ document.getElementById('dhcpNetbootState').textContent = '—'; });
}
document.getElementById('statusClearBtn').addEventListener('click', function(){ fetch('/api/status-clear', { method: 'POST' }).then(function(r){ return r.json(); }).then(function(d){ if(d.ok) fetchStatus(); }); }); document.getElementById('statusClearBtn').addEventListener('click', function(){ fetch('/api/status-clear', { method: 'POST' }).then(function(r){ return r.json(); }).then(function(d){ if(d.ok) fetchStatus(); }); });
fetchStatus(); fetchLog(); fetchPending(); fetchGolden(); document.getElementById('dhcpNetbootDisableBtn').addEventListener('click', function(){
fetch('/api/dhcp-network-boot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: false }) })
.then(function(r){ return r.json(); })
.then(function(d){ if(d.ok) fetchDhcpNetboot(); else alert(d.error || 'Failed'); })
.catch(function(){ alert('Request failed'); });
});
fetchStatus(); fetchLog(); fetchPending(); fetchGolden(); fetchDhcpNetboot();
setInterval(fetchStatus, 2000); setInterval(fetchStatus, 2000);
setInterval(fetchLog, 4000); setInterval(fetchLog, 4000);
setInterval(fetchPending, 2000); setInterval(fetchPending, 2000);
setInterval(fetchGolden, 10000); setInterval(fetchGolden, 10000);
setInterval(fetchDhcpNetboot, 10000);
</script> </script>
</body> </body>
</html> </html>

View File

@@ -67,8 +67,9 @@ So the “netboot environment” is either:
- You open the dashboard at `http://10.20.50.1:5000` (or the LXCs WAN IP if youre not on the provisioning LAN). - You open the dashboard at `http://10.20.50.1:5000` (or the LXCs WAN IP if youre not on the provisioning LAN).
- Under **“Device detected (Network)”** you see the device (identified by MAC). - Under **“Device detected (Network)”** you see the device (identified by MAC).
- You click **Deploy** or **Backup**. - You click **Deploy**, **Backup**, or **Disable network boot**.
- The dashboard sets the **action** (and URL/upload_url) for that MAC; the next **device-action-poll** returns it, and the client runs the corresponding dd + curl. - **Deploy** / **Backup**: the dashboard sets the action and URL; the client runs dd + curl, then calls **/api/action-done**, which **disables DHCP network-boot options** on the LXC so the device will boot from eMMC on the next reboot. No need to unplug ethernet.
- **Disable network boot**: turns off DHCP options 66/67 (next-server, boot file) on the LXC. The DHCP server keeps running; devices just stop receiving netboot and will boot from local storage (eMMC) next time. Use this when you don't want to deploy or backup; the netbooted device can then reboot and boot from eMMC.
--- ---
@@ -83,13 +84,14 @@ So the “netboot environment” is either:
| Your choice | You → LXC | In dashboard: click Deploy or Backup for that device. | | Your choice | You → LXC | In dashboard: click Deploy or Backup for that device. |
| Deploy | LXC → device | Client GETs image URL, streams to `dd of=/dev/mmcblk0`. | | Deploy | LXC → device | Client GETs image URL, streams to `dd of=/dev/mmcblk0`. |
| Backup | Device → LXC | Client `dd if=/dev/mmcblk0` and POSTs to upload_url. | | Backup | Device → LXC | Client `dd if=/dev/mmcblk0` and POSTs to upload_url. |
| After | reTerminal | Reboot; if you deployed, it can now boot from eMMC. | | After | Device → LXC | Client calls **POST /api/action-done**; server disables DHCP netboot options. |
| After | reTerminal | Reboot; device boots from eMMC (no netboot advertised). |
--- ---
## What you need in place ## What you need in place
- **LXC**: eth1 = 10.20.50.1/24, dnsmasq (DHCP + TFTP on eth1), `/srv/tftpboot` with RPi 4 boot files, NAT for 10.20.50.0/24 via eth0. Dashboard running, `golden.img` present for Deploy. - **LXC**: eth1 = 10.20.50.1/24, dnsmasq (DHCP + TFTP on eth1; netboot options 66/67 in a separate snippet so they can be toggled), `/srv/tftpboot` with RPi 4 boot files, NAT for 10.20.50.0/24 via eth0. Toggle script **/opt/cm4-provisioning/toggle-network-boot-dhcp.sh** (enable/disable/status). Dashboard running, `golden.img` present for Deploy.
See **NETWORK-BOOT-LXC.md** and **setup-network-boot-on-lxc.sh**. See **NETWORK-BOOT-LXC.md** and **setup-network-boot-on-lxc.sh**.
- **reTerminal**: EEPROM boot order = network first; Ethernet on 10.20.50.0/24; netboot environment that runs **provisioning-client.sh** with `PROVISIONING_SERVER=http://10.20.50.1:5000`. - **reTerminal**: EEPROM boot order = network first; Ethernet on 10.20.50.0/24; netboot environment that runs **provisioning-client.sh** with `PROVISIONING_SERVER=http://10.20.50.1:5000`.
- **Netboot root**: Must provide network, curl, and the client script (NFS, initramfs, or custom root). - **Netboot root**: Must provide network, curl, and the client script (NFS, initramfs, or custom root).

View File

@@ -0,0 +1,5 @@
# 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
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf

View File

@@ -12,11 +12,7 @@ dhcp-range=10.20.50.100,10.20.50.200,12h
# TFTP for Raspberry Pi / CM4 network boot # TFTP for Raspberry Pi / CM4 network boot
enable-tftp enable-tftp
tftp-root=/srv/tftpboot tftp-root=/srv/tftpboot
# PXE options (66/67) are in network-boot-pxe.conf; remove that file to disable netboot advertising
# RPi 4 netboot: next-server is this host; boot filename (Pi firmware uses this)
# Option 66 = next-server (TFTP), 67 = boot filename
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf
# Logging (optional; disable in production if too noisy) # Logging (optional; disable in production if too noisy)
log-dhcp log-dhcp

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Enable or disable DHCP network-boot options (option 66/67) on the provisioning LXC.
# Does not stop the DHCP server or TFTP; only stops advertising netboot so devices boot from local storage.
# Usage: toggle-network-boot-dhcp.sh enable | disable
# Run as root (or with sudo). Install to /opt/cm4-provisioning/toggle-network-boot-dhcp.sh
set -e
PXE_CONF="/etc/dnsmasq.d/network-boot-pxe.conf"
SNIPPET_CONTENT="# PXE options - do not edit; managed by toggle-network-boot-dhcp.sh
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf
"
case "${1:-}" in
enable)
echo "$SNIPPET_CONTENT" > "$PXE_CONF"
systemctl reload dnsmasq 2>/dev/null || service dnsmasq reload 2>/dev/null || true
echo "Network boot (DHCP options) enabled."
;;
disable)
rm -f "$PXE_CONF"
systemctl reload dnsmasq 2>/dev/null || service dnsmasq reload 2>/dev/null || true
echo "Network boot (DHCP options) disabled. Devices will get DHCP but boot from local storage."
;;
status)
if [ -f "$PXE_CONF" ]; then
echo "enabled"
else
echo "disabled"
fi
;;
*)
echo "Usage: $0 enable | disable | status" >&2
exit 1
;;
esac

View File

@@ -62,7 +62,7 @@ else
$CONTAINER_RUNTIME cp "$CNT_NAME:/out/bin/busybox" "$BUILD_DIR/bin/" 2>/dev/null && \ $CONTAINER_RUNTIME cp "$CNT_NAME:/out/bin/busybox" "$BUILD_DIR/bin/" 2>/dev/null && \
$CONTAINER_RUNTIME cp "$CNT_NAME:/out/usr/bin/curl" "$BUILD_DIR/usr/bin/" 2>/dev/null && \ $CONTAINER_RUNTIME cp "$CNT_NAME:/out/usr/bin/curl" "$BUILD_DIR/usr/bin/" 2>/dev/null && \
$CONTAINER_RUNTIME cp "$CNT_NAME:/out/lib/." "$BUILD_DIR/lib/" 2>/dev/null || true $CONTAINER_RUNTIME cp "$CNT_NAME:/out/lib/." "$BUILD_DIR/lib/" 2>/dev/null || true
$CONTAINER_RUNTIME rm -f "$CNT_NAME" 2>/dev/null $CONTAINER_RUNTIME rm -f "$CNT_NAME" >/dev/null 2>&1
if [ -f "$BUILD_DIR/bin/busybox" ] && [ -f "$BUILD_DIR/usr/bin/curl" ]; then if [ -f "$BUILD_DIR/bin/busybox" ] && [ -f "$BUILD_DIR/usr/bin/curl" ]; then
CONTAINER_OK=1 CONTAINER_OK=1
echo "Container build succeeded." echo "Container build succeeded."
@@ -147,7 +147,7 @@ chmod +x "$BUILD_DIR/bin/busybox" 2>/dev/null || true
cd "$BUILD_DIR/bin" cd "$BUILD_DIR/bin"
./busybox --list 2>/dev/null | while read applet; do ./busybox --list 2>/dev/null | while read applet; do
case "$applet" in case "$applet" in
sh|ash|mount|umount|mkdir|cat|ip|udhcpc|sleep|echo|grep|cut|awk|hostname|dd) ln -sf busybox "$applet"; ;; sh|ash|mount|umount|mkdir|cat|ip|udhcpc|sleep|echo|grep|cut|awk|hostname|dd|reboot) ln -sf busybox "$applet"; ;;
esac esac
done done
[ -e sh ] || ln -sf busybox sh [ -e sh ] || ln -sf busybox sh

Binary file not shown.

View File

@@ -38,7 +38,8 @@ while true; do
continue continue
fi fi
curl -sL "$url" | dd of="$EMMC_DEV" bs=4M status=progress conv=fsync curl -sL "$url" | dd of="$EMMC_DEV" bs=4M status=progress conv=fsync
echo "Deploy done. Reboot to run from eMMC." echo "Deploy done. Disabling network boot on server so device boots from eMMC next time."
curl -s -X POST "$BASE_URL/api/action-done?mac=$MAC" || true
exit 0 exit 0
fi fi
@@ -50,7 +51,14 @@ while true; do
continue continue
fi fi
dd if="$EMMC_DEV" bs=4M status=progress 2>/dev/null | curl -s -X POST -T - "$upload_url" dd if="$EMMC_DEV" bs=4M status=progress 2>/dev/null | curl -s -X POST -T - "$upload_url"
echo "Backup done." echo "Backup done. Disabling network boot on server."
curl -s -X POST "$BASE_URL/api/action-done?mac=$MAC" || true
exit 0
fi
if [ "$action" = "reboot" ]; then
echo "Boot normally: rebooting..."
reboot -f 2>/dev/null || exec reboot 2>/dev/null || true
exit 0 exit 0
fi fi

View File

@@ -29,7 +29,8 @@ while true; do
continue continue
fi fi
curl -sL "$url" | dd of="$EMMC_DEV" bs=4M status=progress conv=fsync curl -sL "$url" | dd of="$EMMC_DEV" bs=4M status=progress conv=fsync
echo "Deploy done. Reboot to run from eMMC." echo "Deploy done. Disabling network boot on server so device boots from eMMC next time."
curl -s -X POST "$BASE_URL/api/action-done?mac=$MAC" || true
exit 0 exit 0
fi fi
@@ -41,9 +42,15 @@ while true; do
continue continue
fi fi
dd if="$EMMC_DEV" bs=4M status=progress 2>/dev/null | curl -s -X POST -T - "$upload_url" dd if="$EMMC_DEV" bs=4M status=progress 2>/dev/null | curl -s -X POST -T - "$upload_url"
echo "Backup done." echo "Backup done. Disabling network boot on server."
curl -s -X POST "$BASE_URL/api/action-done?mac=$MAC" || true
exit 0 exit 0
fi fi
if [[ "$action" == "reboot" ]]; then
echo "Boot normally: rebooting..."
reboot -f 2>/dev/null || exec reboot 2>/dev/null || exit 0
fi
sleep 5 sleep 5
done done

View File

@@ -26,7 +26,7 @@ if ! command -v dnsmasq >/dev/null 2>&1; then
apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq dnsmasq apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq dnsmasq
fi fi
# 2) dnsmasq config for eth1 only (DHCP + TFTP) # 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 mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/network-boot.conf << 'DNSMASQ' cat > /etc/dnsmasq.d/network-boot.conf << 'DNSMASQ'
# DHCP + TFTP on eth1 only (provisioning LAN) # DHCP + TFTP on eth1 only (provisioning LAN)
@@ -35,12 +35,16 @@ bind-interfaces
dhcp-range=10.20.50.100,10.20.50.200,12h dhcp-range=10.20.50.100,10.20.50.200,12h
enable-tftp enable-tftp
tftp-root=/srv/tftpboot tftp-root=/srv/tftpboot
dhcp-option=66,10.20.50.1
dhcp-option=67,start4cd.elf
log-dhcp log-dhcp
log-queries log-queries
port=0 port=0
DNSMASQ DNSMASQ
mkdir -p /opt/cm4-provisioning
if [ -f /tmp/cm4-network-boot-lxc/toggle-network-boot-dhcp.sh ]; then
cp /tmp/cm4-network-boot-lxc/toggle-network-boot-dhcp.sh /opt/cm4-provisioning/
chmod +x /opt/cm4-provisioning/toggle-network-boot-dhcp.sh
/opt/cm4-provisioning/toggle-network-boot-dhcp.sh enable
fi
# 3) TFTP root: fetch Raspberry Pi 4 boot files from GitHub if missing # 3) TFTP root: fetch Raspberry Pi 4 boot files from GitHub if missing
mkdir -p /srv/tftpboot mkdir -p /srv/tftpboot