2026-03-12 12:56:59 +00:00
|
|
|
#!/bin/bash
|
2026-06-12 03:00:15 -04:00
|
|
|
|
|
|
|
|
# Start a dedicated X server for the attached kiosk display.
|
|
|
|
|
/usr/bin/Xorg :0 vt1 -nolisten tcp -keeptty &
|
2026-03-12 12:56:59 +00:00
|
|
|
XPID=$!
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
export DISPLAY=:0
|
|
|
|
|
export HOME=/home/archipelago
|
|
|
|
|
|
|
|
|
|
X_READY=false
|
|
|
|
|
for _ in $(seq 1 30); do
|
|
|
|
|
if kill -0 "$XPID" 2>/dev/null && xrandr --query >/tmp/archipelago-kiosk-xrandr.txt 2>/dev/null; then
|
|
|
|
|
X_READY=true
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
sleep 0.5
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ "$X_READY" != "true" ]; then
|
|
|
|
|
echo 'ERROR: Xorg failed to become ready'
|
|
|
|
|
kill "$XPID" 2>/dev/null || true
|
2026-03-12 12:56:59 +00:00
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-21 12:46:15 -04:00
|
|
|
# Settings-managed display overrides (system.kiosk-display.set writes this
|
|
|
|
|
# file, then restarts the kiosk): may set ARCHIPELAGO_KIOSK_SCALE,
|
|
|
|
|
# ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH or ARCHIPELAGO_KIOSK_MAX_WIDTH.
|
|
|
|
|
[ -f /etc/archipelago/kiosk-display.conf ] && . /etc/archipelago/kiosk-display.conf
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
KIOSK_SAFE_AREA_X_PX=${ARCHIPELAGO_KIOSK_SAFE_AREA_X_PX:-}
|
|
|
|
|
KIOSK_SAFE_AREA_Y_PX=${ARCHIPELAGO_KIOSK_SAFE_AREA_Y_PX:-}
|
|
|
|
|
|
2026-07-13 21:59:41 +01:00
|
|
|
# Actual mode of the kiosk output — configure_display overwrites these so
|
|
|
|
|
# Chromium's window matches the panel instead of assuming 1080p (a 1366x768
|
|
|
|
|
# laptop panel otherwise gets a clipped oversized window).
|
|
|
|
|
KIOSK_MODE_W=1920
|
|
|
|
|
KIOSK_MODE_H=1080
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
configure_display() {
|
|
|
|
|
command -v xrandr >/dev/null 2>&1 || return 0
|
|
|
|
|
|
|
|
|
|
local output mode internal width height
|
|
|
|
|
output=$(awk '/ connected/ && $1 !~ /^eDP|^LVDS/{print $1; exit}' /tmp/archipelago-kiosk-xrandr.txt)
|
|
|
|
|
[ -n "$output" ] || output=$(awk '/ connected/{print $1; exit}' /tmp/archipelago-kiosk-xrandr.txt)
|
|
|
|
|
[ -n "$output" ] || return 0
|
|
|
|
|
|
2026-07-01 11:59:21 +00:00
|
|
|
# Pick the EDID-preferred ("+") mode, falling back to the first-listed
|
|
|
|
|
# mode (EDID lists native first). Deliberately ignore "*" (currently
|
|
|
|
|
# active) — trusting "active" lets a bad clone/mirror state from a
|
|
|
|
|
# previous boot perpetuate itself forever instead of self-healing.
|
2026-06-12 03:00:15 -04:00
|
|
|
mode=$(awk -v out="$output" '
|
|
|
|
|
$1 == out { active = 1; next }
|
|
|
|
|
active && /^[[:space:]]+[0-9]+x[0-9]+/ {
|
2026-07-01 11:59:21 +00:00
|
|
|
if ($0 ~ /\+/ && !preferred) { preferred = $1 }
|
2026-06-12 03:00:15 -04:00
|
|
|
if (!first) first = $1
|
|
|
|
|
}
|
|
|
|
|
active && /^[^[:space:]]/ { active = 0 }
|
2026-07-01 11:59:21 +00:00
|
|
|
END { if (preferred) print preferred; else if (first) print first }
|
2026-06-12 03:00:15 -04:00
|
|
|
' /tmp/archipelago-kiosk-xrandr.txt)
|
|
|
|
|
[ -n "$mode" ] || mode=1920x1080
|
|
|
|
|
|
2026-07-21 12:46:15 -04:00
|
|
|
# Optional resolution cap for weak hardware: ARCHIPELAGO_KIOSK_MAX_WIDTH=1920
|
|
|
|
|
# drops a 4K panel to its best <=1920-wide mode (the TV upscales) so the
|
|
|
|
|
# software rasterizer paints 1/4 the pixels. Off by default — native mode
|
|
|
|
|
# is sharp and fits the tile budget now that --window-size is in DIPs.
|
|
|
|
|
local max_w=${ARCHIPELAGO_KIOSK_MAX_WIDTH:-0}
|
|
|
|
|
if [ "$max_w" -gt 0 ] 2>/dev/null && [ "${mode%x*}" -gt "$max_w" ] 2>/dev/null; then
|
|
|
|
|
local capped
|
|
|
|
|
capped=$(awk -v out="$output" -v maxw="$max_w" '
|
|
|
|
|
$1 == out { active = 1; next }
|
|
|
|
|
active && /^[[:space:]]+[0-9]+x[0-9]+/ {
|
|
|
|
|
split($1, wh, "x")
|
|
|
|
|
if (wh[1] + 0 <= maxw && wh[1] + 0 > best_w) { best_w = wh[1] + 0; best = $1 }
|
|
|
|
|
}
|
|
|
|
|
active && /^[^[:space:]]/ { active = 0 }
|
|
|
|
|
END { if (best) print best }
|
|
|
|
|
' /tmp/archipelago-kiosk-xrandr.txt)
|
|
|
|
|
[ -n "$capped" ] && mode=$capped
|
|
|
|
|
fi
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
# Kiosk should use one native output. A spanning desktop makes Chromium land
|
|
|
|
|
# on the laptop panel or stretch across both outputs.
|
|
|
|
|
for internal in $(awk '/ connected/ && $1 ~ /^eDP|^LVDS/{print $1}' /tmp/archipelago-kiosk-xrandr.txt); do
|
|
|
|
|
[ "$internal" = "$output" ] || xrandr --output "$internal" --off 2>/dev/null || true
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
xrandr --output "$output" \
|
|
|
|
|
--primary \
|
|
|
|
|
--mode "$mode" \
|
|
|
|
|
--pos 0x0 \
|
|
|
|
|
--scale 1x1 \
|
|
|
|
|
--panning 0x0 \
|
|
|
|
|
--transform none 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
width=${mode%x*}
|
|
|
|
|
height=${mode#*x}
|
|
|
|
|
case "$width:$height" in *[!0-9:]*|:*) width=1920; height=1080 ;; esac
|
2026-07-13 21:59:41 +01:00
|
|
|
KIOSK_MODE_W=$width
|
|
|
|
|
KIOSK_MODE_H=$height
|
2026-03-12 12:56:59 +00:00
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
# Browser safe-area fallback for TVs that crop edges. Driver underscan is
|
|
|
|
|
# preferable, but many Intel HDMI outputs do not expose that property.
|
|
|
|
|
KIOSK_SAFE_AREA_X_PX=${KIOSK_SAFE_AREA_X_PX:-$((width * 3 / 100))}
|
|
|
|
|
KIOSK_SAFE_AREA_Y_PX=${KIOSK_SAFE_AREA_Y_PX:-$((height * 3 / 100))}
|
|
|
|
|
}
|
2026-03-12 12:56:59 +00:00
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
configure_display
|
2026-03-12 12:56:59 +00:00
|
|
|
|
2026-07-20 17:48:14 -04:00
|
|
|
# --- Kiosk UI scaling for large / high-res displays -----------------------
|
|
|
|
|
# REVERT: set env ARCHIPELAGO_KIOSK_SCALE=1 (per-node, no rebuild), or restore
|
|
|
|
|
# the hardcoded --force-device-scale-factor=1 below to disable entirely.
|
|
|
|
|
#
|
|
|
|
|
# A big TV reports its full native resolution as the CSS viewport, so a 4K
|
|
|
|
|
# panel becomes a 3840px-wide viewport and the UI renders tiny — and a
|
|
|
|
|
# keyboard-less kiosk can't zoom. Derive Chromium's device-scale-factor from
|
|
|
|
|
# the detected panel width so the *effective* CSS viewport lands near a
|
2026-07-21 12:46:15 -04:00
|
|
|
# comfortable target. Panels >=2560 wide get a 1920-wide layout (4K -> scale
|
|
|
|
|
# 2.0 — user-validated on a 72" TV: spacious desktop layout, 2x sharp);
|
|
|
|
|
# smaller panels get a 1280-wide layout (1920 -> 1.50, laptops -> 1.0).
|
|
|
|
|
if [ -z "${ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH:-}" ]; then
|
|
|
|
|
if [ "$KIOSK_MODE_W" -ge 2560 ] 2>/dev/null; then
|
|
|
|
|
ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1920
|
|
|
|
|
else
|
|
|
|
|
ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1280
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
KIOSK_TARGET_CSS_WIDTH=$ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH
|
2026-07-20 17:48:14 -04:00
|
|
|
if [ -n "${ARCHIPELAGO_KIOSK_SCALE:-}" ]; then
|
|
|
|
|
KIOSK_SCALE=$ARCHIPELAGO_KIOSK_SCALE
|
|
|
|
|
else
|
|
|
|
|
KIOSK_SCALE=$(awk -v w="$KIOSK_MODE_W" -v t="$KIOSK_TARGET_CSS_WIDTH" \
|
|
|
|
|
'BEGIN{if(t<=0)t=1280; s=w/t; s=int(s*4+0.5)/4; if(s<1)s=1; if(s>3)s=3; printf "%.2f", s}')
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-21 12:46:15 -04:00
|
|
|
# Chromium's --window-size is in DIPs (CSS px), not physical pixels: at scale S
|
|
|
|
|
# the window paints S x larger. This X session has no window manager, so
|
|
|
|
|
# --kiosk/--start-fullscreen cannot snap an oversized window back to the panel
|
|
|
|
|
# — it just hangs off the right/bottom edges (content cropped, background art
|
|
|
|
|
# offscreen). Size the window in DIPs so DIPs x scale = the panel exactly.
|
|
|
|
|
KIOSK_WIN_W=$(awk -v w="$KIOSK_MODE_W" -v s="$KIOSK_SCALE" 'BEGIN{printf "%d", w/s}')
|
|
|
|
|
KIOSK_WIN_H=$(awk -v h="$KIOSK_MODE_H" -v s="$KIOSK_SCALE" 'BEGIN{printf "%d", h/s}')
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
xhost +SI:localuser:archipelago 2>/dev/null || true
|
|
|
|
|
xsetroot -solid black 2>/dev/null || true
|
|
|
|
|
xset s off 2>/dev/null || true
|
|
|
|
|
xset -dpms 2>/dev/null || true
|
|
|
|
|
xset s noblank 2>/dev/null || true
|
2026-03-12 12:56:59 +00:00
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
pkill -u archipelago -f 'chromium.*localhost' 2>/dev/null || true
|
2026-04-02 20:28:53 +01:00
|
|
|
sleep 1
|
|
|
|
|
|
2026-07-01 17:02:43 +00:00
|
|
|
# GPU vs headless (#36, choppy-audio incident 2026-06-28). --enable-gpu-rasterization
|
|
|
|
|
# spins a dedicated GPU process at 55-92% CPU even on real GPU hardware (Intel HD 5500)
|
|
|
|
|
# because under X11 it falls back to software compositing anyway — that CPU
|
|
|
|
|
# starvation is what caused choppy HDMI audio. --in-process-gpu avoids the
|
|
|
|
|
# separate process; GpuRasterization is also disabled via --disable-features below.
|
|
|
|
|
# On a GPU-less / headless server (no /dev/dri), disable GPU entirely instead.
|
2026-06-16 11:10:26 -04:00
|
|
|
if [ -e /dev/dri/card0 ] || [ -e /dev/dri/renderD128 ]; then
|
2026-07-01 17:02:43 +00:00
|
|
|
GPU_FLAGS="--in-process-gpu --num-raster-threads=1"
|
2026-06-16 11:10:26 -04:00
|
|
|
else
|
|
|
|
|
GPU_FLAGS="--disable-gpu --num-raster-threads=1"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-01 16:22:45 +00:00
|
|
|
ARCHIPELAGO_UID=$(id -u archipelago)
|
|
|
|
|
|
2026-03-12 12:56:59 +00:00
|
|
|
while true; do
|
2026-07-17 11:31:43 -04:00
|
|
|
# A profile lock left by a previous boot encodes <hostname>-<pid>; after a
|
|
|
|
|
# hostname change (node rename) Chromium reads it as another computer
|
|
|
|
|
# holding the profile and refuses to start — with --noerrdialogs that is an
|
|
|
|
|
# invisible failure and the kiosk black-screens forever. Any Chromium that
|
|
|
|
|
# owned the lock is dead by now (pkill above / previous loop iteration).
|
|
|
|
|
rm -f /var/lib/archipelago/chromium-kiosk/Singleton{Lock,Cookie,Socket}
|
2026-07-01 16:22:45 +00:00
|
|
|
# XDG_RUNTIME_DIR must be passed explicitly — without it Chromium's audio
|
|
|
|
|
# backend can't find PipeWire-Pulse's socket at /run/user/<uid>/pulse/native,
|
|
|
|
|
# falls back to raw ALSA "default", fails to connect, and produces no audio
|
|
|
|
|
# at all with no visible error (--noerrdialogs suppresses it).
|
2026-07-23 15:30:09 -04:00
|
|
|
# OverlayScrollbar: thin auto-hiding scrollbars (the Chrome-on-a-remote-
|
|
|
|
|
# device look) instead of classic X11 scrollbar chrome — a kiosk TV showed
|
|
|
|
|
# a permanent fat scrollbar on scrollable views (Peers).
|
2026-07-20 16:57:46 -04:00
|
|
|
# Force a DARK color-scheme preference. The main UI hardcodes its dark
|
|
|
|
|
# theme, but the bundled AIUI app themes via `@media (prefers-color-scheme)`
|
|
|
|
|
# and defaults to its LIGHT variant (white panels) when the browser reports
|
|
|
|
|
# no preference — which a minimal kiosk X session does. Two independent dark
|
|
|
|
|
# signals so this survives a Chromium enum change: GTK_THEME is version-
|
|
|
|
|
# independent and can never force light (worst case: no effect), and the
|
|
|
|
|
# blink-settings flag (0 = kDark in modern Chromium) reinforces it. Neither
|
|
|
|
|
# can regress the always-dark main UI.
|
|
|
|
|
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago GTK_THEME=Adwaita:dark XDG_RUNTIME_DIR=/run/user/$ARCHIPELAGO_UID chromium --kiosk \
|
2026-06-12 03:00:15 -04:00
|
|
|
--app=http://localhost/kiosk?safe_area_x=${KIOSK_SAFE_AREA_X_PX:-0}\&safe_area_y=${KIOSK_SAFE_AREA_Y_PX:-0} \
|
2026-07-20 16:57:46 -04:00
|
|
|
--blink-settings=preferredColorScheme=0 \
|
2026-03-31 02:42:44 +01:00
|
|
|
--noerrdialogs \
|
|
|
|
|
--disable-infobars \
|
|
|
|
|
--disable-translate \
|
|
|
|
|
--no-first-run \
|
|
|
|
|
--check-for-update-interval=31536000 \
|
2026-07-01 17:02:43 +00:00
|
|
|
--disable-features=TranslateUI,MetricsReporting,AutofillServerCommunication,PasswordManagerEnabled,GpuRasterization \
|
2026-07-23 15:30:09 -04:00
|
|
|
--enable-features=OverlayScrollbar \
|
2026-03-31 02:42:44 +01:00
|
|
|
--disable-session-crashed-bubble \
|
|
|
|
|
--disable-save-password-bubble \
|
|
|
|
|
--disable-suggestions-service \
|
|
|
|
|
--disable-component-update \
|
2026-06-16 11:10:26 -04:00
|
|
|
$GPU_FLAGS \
|
fix: BUILD_VERSION from Cargo.toml, kiosk scaling, new apps, Rust warnings
Critical:
- BUILD_VERSION was hardcoded as "1.3.0-alpha" — now reads from Cargo.toml
This caused ALL ISOs to show v1.3.0 regardless of actual binary version
Kiosk:
- Remove --disable-gpu flags (broke display scaling on some monitors)
- Add --start-fullscreen --window-size for reliable fullscreen
New apps:
- Nostr VPN, FIPS, Routstr, noStrudel, BotFights, NWNN, 484 Kitchen,
Call the Operator, Arch Presentation, Syntropy Institute, T-0
Rust: suppress dead_code and unused_assignments warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:35:52 +01:00
|
|
|
--renderer-process-limit=2 \
|
2026-07-21 12:46:15 -04:00
|
|
|
--window-size=${KIOSK_WIN_W},${KIOSK_WIN_H} \
|
fix: BUILD_VERSION from Cargo.toml, kiosk scaling, new apps, Rust warnings
Critical:
- BUILD_VERSION was hardcoded as "1.3.0-alpha" — now reads from Cargo.toml
This caused ALL ISOs to show v1.3.0 regardless of actual binary version
Kiosk:
- Remove --disable-gpu flags (broke display scaling on some monitors)
- Add --start-fullscreen --window-size for reliable fullscreen
New apps:
- Nostr VPN, FIPS, Routstr, noStrudel, BotFights, NWNN, 484 Kitchen,
Call the Operator, Arch Presentation, Syntropy Institute, T-0
Rust: suppress dead_code and unused_assignments warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:35:52 +01:00
|
|
|
--window-position=0,0 \
|
|
|
|
|
--start-fullscreen \
|
2026-07-20 17:48:14 -04:00
|
|
|
--force-device-scale-factor=${KIOSK_SCALE} \
|
2026-03-31 02:42:44 +01:00
|
|
|
--disable-background-networking \
|
|
|
|
|
--disable-background-timer-throttling \
|
|
|
|
|
--disable-backgrounding-occluded-windows \
|
fix: container DNS, nginx chown, onboarding guard, seed UX, install flow
Backend:
- Add --add-host host.containers.internal:host-gateway to LND and Bitcoin
Knots containers (fixes DNS resolution failure in rootless podman)
- Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers
(fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui)
- Add hostadd to Rust Podman API client for web UI container installs
- Add Chromium privacy flags to kiosk launcher (disable telemetry)
Frontend:
- Fix onboarding reset on raw IP visits (trust localStorage as first-class
signal, skip boot screen when server is up but not onboarded)
- Fix seed regression: persist challenge indices in sessionStorage so going
back from Verify doesn't change which words are asked
- Remove glass container from seed Generate/Verify/Restore screens
- Add Back button to Restore from Seed screen
- Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange)
- Add ElectrumX to curated app list with correct .webp icon
- Install flow: navigate to My Apps immediately with toast, hide
installed/installing apps from marketplace and discover views
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:06:57 +01:00
|
|
|
--disable-breakpad \
|
|
|
|
|
--disable-metrics \
|
|
|
|
|
--disable-metrics-reporting \
|
|
|
|
|
--disable-domain-reliability \
|
2026-07-16 15:38:22 -04:00
|
|
|
--js-flags="--max-old-space-size=256" \
|
2026-04-02 11:10:08 +01:00
|
|
|
--user-data-dir=/var/lib/archipelago/chromium-kiosk
|
2026-03-12 12:56:59 +00:00
|
|
|
sleep 3
|
|
|
|
|
done
|
|
|
|
|
|
2026-06-12 03:00:15 -04:00
|
|
|
kill "$XPID" 2>/dev/null || true
|