Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84e8f074e8 | ||
|
|
551df48687 |
@@ -91,7 +91,7 @@ Useful options (`iso/build-iso.sh --help` for all):
|
|||||||
|
|
||||||
| Option | Effect |
|
| Option | Effect |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `--unattended` | First-boot install takes every default with no prompts (skips the Core Settings menu) and reboots into the kiosk. Set sites afterwards in the Web UI (`http://<ip>:8090`) or `./install.sh`. |
|
| `--unattended` | First-boot install takes every default with no prompts (skips the Core Settings menu) and reboots into the kiosk. Set sites afterwards in the Web UI (`http://<ip>:8888`) or `./install.sh`. |
|
||||||
| `--username NAME [--password PASS]` | Pre-set the admin account (password is prompted for if omitted; only a hash goes on the ISO). Not `kiosk`. |
|
| `--username NAME [--password PASS]` | Pre-set the admin account (password is prompted for if omitted; only a hash goes on the ISO). Not `kiosk`. |
|
||||||
| `--hostname`, `--ssh-key FILE`, `--locale`, `--keyboard` | Pre-set those. |
|
| `--hostname`, `--ssh-key FILE`, `--locale`, `--keyboard` | Pre-set those. |
|
||||||
| `--auto-network` / `--auto-storage` | Skip the network screen (DHCP on ethernet) / wipe the largest disk without asking. |
|
| `--auto-network` / `--auto-storage` | Skip the network screen (DHCP on ethernet) / wipe the largest disk without asking. |
|
||||||
@@ -1585,7 +1585,7 @@ full migration pass.
|
|||||||
- **Fix:** the CUPS menu could report an installed CUPS as "not installed" (`dpkg -l | grep -q` failing under `set -o pipefail`).
|
- **Fix:** the CUPS menu could report an installed CUPS as "not installed" (`dpkg -l | grep -q` failing under `set -o pipefail`).
|
||||||
|
|
||||||
**Previous (v2.17.0):**
|
**Previous (v2.17.0):**
|
||||||
- **Web UI now installs by default** during first-time provisioning (fixed port 8090, no prompt) instead of being opt-in — the Addons menu entry still works standalone for reconfiguring the port or reinstalling it on a kiosk provisioned before this change.
|
- **Web UI now installs by default** during first-time provisioning (port 8888 for new installs, 8090 on older ones; no prompt) instead of being opt-in — the Addons menu entry still works standalone for reconfiguring the port or reinstalling it on a kiosk provisioned before this change.
|
||||||
- **The web UI can now install/reconfigure CUPS Printing, LMS Server, Squeezelite Player, and Asterisk Intercom, and check for updates** — the same four addons plus Update named directly. Every one of these is the exact same interactive `action_*` function the terminal menu already uses (no prompt/mutation refactor of any addon file), driven by piping the right answers on stdin — the same technique this project's own bash tests already use to drive these functions.
|
- **The web UI can now install/reconfigure CUPS Printing, LMS Server, Squeezelite Player, and Asterisk Intercom, and check for updates** — the same four addons plus Update named directly. Every one of these is the exact same interactive `action_*` function the terminal menu already uses (no prompt/mutation refactor of any addon file), driven by piping the right answers on stdin — the same technique this project's own bash tests already use to drive these functions.
|
||||||
- **Privilege model:** the web service itself still runs as `$KIOSK_USER` with zero ambient `sudo`. A new narrow, allow-listed root helper is the only way it ever gains privilege — reachable only via a single-path passwordless sudo rule (generated and validated with `visudo -c -f` before being installed), and it re-checks its own fixed action allow-list before dispatching anything. Chosen over running the whole service as root after asking directly: since this repo has no login of its own by design, a request that reaches the web UI with no reverse proxy in front is effectively unauthenticated, so the allow-list bounds what that can actually do to five vetted actions, never a root shell.
|
- **Privilege model:** the web service itself still runs as `$KIOSK_USER` with zero ambient `sudo`. A new narrow, allow-listed root helper is the only way it ever gains privilege — reachable only via a single-path passwordless sudo rule (generated and validated with `visudo -c -f` before being installed), and it re-checks its own fixed action allow-list before dispatching anything. Chosen over running the whole service as root after asking directly: since this repo has no login of its own by design, a request that reaches the web UI with no reverse proxy in front is effectively unauthenticated, so the allow-list bounds what that can actually do to five vetted actions, never a root shell.
|
||||||
- Long-running installs stream live output to the browser via Server-Sent Events, one action at a time — a second request while one is in flight gets a clear `409`, never silently queued or dropped.
|
- Long-running installs stream live output to the browser via Server-Sent Events, one action at a time — a second request while one is in flight gets a clear `409`, never silently queued or dropped.
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
# ~/ubuntu-based-kiosk - interactively by default (same prompts as a
|
# ~/ubuntu-based-kiosk - interactively by default (same prompts as a
|
||||||
# manual install), or with --unattended taking every default and
|
# manual install), or with --unattended taking every default and
|
||||||
# rebooting straight into the kiosk. Configure sites afterwards via
|
# rebooting straight into the kiosk. Configure sites afterwards via
|
||||||
# the Web UI (http://<ip>:8090) or ./install.sh.
|
# the Web UI (http://<ip>:8888) or ./install.sh.
|
||||||
#
|
#
|
||||||
# Nothing in the original ISO is changed except /boot/grub/grub.cfg
|
# Nothing in the original ISO is changed except /boot/grub/grub.cfg
|
||||||
# (menu title, plus the `autoinstall` kernel argument with --no-confirm);
|
# (menu title, plus the `autoinstall` kernel argument with --no-confirm);
|
||||||
|
|||||||
+15
-1
@@ -1226,6 +1226,19 @@ function historyHome(){
|
|||||||
markActivity();
|
markActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Power menu -> Lock now: put up the lock screen right away (the same one
|
||||||
|
// the inactivity timeout shows). Only when password protection is on and a
|
||||||
|
// code is set - showLockoutScreen refuses otherwise, so a kiosk with no
|
||||||
|
// code can't be locked with nothing to unlock it.
|
||||||
|
function lockNow(){
|
||||||
|
if(!enablePasswordProtection||!lockoutPassword){
|
||||||
|
console.log('[LOCKOUT] Lock now ignored - password protection is off');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('[LOCKOUT] Lock now requested from the power menu');
|
||||||
|
showLockoutScreen('unlock');
|
||||||
|
}
|
||||||
|
|
||||||
function showPinEntry(){
|
function showPinEntry(){
|
||||||
if(pinWindow&&!pinWindow.isDestroyed()){
|
if(pinWindow&&!pinWindow.isDestroyed()){
|
||||||
pinWindow.focus();
|
pinWindow.focus();
|
||||||
@@ -1545,7 +1558,7 @@ function showPowerMenu(){
|
|||||||
|
|
||||||
// For normal mode, use custom overlay with 30-second timeout
|
// For normal mode, use custom overlay with 30-second timeout
|
||||||
console.log('[POWER] Showing custom power menu overlay');
|
console.log('[POWER] Showing custom power menu overlay');
|
||||||
const powerInfo={version:VERSION,localIP:localIP,vpnIP:vpnIP.trim()};
|
const powerInfo={version:VERSION,localIP:localIP,vpnIP:vpnIP.trim(),canLock:!!(enablePasswordProtection&&lockoutPassword)};
|
||||||
// Send to all views (preload.js runs in views, not mainWindow)
|
// Send to all views (preload.js runs in views, not mainWindow)
|
||||||
for(const view of views){
|
for(const view of views){
|
||||||
if(view&&view.webContents&&!view.webContents.isDestroyed()){
|
if(view&&view.webContents&&!view.webContents.isDestroyed()){
|
||||||
@@ -1730,6 +1743,7 @@ async function createWindow(){
|
|||||||
if(action==='shutdown')exec('systemctl poweroff');
|
if(action==='shutdown')exec('systemctl poweroff');
|
||||||
else if(action==='restart')exec('systemctl reboot');
|
else if(action==='restart')exec('systemctl reboot');
|
||||||
else if(action==='reload'){app.relaunch();app.quit();}
|
else if(action==='reload'){app.relaunch();app.quit();}
|
||||||
|
else if(action==='lock')lockNow();
|
||||||
});
|
});
|
||||||
ipcMain.on('toggle-hidden',toggleHidden);
|
ipcMain.on('toggle-hidden',toggleHidden);
|
||||||
ipcMain.on('history-nav',(event,direction)=>{
|
ipcMain.on('history-nav',(event,direction)=>{
|
||||||
|
|||||||
@@ -666,6 +666,16 @@ window.addEventListener('DOMContentLoaded',()=>{
|
|||||||
ipcRenderer.send('power-action','reload');
|
ipcRenderer.send('power-action','reload');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only offered when password protection is on (set in showPowerMenu).
|
||||||
|
const lockBtn=document.createElement('button');
|
||||||
|
lockBtn.id='power-menu-lock';
|
||||||
|
lockBtn.textContent='🔒 Lock now';
|
||||||
|
lockBtn.style.cssText=btnStyle+'background:#8e44ad;color:white;display:none;';
|
||||||
|
lockBtn.addEventListener('click',()=>{
|
||||||
|
hidePowerMenu();
|
||||||
|
ipcRenderer.send('power-action','lock');
|
||||||
|
});
|
||||||
|
|
||||||
const cancelBtn=document.createElement('button');
|
const cancelBtn=document.createElement('button');
|
||||||
cancelBtn.textContent='Cancel';
|
cancelBtn.textContent='Cancel';
|
||||||
cancelBtn.style.cssText=btnStyle+'background:#7f8c8d;color:white;';
|
cancelBtn.style.cssText=btnStyle+'background:#7f8c8d;color:white;';
|
||||||
@@ -673,6 +683,7 @@ window.addEventListener('DOMContentLoaded',()=>{
|
|||||||
hidePowerMenu();
|
hidePowerMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
buttonsDiv.appendChild(lockBtn);
|
||||||
buttonsDiv.appendChild(shutdownBtn);
|
buttonsDiv.appendChild(shutdownBtn);
|
||||||
buttonsDiv.appendChild(restartBtn);
|
buttonsDiv.appendChild(restartBtn);
|
||||||
buttonsDiv.appendChild(reloadBtn);
|
buttonsDiv.appendChild(reloadBtn);
|
||||||
@@ -708,6 +719,8 @@ window.addEventListener('DOMContentLoaded',()=>{
|
|||||||
}
|
}
|
||||||
infoDiv.innerHTML=infoText;
|
infoDiv.innerHTML=infoText;
|
||||||
}
|
}
|
||||||
|
const lockBtn=document.getElementById('power-menu-lock');
|
||||||
|
if(lockBtn)lockBtn.style.display=(info&&info.canLock)?'':'none';
|
||||||
|
|
||||||
powerMenu.style.display='flex';
|
powerMenu.style.display='flex';
|
||||||
powerMenuVisible=true;
|
powerMenuVisible=true;
|
||||||
|
|||||||
+1
-1
@@ -328,7 +328,7 @@ provision_configure_webui() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local port="8090"
|
local port="8888"
|
||||||
webui_write_env_file "$port"
|
webui_write_env_file "$port"
|
||||||
webui_write_unit_file
|
webui_write_unit_file
|
||||||
webui_write_helper_script
|
webui_write_helper_script
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ webui_is_active() {
|
|||||||
|
|
||||||
webui_port() {
|
webui_port() {
|
||||||
if sudo test -f "$WEBUI_ENV_DIR/webui.env" 2>/dev/null; then
|
if sudo test -f "$WEBUI_ENV_DIR/webui.env" 2>/dev/null; then
|
||||||
sudo grep -oP '^PORT=\K.*' "$WEBUI_ENV_DIR/webui.env" 2>/dev/null || echo "8090"
|
sudo grep -oP '^PORT=\K.*' "$WEBUI_ENV_DIR/webui.env" 2>/dev/null || echo "8888"
|
||||||
else
|
else
|
||||||
echo "8090"
|
echo "8888"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ action_webui_install() {
|
|||||||
ask_yes_no "Install the Web UI?" "n" || { echo "Cancelled"; return; }
|
ask_yes_no "Install the Web UI?" "n" || { echo "Cancelled"; return; }
|
||||||
|
|
||||||
local port
|
local port
|
||||||
port=$(ask_integer "Port to listen on" "8090" 1024 65535)
|
port=$(ask_integer "Port to listen on" "8888" 1024 65535)
|
||||||
|
|
||||||
echo "Installing app files and npm dependencies..."
|
echo "Installing app files and npm dependencies..."
|
||||||
if ! webui_install_app_files; then
|
if ! webui_install_app_files; then
|
||||||
@@ -321,6 +321,8 @@ action_webui_reconfigure() {
|
|||||||
webui_write_env_file "$port"
|
webui_write_env_file "$port"
|
||||||
sudo systemctl restart kiosk-webui
|
sudo systemctl restart kiosk-webui
|
||||||
sudo ufw allow "${port}/tcp" comment 'Kiosk Web UI' 2>/dev/null || true
|
sudo ufw allow "${port}/tcp" comment 'Kiosk Web UI' 2>/dev/null || true
|
||||||
|
# The old port is no longer used - close it.
|
||||||
|
sudo ufw delete allow "${current_port}/tcp" 2>/dev/null || true
|
||||||
log_success "Web UI now listening on port ${port}"
|
log_success "Web UI now listening on port ${port}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -294,7 +294,7 @@ app.put('/api/region', (req, res) => {
|
|||||||
runRegionHelper('region-set', input, res);
|
runRegionHelper('region-set', input, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8090;
|
const PORT = process.env.PORT || 8888;
|
||||||
const BIND_ADDR = process.env.BIND_ADDR || '0.0.0.0';
|
const BIND_ADDR = process.env.BIND_ADDR || '0.0.0.0';
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
|||||||
Reference in New Issue
Block a user