Add real first-time provisioning to install.sh (v2.14.0)

Until now ./install.sh only managed an already-installed kiosk;
ubuntu-based-kiosk.sh was still the only path from a bare Ubuntu Server
box to a running one. install.sh now provisions from scratch too:
packages, kiosk user, Node.js/Electron, LightDM+Openbox autologin,
audio/video/HDMI/power-button hardware setup, and the firewall, then
hands off to the already-migrated Core Settings/Advanced menus for
initial configuration instead of reimplementing that logic again.

- lib/provision.sh: the new provisioning flow, built mostly by calling
  existing menus (core_settings_menu, emergency hotspot, virtual
  consoles) - cuts it to ~300 lines against the legacy script's
  ~4,000-line first_time_install().
- lib/electron.sh: electron_install_binary() extracted out of
  menus/advanced_electron.sh so provisioning and the existing "Fix
  blank screen" action share one implementation.
- kiosk-app/ and provision/files/: the Electron app source and every
  system template file, extracted byte-for-byte out of
  ubuntu-based-kiosk.sh's heredocs into real files.
- Found and fixed a bash set -e gotcha along the way: testing a
  multi-statement function as an if-condition (`if ! some_func; then`)
  silently exempts everything inside that function from set -e for the
  duration of the call. Fixed in the new provisioning code and in
  menus/advanced_electron.sh's pre-existing repair action, which had
  the same shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
This commit is contained in:
Claude
2026-08-19 05:24:32 +00:00
parent 1c9447bc5d
commit 037a62e008
33 changed files with 4209 additions and 99 deletions
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: rgba(0,0,0,0.9);
color: #ecf0f1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
padding: 40px;
}
.container { text-align: center; max-width: 600px; }
h2 { font-size: 32px; margin-bottom: 20px; }
.message { font-size: 20px; margin-bottom: 20px; line-height: 1.5; }
.countdown { font-size: 72px; font-weight: bold; color: #e74c3c; margin: 20px 0; }
.options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin: 30px 0;
}
.btn {
padding: 20px 40px;
font-size: 18px;
cursor: pointer;
border: none;
border-radius: 12px;
font-weight: bold;
transition: all 0.2s;
color: white;
}
.btn-primary {
background: #27ae60;
grid-column: 1 / -1;
}
.btn-primary:hover { background: #229954; }
.btn-primary:active { background: #1e8449; }
.btn-extend { background: #3498db; }
.btn-extend:hover { background: #2980b9; }
.btn-extend:active { background: #21618c; }
.btn-home {
background: #95a5a6;
grid-column: 1 / -1;
font-size: 14px;
padding: 12px;
}
.btn-home:hover { background: #7f8c8d; }
.info {
font-size: 14px;
color: #95a5a6;
margin-top: 20px;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<h2>👋 Are you still here?</h2>
<div class="message">
No activity detected. Choose an option:
</div>
<div class="countdown" id="countdown">15</div>
<div class="options">
<button class="btn btn-primary" onclick="imHere(0)">
✓ Yes, I'm here!
</button>
<button class="btn btn-extend" onclick="imHere(15)">
🍿 15 more minutes
</button>
<button class="btn btn-extend" onclick="imHere(30)">
⏱️ 30 more minutes
</button>
<button class="btn btn-extend" onclick="imHere(60)">
🎬 1 hour
</button>
<button class="btn btn-extend" onclick="imHere(120)">
📺 2 hours
</button>
<button class="btn btn-home" onclick="goHome()">
🏠 Return to home now
</button>
</div>
<div class="info">
️ Extensions pause the inactivity timer<br>
Media playback (video/audio) automatically pauses the timer<br>
Maximum extension: 4 hours (safety timeout)
</div>
</div>
<script>
const {ipcRenderer} = require('electron');
let count = 15;
const interval = setInterval(() => {
count--;
document.getElementById('countdown').textContent = count;
if (count <= 0) {
clearInterval(interval);
}
}, 1000);
function imHere(minutes) {
clearInterval(interval);
console.log('[PROMPT] User selected: '+(minutes===0?'Continue':minutes+' minutes'));
ipcRenderer.send('user-still-here', minutes);
}
function goHome() {
clearInterval(interval);
console.log('[PROMPT] User requested immediate home return');
ipcRenderer.send('user-still-here', -1);
}
document.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
imHere(0);
} else if (e.key === 'Escape') {
goHome();
}
});
</script>
</body>
</html>