Merge pull request #82 from outis1one/claude/gifted-bohr-dfzdig

Claude/gifted bohr dfzdig
This commit is contained in:
Outis
2026-06-15 11:56:26 -04:00
committed by GitHub
3 changed files with 11810 additions and 8 deletions
+96 -5
View File
@@ -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*
+165 -3
View File
@@ -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