Merge pull request #82 from outis1one/claude/gifted-bohr-dfzdig
Claude/gifted bohr dfzdig
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Ubuntu Based Kiosk
|
||||
|
||||
**Current Version:** 1.0.1 (check script header for latest version)
|
||||
**Current Version:** 1.0.2 (check script header for latest version)
|
||||
**Built with Claude Sonnet 4.6 AI assistance**
|
||||
**License:** GPL v3 - Keep derivatives open source
|
||||
**Repository:** https://github.com/outis1one/ubuntu-based-kiosk/
|
||||
@@ -114,6 +114,92 @@ The installer will guide you through configuration during setup.
|
||||
|
||||
## Optional Add-ons
|
||||
|
||||
### Authentication
|
||||
|
||||
#### Authelia Auto-Login (SSO)
|
||||
|
||||
Automatically authenticates the kiosk against a self-hosted [Authelia](https://www.authelia.com) instance on every startup. The Authelia password is **not stored in plain text** — it is encrypted with AES-256-CBC using a key derived from the machine's unique `/etc/machine-id`, so the encrypted blob is useless on any other machine.
|
||||
|
||||
**Access via menu:** `Addons → 5. Authelia Auto-Login`
|
||||
|
||||
After running the addon it prints the full server-side setup, but the summary is below.
|
||||
|
||||
##### Kiosk side (SSH in and run the installer)
|
||||
|
||||
```bash
|
||||
ssh user@kiosk-machine
|
||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
||||
# Addons → 5. Authelia Auto-Login
|
||||
# Enter your Authelia URL, username, and password when prompted
|
||||
```
|
||||
|
||||
##### Authelia server side (Dockerized)
|
||||
|
||||
**Step 1 — Generate the argon2 password hash** (run on your Docker host):
|
||||
|
||||
```bash
|
||||
docker run --rm authelia/authelia:latest \
|
||||
authelia crypto hash generate argon2 \
|
||||
--password 'yourpassword'
|
||||
```
|
||||
|
||||
Copy the `$argon2id$...` output — that is your hash.
|
||||
|
||||
**Step 2 — Add a kiosk user** to `~/docker/authelia/config/users.yml`:
|
||||
|
||||
```yaml
|
||||
kiosk:
|
||||
displayname: "Kiosk Display"
|
||||
password: '$argon2id$v=19$m=65536,t=3,p=4$<paste hash here>'
|
||||
email: kiosk@local.com
|
||||
groups:
|
||||
- kiosk
|
||||
```
|
||||
|
||||
**Step 3 — Configure session duration and access control** in `~/docker/authelia/config/configuration.yml`:
|
||||
|
||||
```yaml
|
||||
session:
|
||||
expiration: 1y # absolute session lifetime
|
||||
inactivity: 90d # idle timeout before logout
|
||||
remember_me: 1y # duration granted by keepMeLoggedIn
|
||||
|
||||
access_control:
|
||||
default_policy: deny
|
||||
rules:
|
||||
# Allow kiosk group to reach any subdomain with one-factor auth
|
||||
- domain: '*.yourdomain.com'
|
||||
subject: 'group:kiosk'
|
||||
policy: one_factor
|
||||
# Optional: bypass Authelia entirely for the kiosk's static IP
|
||||
# - domain: '*.yourdomain.com'
|
||||
# networks: ['192.168.1.50/32']
|
||||
# policy: bypass
|
||||
```
|
||||
|
||||
**Step 4 — Restart Authelia:**
|
||||
|
||||
```bash
|
||||
docker compose restart authelia
|
||||
```
|
||||
|
||||
##### How it works
|
||||
|
||||
On every kiosk startup, Electron calls Authelia's `/api/firstfactor` endpoint with `keepMeLoggedIn: true` **before** any sites load. Authelia responds with a `Set-Cookie` header that Electron absorbs into its default session. All BrowserViews then load with that session cookie already present.
|
||||
|
||||
Because Electron's session persists to disk across reboots (`/home/kiosk/.config/kiosk-app/`), the cookie also survives restarts — the API call on startup just refreshes or extends it.
|
||||
|
||||
##### Authelia vs HTTP Basic Auth
|
||||
|
||||
Both can be used at the same time — they serve different purposes:
|
||||
|
||||
| Method | Where configured | When to use |
|
||||
|--------|-----------------|-------------|
|
||||
| **Authelia SSO** | `Addons → Authelia Auto-Login` (global) | Sites protected by an Authelia reverse proxy |
|
||||
| **HTTP Basic Auth** | Per-site username/password in tab config | Sites that show a browser popup asking for credentials |
|
||||
|
||||
---
|
||||
|
||||
### Communication
|
||||
- **Easy Asterisk Intercom** - Voice communication and intercom system
|
||||
- Downloads latest version from Easy Asterisk repository
|
||||
@@ -399,7 +485,7 @@ smb://WORKGROUP/COMPUTER/PrinterName
|
||||
|
||||
# Menu structure:
|
||||
# 1. Core Settings - Sites, WiFi, schedules, passwords, full reinstall, complete uninstall
|
||||
# 2. Addons - Easy Asterisk Intercom, LMS, CUPS, VNC, VPNs
|
||||
# 2. Addons - Authelia Auto-Login, Easy Asterisk Intercom, LMS, CUPS, VNC, VPNs
|
||||
# 3. Advanced - Diagnostics, logs, Electron updates, virtual consoles, emergency hotspot
|
||||
# 4. Restart Kiosk Display
|
||||
```
|
||||
@@ -974,9 +1060,14 @@ See the LICENSE file in the repository for full terms.
|
||||
|
||||
## Project Status & Future Plans
|
||||
|
||||
**Current Version:** 1.0.1
|
||||
**Current Version:** 1.0.2
|
||||
|
||||
**Recent Updates (v1.0.1):**
|
||||
**Recent Updates (v1.0.2):**
|
||||
- **Authelia Auto-Login addon** (`Addons → 5`) — authenticates with Authelia SSO on every startup; password stored encrypted (AES-256 keyed from machine ID, not plain text); prints full Dockerized Authelia server-side setup after configuration
|
||||
- **Bug fix:** PipeWire config dirs were created as root at step [5.5/27], causing "Permission denied" on fresh installs
|
||||
- **README:** install commands no longer hardcode version numbers — always fetch latest from GitHub
|
||||
|
||||
**Previous (v1.0.1):**
|
||||
- **Node.js upgrade:** 20 LTS → 22 LTS
|
||||
- **Electron upgrade:** v39/41 → v42.x
|
||||
|
||||
@@ -1036,4 +1127,4 @@ Special thanks to the maintainers of all upstream projects that make Ubuntu Base
|
||||
---
|
||||
|
||||
*Last Updated: June 15, 2026*
|
||||
*Version: 1.0.1*
|
||||
*Version: 1.0.2*
|
||||
|
||||
@@ -4029,11 +4029,12 @@ NOISECFG
|
||||
###################################################################
|
||||
echo "[13/27] Installing Electron..."
|
||||
sudo -u "$KIOSK_USER" tee "$KIOSK_DIR/main.js" > /dev/null <<'MAINJS'
|
||||
const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog}=require('electron');
|
||||
const {app,BrowserWindow,BrowserView,globalShortcut,ipcMain,dialog,session}=require('electron');
|
||||
const {exec}=require('child_process');
|
||||
const fs=require('fs');
|
||||
const path=require('path');
|
||||
const os=require('os');
|
||||
const crypto=require('crypto');
|
||||
|
||||
// Suppress EPIPE errors (happen when no terminal attached)
|
||||
process.stdout.on('error',(e)=>{if(e.code!=='EPIPE')throw e;});
|
||||
@@ -4100,6 +4101,11 @@ let lockoutActivityTime=Date.now();
|
||||
let requirePasswordAfterDisplay=false;
|
||||
let lastScheduledLockCheck=0;
|
||||
|
||||
// Authelia auto-login state
|
||||
let autheliaURL='';
|
||||
let autheliaUsername='';
|
||||
let autheliaEncryptedPassword='';
|
||||
|
||||
function loadConfig(){
|
||||
try{
|
||||
if(!fs.existsSync(CONFIG_FILE)){
|
||||
@@ -4123,6 +4129,9 @@ function loadConfig(){
|
||||
lockoutActiveStart=config.lockoutActiveStart||"";
|
||||
lockoutActiveEnd=config.lockoutActiveEnd||"";
|
||||
requirePasswordOnBoot=(config.requirePasswordOnBoot===true);
|
||||
autheliaURL=config.autheliaURL||'';
|
||||
autheliaUsername=config.autheliaUsername||'';
|
||||
autheliaEncryptedPassword=config.autheliaEncryptedPassword||'';
|
||||
|
||||
console.log('[CONFIG] ═════════════════════════════════');
|
||||
console.log('[CONFIG] Home tab index:',homeTabIndex);
|
||||
@@ -5276,8 +5285,36 @@ function showPowerMenu(){
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow(){
|
||||
async function autheliaAuthenticate(){
|
||||
if(!autheliaURL||!autheliaUsername||!autheliaEncryptedPassword)return;
|
||||
try{
|
||||
const machineId=fs.readFileSync('/etc/machine-id','utf8').trim();
|
||||
const key=crypto.scryptSync(machineId,'kiosk-authelia-v1',32);
|
||||
const buf=Buffer.from(autheliaEncryptedPassword,'base64');
|
||||
const iv=buf.subarray(0,16);
|
||||
const enc=buf.subarray(16);
|
||||
const decipher=crypto.createDecipheriv('aes-256-cbc',key,iv);
|
||||
const password=Buffer.concat([decipher.update(enc),decipher.final()]).toString('utf8');
|
||||
|
||||
const res=await session.defaultSession.fetch(`${autheliaURL}/api/firstfactor`,{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json','User-Agent':'kiosk/1.0'},
|
||||
body:JSON.stringify({username:autheliaUsername,password,keepMeLoggedIn:true,requestMethod:'GET',targetURL:''})
|
||||
});
|
||||
const body=await res.json().catch(()=>({}));
|
||||
if(res.ok&&body.status==='OK'){
|
||||
console.log('[AUTHELIA] Authenticated as',autheliaUsername);
|
||||
}else{
|
||||
console.error('[AUTHELIA] Auth failed:',res.status,body.message||'');
|
||||
}
|
||||
}catch(e){
|
||||
console.error('[AUTHELIA] Error:',e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function createWindow(){
|
||||
tabs=loadConfig();
|
||||
await autheliaAuthenticate();
|
||||
|
||||
mainWindow=new BrowserWindow({
|
||||
fullscreen:true,
|
||||
@@ -9903,6 +9940,129 @@ show_easy_asterisk_status() {
|
||||
echo "────────────────────────────────────────────────────────────"
|
||||
}
|
||||
|
||||
configure_authelia() {
|
||||
clear
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " Authelia Auto-Login Setup"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo
|
||||
echo "Stores encrypted Authelia credentials so the kiosk"
|
||||
echo "authenticates automatically on every startup."
|
||||
echo "Password is encrypted with this machine's unique ID —"
|
||||
echo "the encrypted blob is useless on any other machine."
|
||||
echo
|
||||
|
||||
local config_file="$KIOSK_DIR/config.json"
|
||||
if [[ ! -f "$config_file" ]]; then
|
||||
echo "✗ config.json not found — run a full install first."
|
||||
read -r -p "Press Enter to return..." _; return
|
||||
fi
|
||||
|
||||
# Show current status
|
||||
local current_url current_user
|
||||
current_url=$(jq -r '.autheliaURL // ""' "$config_file" 2>/dev/null)
|
||||
current_user=$(jq -r '.autheliaUsername // ""' "$config_file" 2>/dev/null)
|
||||
if [[ -n "$current_url" ]]; then
|
||||
echo "Current config:"
|
||||
echo " URL: $current_url"
|
||||
echo " Username: $current_user"
|
||||
echo
|
||||
read -r -p "Overwrite existing Authelia config? [y/N]: " confirm
|
||||
[[ "$confirm" != "y" && "$confirm" != "Y" ]] && return
|
||||
echo
|
||||
fi
|
||||
|
||||
read -r -p "Authelia URL (e.g. https://auth.yourdomain.com): " authelia_url
|
||||
[[ -z "$authelia_url" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_url" ]] && return
|
||||
read -r -p "Authelia username: " authelia_user
|
||||
[[ -z "$authelia_user" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_user" ]] && return
|
||||
read -r -s -p "Authelia password: " authelia_pass
|
||||
echo
|
||||
[[ -z "$authelia_pass" ]] && echo "Cancelled." && read -r -p "Press Enter..." _; [[ -z "$authelia_pass" ]] && return
|
||||
|
||||
echo "Encrypting with machine ID..."
|
||||
local encrypted
|
||||
encrypted=$(node -e "
|
||||
const crypto=require('crypto'),fs=require('fs');
|
||||
const id=fs.readFileSync('/etc/machine-id','utf8').trim();
|
||||
const key=crypto.scryptSync(id,'kiosk-authelia-v1',32);
|
||||
const iv=crypto.randomBytes(16);
|
||||
const c=crypto.createCipheriv('aes-256-cbc',key,iv);
|
||||
const enc=Buffer.concat([c.update(process.argv[1],'utf8'),c.final()]);
|
||||
process.stdout.write(Buffer.concat([iv,enc]).toString('base64'));
|
||||
" "$authelia_pass" 2>/dev/null)
|
||||
|
||||
if [[ -z "$encrypted" ]]; then
|
||||
echo "✗ Encryption failed — is Node.js installed?"
|
||||
read -r -p "Press Enter..." _; return
|
||||
fi
|
||||
|
||||
local tmp
|
||||
tmp=$(mktemp)
|
||||
jq --arg url "$authelia_url" \
|
||||
--arg user "$authelia_user" \
|
||||
--arg enc "$encrypted" \
|
||||
'. + {autheliaURL: $url, autheliaUsername: $user, autheliaEncryptedPassword: $enc}' \
|
||||
"$config_file" > "$tmp" && mv "$tmp" "$config_file"
|
||||
sudo chown "$KIOSK_USER:$KIOSK_USER" "$config_file"
|
||||
|
||||
echo
|
||||
echo "✓ Authelia config saved (password encrypted, NOT stored in plain text)."
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " AUTHELIA SERVER-SIDE SETUP (Dockerized)"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo
|
||||
echo "1. Generate the argon2 password hash on your Docker host:"
|
||||
echo
|
||||
echo " docker run --rm authelia/authelia:latest \\"
|
||||
echo " authelia crypto hash generate argon2 \\"
|
||||
echo " --password 'yourpassword'"
|
||||
echo
|
||||
echo " Copy the \$argon2id\$... output — that is your hash."
|
||||
echo
|
||||
echo "2. Add a kiosk user to ~/docker/authelia/config/users.yml:"
|
||||
echo
|
||||
echo " kiosk:"
|
||||
echo " displayname: \"Kiosk Display\""
|
||||
echo " password: '\$argon2id\$v=19\$m=65536,t=3,p=4\$<paste hash here>'"
|
||||
echo " email: kiosk@local.com"
|
||||
echo " groups:"
|
||||
echo " - kiosk"
|
||||
echo
|
||||
echo "3. Add to ~/docker/authelia/config/configuration.yml:"
|
||||
echo
|
||||
echo " session:"
|
||||
echo " expiration: 1y"
|
||||
echo " inactivity: 90d"
|
||||
echo " remember_me: 1y"
|
||||
echo
|
||||
echo " access_control:"
|
||||
echo " default_policy: deny"
|
||||
echo " rules:"
|
||||
echo " - domain: '*.yourdomain.com'"
|
||||
echo " subject: 'group:kiosk'"
|
||||
echo " policy: one_factor"
|
||||
echo
|
||||
echo "4. Restart Authelia on your Docker host:"
|
||||
echo " docker compose restart authelia"
|
||||
echo
|
||||
echo "────────────────────────────────────────────────────────────"
|
||||
echo " NOTE: HTTP Basic Auth (per-site username/password) still"
|
||||
echo " works alongside Authelia for sites that use browser-popup"
|
||||
echo " authentication rather than Authelia SSO."
|
||||
echo "────────────────────────────────────────────────────────────"
|
||||
echo
|
||||
echo " To clear Authelia config later, remove autheliaURL /"
|
||||
echo " autheliaUsername / autheliaEncryptedPassword from:"
|
||||
echo " $config_file"
|
||||
echo
|
||||
read -r -p "Restart kiosk display now? [y/N]: " restart
|
||||
if [[ "$restart" == "y" || "$restart" == "Y" ]]; then
|
||||
sudo systemctl restart lightdm
|
||||
fi
|
||||
}
|
||||
|
||||
# Main Easy Asterisk addon menu
|
||||
addon_easy_asterisk_intercom() {
|
||||
while true; do
|
||||
@@ -11086,15 +11246,17 @@ addons_menu() {
|
||||
echo " 2. CUPS Printing"
|
||||
echo " 3. Remote Access (VNC/VPN)"
|
||||
echo " 4. Easy Asterisk Intercom"
|
||||
echo " 5. Authelia Auto-Login"
|
||||
echo " 0. Return"
|
||||
echo
|
||||
read -r -p "Choose [0-4]: " choice
|
||||
read -r -p "Choose [0-5]: " choice
|
||||
|
||||
case "$choice" in
|
||||
1) addon_lms_squeezelite ;;
|
||||
2) addon_cups ;;
|
||||
3) remote_access_menu ;;
|
||||
4) addon_easy_asterisk_intercom ;;
|
||||
5) configure_authelia ;;
|
||||
0) return ;;
|
||||
esac
|
||||
done
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user