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