From 16bfc1e0e1671c12ed79c1d0c30a97edf782b6d9 Mon Sep 17 00:00:00 2001 From: nearxos Date: Tue, 24 Feb 2026 08:50:32 +0200 Subject: [PATCH] Enhance cloud-init scripts and dashboard for improved USB boot functionality Update the bootstrap script to ensure hostname resolution by adding entries to /etc/hosts, preventing "sudo: unable to resolve host" errors. Modify user-data.bootstrap to include the same hostname resolution logic. Revise dashboard templates to reflect the new project name "GNSS Guard Provisioning" and improve user interface elements related to USB boot operations, including clearer instructions and status messages. These changes enhance the overall user experience and streamline the provisioning process. --- emmc-provisioning/cloud-init/bootstrap.sh | 5 ++ .../cloud-init/user-data.bootstrap | 4 ++ .../dashboard/templates/admin.html | 60 ++++++++++++++++++- .../dashboard/templates/cloudinit_build.html | 2 +- .../dashboard/templates/home.html | 31 +++++----- .../dashboard/templates/index.html | 35 +++++------ .../dashboard/templates/login.html | 4 +- .../dashboard/templates/portal_files.html | 2 +- .../docs/PROXMOX-LXC-DEPLOYMENT.md | 2 +- .../host/flash-emmc-on-connect.sh | 6 +- 10 files changed, 111 insertions(+), 40 deletions(-) diff --git a/emmc-provisioning/cloud-init/bootstrap.sh b/emmc-provisioning/cloud-init/bootstrap.sh index 5932280..f16cf8e 100644 --- a/emmc-provisioning/cloud-init/bootstrap.sh +++ b/emmc-provisioning/cloud-init/bootstrap.sh @@ -1,4 +1,9 @@ #!/bin/bash # Minimal bootstrap script for cloud-init first boot (test). set -e + +# Ensure hostname resolves (avoids "sudo: unable to resolve host") +H="$(hostname)" +grep -q "127.0.1.1.*$H" /etc/hosts || echo "127.0.1.1 $H" >> /etc/hosts + echo "[$(date -Iseconds)] test completed" | tee -a /var/log/cloud-init-bootstrap.log diff --git a/emmc-provisioning/cloud-init/user-data.bootstrap b/emmc-provisioning/cloud-init/user-data.bootstrap index 3941b73..6e7332b 100644 --- a/emmc-provisioning/cloud-init/user-data.bootstrap +++ b/emmc-provisioning/cloud-init/user-data.bootstrap @@ -20,6 +20,10 @@ write_files: PermitRootLogin no 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 - systemctl enable ssh - systemctl start ssh # Download and run bootstrap script (edit URL to match your file server) diff --git a/emmc-provisioning/dashboard/templates/admin.html b/emmc-provisioning/dashboard/templates/admin.html index a86292a..299236f 100644 --- a/emmc-provisioning/dashboard/templates/admin.html +++ b/emmc-provisioning/dashboard/templates/admin.html @@ -4,7 +4,7 @@ - Admin · CM4 Provisioning + Admin · GNSS Guard Provisioning @@ -76,6 +76,23 @@

Loading…

+ +
+

Update boot (EEPROM)

+

When a device is connected in USB boot mode, it appears here. Use this to write EEPROM boot order (e.g. eMMC only) to the device.

+ + +
+

@@ -247,6 +264,47 @@ authFetch('/api/admin/users', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({username: username, password: password}) }).then(function(r){ return r.json(); }).then(function(d){ if(d.ok) { document.getElementById('addUserForm').style.display = 'none'; document.getElementById('newUsername').value = ''; document.getElementById('newPassword').value = ''; fetchUsers(); } else alert(d.error); }); }; + function fetchPendingDevices() { + authFetch('/api/pending-devices').then(function(r){ return r.json(); }).then(function(d){ + var devEl = document.getElementById('adminUpdateBootDevice'); + var noneEl = document.getElementById('adminUpdateBootNone'); + if (d.usb) { + if (devEl) devEl.style.display = 'block'; + if (noneEl) noneEl.style.display = 'none'; + } else { + if (devEl) devEl.style.display = 'none'; + if (noneEl) noneEl.style.display = 'block'; + } + }).catch(function(){}); + } + function fetchEepromPresets() { + authFetch('/api/eeprom-presets').then(function(r){ return r.json(); }).then(function(d){ + var sel = document.getElementById('adminEepromPreset'); + if (!sel) return; + sel.innerHTML = ''; + (d.presets || []).forEach(function(p){ + var opt = document.createElement('option'); + opt.value = p.id || p.value; + opt.textContent = p.label || p.id || p.value; + sel.appendChild(opt); + }); + }).catch(function(){}); + } + fetchPendingDevices(); + fetchEepromPresets(); + var adminUpdateEepromBtn = document.getElementById('adminUpdateEepromBtn'); + if (adminUpdateEepromBtn) { + adminUpdateEepromBtn.onclick = function(){ + var presetEl = document.getElementById('adminEepromPreset'); + var bootOrder = (presetEl && presetEl.value) ? presetEl.value : '0x1'; + authFetch('/api/device-action', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ source: 'usb', action: 'eeprom_update', boot_order: bootOrder }) }) + .then(function(r){ return r.json(); }) + .then(function(d){ if (d.ok) { fetchPendingDevices(); alert('Update EEPROM sent. The host will write to the device.'); } else alert(d.error || 'Failed'); }) + .catch(function(){ alert('Request failed'); }); + }; + } + setInterval(fetchPendingDevices, 3000); + fetchBackups(); fetchCloudinit(); fetchUsers(); fetchLogs(); fetchGolden(); setInterval(fetchLogs, 30000); diff --git a/emmc-provisioning/dashboard/templates/cloudinit_build.html b/emmc-provisioning/dashboard/templates/cloudinit_build.html index 3884161..fdae898 100644 --- a/emmc-provisioning/dashboard/templates/cloudinit_build.html +++ b/emmc-provisioning/dashboard/templates/cloudinit_build.html @@ -4,7 +4,7 @@ - Cloud-init build · Admin · CM4 Provisioning + Cloud-init build · Admin · GNSS Guard Provisioning diff --git a/emmc-provisioning/dashboard/templates/home.html b/emmc-provisioning/dashboard/templates/home.html index d774d4c..5668756 100644 --- a/emmc-provisioning/dashboard/templates/home.html +++ b/emmc-provisioning/dashboard/templates/home.html @@ -4,7 +4,7 @@ - Deploy · CM4 Provisioning + Deploy · GNSS Guard Provisioning @@ -96,7 +96,7 @@
-

CM4 eMMC Provisioning

+

GNSS Guard Provisioning

Admin
@@ -107,7 +107,7 @@

Status

Idle - Waiting for device + Waiting for Device in USB boot mode.
@@ -128,7 +128,7 @@
-

Capture or deploy

+

Deploy and Backup

@@ -142,12 +142,12 @@

Loading…

-
+
Recent log
-
+
DHCP leases

Provisioning LAN (dnsmasq) — when dashboard runs on LXC.

@@ -162,7 +162,7 @@

USB boot

    -
  1. 1 Set reTerminal to boot mode (eMMC disable jumper).
  2. +
  3. 1 Set device to boot mode (eMMC disable jumper).
  4. 2 Connect USB to host; choose Backup or Deploy above.
  5. 3 Remove jumper and power cycle when done.
@@ -178,7 +178,7 @@ const phase = data.phase || 'idle'; document.getElementById('statusPill').className = 'status-pill ' + phase; document.getElementById('statusPill').textContent = phaseLabels[phase] || phase; - document.getElementById('statusMsg').textContent = data.message || ''; + document.getElementById('statusMsg').textContent = data.message || (phase === 'idle' ? 'Waiting for Device in USB boot mode.' : ''); const err = document.getElementById('statusErr'); if (data.error) { err.textContent = data.error; err.style.display = 'block'; } else { err.style.display = 'none'; } if (phase === 'done') scheduleDoneClear(); @@ -204,7 +204,7 @@ shrinkWrap.style.display = 'block'; const el = document.createElement('div'); el.className = 'device-item'; - el.innerHTML = '
USB device — choose Backup, Deploy, or Update EEPROM
'; + el.innerHTML = '
USB device — choose Backup or Deploy
'; container.appendChild(el); } else shrinkWrap.style.display = 'none'; noPending.style.display = hasAny ? 'none' : 'block'; @@ -212,10 +212,6 @@ btn.onclick = function() { const body = { source: btn.getAttribute('data-source'), action: btn.getAttribute('data-action') }; if (body.source === 'network') body.mac = btn.getAttribute('data-mac'); - if (body.action === 'eeprom_update' && body.source === 'usb') { - const presetEl = btn.closest('.device-item') && btn.closest('.device-item').querySelector('.eeprom-preset'); - body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0x1'; - } 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) }) .then(function(r) { return r.json(); }) @@ -267,7 +263,14 @@ fetch('/api/first-boot-status').then(function(r){ return r.json(); }).then(renderFirstBootStatus).catch(function(){}); } function fetchPending() { fetch('/api/pending-devices').then(function(r){ return r.json(); }).then(function(d){ renderPending(d.usb || null, d.network || []); }).catch(function(){ renderPending(null, []); }); } - function fetchLog() { fetch('/api/log').then(function(r){ return r.json(); }).then(function(d){ document.getElementById('log').textContent = d.log || ''; }).catch(function(){}); } + function fetchLog() { + fetch('/api/log').then(function(r){ return r.json(); }).then(function(d){ + var logEl = document.getElementById('log'); + if (!logEl) return; + logEl.textContent = d.log || ''; + logEl.scrollTop = logEl.scrollHeight; + }).catch(function(){}); + } function fetchGolden() { fetch('/api/golden-info').then(function(r){ return r.json(); }).then(function(d){ const el = document.getElementById('goldenInfo'); diff --git a/emmc-provisioning/dashboard/templates/index.html b/emmc-provisioning/dashboard/templates/index.html index 43149a9..6f431e6 100644 --- a/emmc-provisioning/dashboard/templates/index.html +++ b/emmc-provisioning/dashboard/templates/index.html @@ -4,7 +4,7 @@ - CM4 eMMC Provisioning + GNSS Guard Provisioning @@ -359,8 +359,8 @@
-

CM4 eMMC Provisioning

-

Deploy or backup reTerminal via USB boot mode

+

GNSS Guard Provisioning

+

Deploy or backup device via USB boot mode

@@ -368,12 +368,12 @@

Current status

Idle - Waiting for device + Waiting for Device in USB boot mode.
@@ -384,8 +384,8 @@
-

Capture image or deploy

-

To capture (backup) an image from a device: connect it in USB boot mode. When the device appears below, click Backup to save its eMMC to a file. To write an image to a device, click Deploy (requires a golden image).

+

Deploy and Backup

+

Connect a device in USB boot mode. When it appears below, click Backup to save its eMMC to a file, or Deploy to write the golden image to the device (requires a golden image set in Admin).

@@ -398,7 +398,7 @@ — USB boot mode
- + @@ -477,7 +477,7 @@

USB boot mode

    -
  1. 1 Set reTerminal to boot mode (eMMC disable jumper, e.g. J2 / nRPIBOOT).
  2. +
  3. 1 Set device to boot mode (eMMC disable jumper, e.g. J2 / nRPIBOOT).
  4. 2 Connect USB slave to the host and power on. The device appears above; choose Backup or Deploy.
  5. 3 When done, remove the jumper and power cycle to boot from eMMC.
@@ -485,7 +485,7 @@
-
+
Recent log

@@ -516,7 +516,7 @@
       const phase = data.phase || 'idle';
       statusPill.className = 'status-pill ' + phase;
       statusPill.textContent = phaseLabels[phase] || phase;
-      statusMsg.textContent = data.message || '';
+      statusMsg.textContent = data.message || (phase === 'idle' ? 'Waiting for Device in USB boot mode.' : '');
 
       if (data.error) {
         statusErr.textContent = data.error;
@@ -566,7 +566,7 @@
         if (shrinkWrap) shrinkWrap.style.display = 'block';
         const el = document.createElement('div');
         el.className = 'device-item';
-        el.innerHTML = '
USB boot
Device connected — choose Backup, Deploy, or Update EEPROM
'; + el.innerHTML = '
USB boot
Device connected — choose Backup or Deploy
'; container.appendChild(el); } else { if (shrinkWrap) shrinkWrap.style.display = 'none'; @@ -583,10 +583,6 @@ const mac = btn.getAttribute('data-mac'); const body = { source: source, action: action }; if (mac) body.mac = mac; - if (action === 'eeprom_update' && source === 'usb') { - const presetEl = btn.closest('.device-item') && btn.closest('.device-item').querySelector('.eeprom-preset'); - body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0x1'; - } const shrinkCb = document.getElementById('shrinkAfterBackup'); if (action === 'backup' && shrinkCb && shrinkCb.checked) body.shrink = true; fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) @@ -785,7 +781,12 @@ fetch('/api/pending-devices').then(function(r) { return r.json(); }).then(function(d) { renderPending(d.usb || null, d.network || []); }).catch(function() { renderPending(null, []); }); } function fetchLog() { - fetch('/api/log').then(function(r) { return r.json(); }).then(function(d) { document.getElementById('log').textContent = d.log || ''; }).catch(function() {}); + fetch('/api/log').then(function(r) { return r.json(); }).then(function(d) { + var logEl = document.getElementById('log'); + if (!logEl) return; + logEl.textContent = d.log || ''; + logEl.scrollTop = logEl.scrollHeight; + }).catch(function() {}); } function fetchBackups() { fetch('/api/backups').then(function(r) { return r.json(); }).then(function(d) { diff --git a/emmc-provisioning/dashboard/templates/login.html b/emmc-provisioning/dashboard/templates/login.html index c9b4ed0..454fee5 100644 --- a/emmc-provisioning/dashboard/templates/login.html +++ b/emmc-provisioning/dashboard/templates/login.html @@ -4,7 +4,7 @@ - Admin login · CM4 Provisioning + Admin login · GNSS Guard Provisioning @@ -28,7 +28,7 @@

Admin login

-

CM4 eMMC provisioning dashboard

+

GNSS Guard Provisioning admin

{% if error %}

{{ error }}

{% endif %}
diff --git a/emmc-provisioning/dashboard/templates/portal_files.html b/emmc-provisioning/dashboard/templates/portal_files.html index bb526a8..4e9bdb9 100644 --- a/emmc-provisioning/dashboard/templates/portal_files.html +++ b/emmc-provisioning/dashboard/templates/portal_files.html @@ -4,7 +4,7 @@ - Portal files · Admin · CM4 Provisioning + Portal files · Admin · GNSS Guard Provisioning diff --git a/emmc-provisioning/docs/PROXMOX-LXC-DEPLOYMENT.md b/emmc-provisioning/docs/PROXMOX-LXC-DEPLOYMENT.md index 6230713..fd6e2e2 100644 --- a/emmc-provisioning/docs/PROXMOX-LXC-DEPLOYMENT.md +++ b/emmc-provisioning/docs/PROXMOX-LXC-DEPLOYMENT.md @@ -210,7 +210,7 @@ Or copy `scripts/monitor-from-host.sh` to the host and run `./monitor-from-host. 4. **"No 'bootcode' files found in mass-storage-gadget64"** – Usually because `bootfiles.bin` is a **broken symlink** (e.g. `-> ../firmware/bootfiles.bin`) and that target doesn’t exist. **Fix on host:** run `scripts/fix-gadget-bootcode-on-host.sh` on the host (it removes the symlink and extracts `bootcode4.bin` from the installed rpiboot binary). From your machine: `ssh root@10.130.60.224 'bash -s' < scripts/fix-gadget-bootcode-on-host.sh`. **Alternative:** repopulate the gadget dir with `./scripts/populate-gadget-on-host.sh root@10.130.60.224`, or full reinstall with `./scripts/build-and-deploy-usbboot-to-host.sh root@10.130.60.224`. Then verify: `ls -la /opt/usbboot/mass-storage-gadget64/` (should list a real `bootcode4.bin` or `bootfiles.bin`, plus `boot.img`, `config.txt`). -4. **Clear stuck error in portal** – If the portal shows an old error (e.g. "Golden image not found" or "rpiboot failed"), click **Clear message** in the dashboard, or: `ssh root@10.130.60.224 "echo '{\"phase\":\"idle\",\"message\":\"Waiting for reTerminal in boot mode or network.\",\"progress\":null}' > /var/lib/cm4-provisioning/status.json"`. Then unplug/replug the device. +4. **Clear stuck error in portal** – If the portal shows an old error (e.g. "Golden image not found" or "rpiboot failed"), click **Clear message** in the dashboard, or: `ssh root@10.130.60.224 "echo '{\"phase\":\"idle\",\"message\":\"Waiting for Device in USB boot mode.\",\"progress\":null}' > /var/lib/cm4-provisioning/status.json"`. Then unplug/replug the device. 5. **"PiShrink not installed" when clicking Shrink/Compress** – Shrink and Compress run **on the host**, not in the LXC. Install PiShrink on the host: `ssh root@HOST 'bash -s' < emmc-provisioning/scripts/install-pishrink-on-host.sh`. Ensure deploy has been run so the host has `run-shrink-on-host.sh` and `cm4-shrink.path` enabled (`systemctl status cm4-shrink.path`). diff --git a/emmc-provisioning/host/flash-emmc-on-connect.sh b/emmc-provisioning/host/flash-emmc-on-connect.sh index 7a75b7b..c9ec24d 100644 --- a/emmc-provisioning/host/flash-emmc-on-connect.sh +++ b/emmc-provisioning/host/flash-emmc-on-connect.sh @@ -206,7 +206,7 @@ for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do fi log "Backup complete: $BACKUPS_DIR/$final_name" write_status "done" "Backup complete: $final_name" "100" - ( sleep 90 && write_status "idle" "Waiting for reTerminal in boot mode or network." "null" ) & + ( sleep 90 && write_status "idle" "Waiting for Device in USB boot mode." "null" ) & else write_status "error" "Backup failed" "null" "dd failed" fi @@ -230,7 +230,7 @@ for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do log "Flash complete. Remove eMMC disable jumper and power cycle the reTerminal." write_status "done" "Flash complete. Remove eMMC disable jumper and power cycle the reTerminal." "100" # Auto-reset status to idle after 90s so dashboard does not stay on this message (dashboard also auto-clears after 60s when open) - ( sleep 90 && write_status "idle" "Waiting for reTerminal in boot mode or network." "null" ) & + ( sleep 90 && write_status "idle" "Waiting for Device in USB boot mode." "null" ) & else write_status "error" "Flash failed" "null" "dd failed" fi @@ -265,7 +265,7 @@ for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do log "EEPROM update written. Remove eMMC disable jumper and power cycle to apply." write_status "done" "EEPROM update written to boot partition. Remove eMMC disable jumper and power cycle the reTerminal to apply." "100" rm -f "$EEPROM_UPD" "$EEPROM_SIG" - ( sleep 90 && write_status "idle" "Waiting for reTerminal in boot mode or network." "null" ) & + ( sleep 90 && write_status "idle" "Waiting for Device in USB boot mode." "null" ) & else write_status "error" "EEPROM update failed" "null" "Failed to copy pieeprom.upd/sig" fi