From 2e2d951c9be343adf6f1a86ee971e85ceeadcba1 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 24 Oct 2026 21:45:45 +0000 Subject: [PATCH] W3BS OS 1.2.7: boot to a locked desktop, fleet screen out of the box A server install of the desktop showed nothing on w3bs.org/fleet: nobody logs in at a VPS console, so there was no Wayland session for a VNC server to attach to, and nothing set one up. - greetd initial_session logs the first user in at boot with W3BS_LOCK_AT_START=1; w3bs-hyprland runs hyprlock as soon as Hyprland is up (a plain Wayland client: in a Lua config `hyprctl dispatch` takes Lua). --no-autologin keeps the greeter. - Every Hyprland session runs wayvnc on $XDG_RUNTIME_DIR/w3bs-vnc.sock (0700, no TCP port, so other local users cannot drive it), restarted when it exits (it does when the lock screen goes away: "Fatal error while capturing"). - wayvnc in the desktop packages. - --fleet-token fj_...: w3bs-fleet-join.service joins at first boot as the first user (lingering), with --screen control --vnc unix:/run/user//w3bs-vnc.sock on a desktop. - dtest: unlock the boot lock screen from the keyboard; check autologin, the unlock and the private VNC socket. Verified on dev1.w3bs.org (Luxvps, bochs VGA): boots to the hyprlock screen, VNC over the socket shows it, typing the password over VNC unlocks to W3BS DE, wayvnc survives the unlock, `w3bs fleet screen` reports the socket answers. Co-Authored-By: Claude Opus 5.5 --- docs/fleet-screen.md | 11 ++++ os/debian/README.md | 9 +++ .../usr/local/sbin/w3bs-os-install | 65 ++++++++++++++++++- .../usr/share/w3bs-os/desktop/w3bs-hyprland | 37 +++++++++++ os/debian/test/dtest.py | 13 +++- 5 files changed, 131 insertions(+), 4 deletions(-) diff --git a/docs/fleet-screen.md b/docs/fleet-screen.md index d7bbbe6..b9879b6 100644 --- a/docs/fleet-screen.md +++ b/docs/fleet-screen.md @@ -145,6 +145,17 @@ Docker, `w0vncserver`, the real /fleet page in Chromium. KDE's own `krfb` 26.08 does not work for this: it never opens its port unless started as `krfb-virtualmonitor`, which shares a new, empty monitor instead of the desktop. +### W3BS OS (1.2.7 and later) + +A W3BS OS desktop shares its screen out of the box: every W3BS DE session runs wayvnc on a socket +only its user can open (`/run/user//w3bs-vnc.sock`, no TCP port), and the machine boots +straight to a locked desktop, so the page shows the lock screen and, with control, you type the +password there. Install with `--fleet-token fj_...` to join at first boot, or join later: + +```sh +w3bs fleet join fj_... --screen control --vnc unix:/run/user/$(id -u)/w3bs-vnc.sock +``` + ### Other desktops (not yet tested by us) - **GNOME (Wayland):** `w0vncserver` uses the same portal and should work; GNOME shows its diff --git a/os/debian/README.md b/os/debian/README.md index 05df2e2..7728c89 100644 --- a/os/debian/README.md +++ b/os/debian/README.md @@ -47,6 +47,15 @@ come with it, and the **Agentic Desktop Layer** (`w3bs-desktop`, glibc build in `w3bs-de autostart` starts its broker and tray, and agent access stays OFF until the user turns it on. +**Login and fleet screen (1.2.7).** At boot greetd's `initial_session` logs the first user in on +its own with `W3BS_LOCK_AT_START=1`: `w3bs-hyprland` runs hyprlock as soon as Hyprland is up, so +the console shows a lock screen, never an open desktop (`--no-autologin` keeps the tuigreet +greeter). Every Hyprland session also runs wayvnc on `$XDG_RUNTIME_DIR/w3bs-vnc.sock` (0700, no TCP +port; restarted when it exits, which it does when the lock screen goes away). `w3bs fleet screen on +--control --vnc unix:/run/user//w3bs-vnc.sock` relays it to w3bs.org/fleet, where the owner +sees the lock screen and can type the password. `--fleet-token fj_...` does that join at first boot +(`w3bs-fleet-join.service`, lingering user service of the first user). + ## Tests `test/dtest.py` boots the ISO in QEMU (KVM when available) and checks the live system, an diff --git a/os/debian/config/includes.chroot/usr/local/sbin/w3bs-os-install b/os/debian/config/includes.chroot/usr/local/sbin/w3bs-os-install index e0b079a..221db3a 100755 --- a/os/debian/config/includes.chroot/usr/local/sbin/w3bs-os-install +++ b/os/debian/config/includes.chroot/usr/local/sbin/w3bs-os-install @@ -49,6 +49,11 @@ machine needs the internet (DHCP and IPv6 are automatic; --ip for a static addre --ip ADDR/PREFIX --gateway GW [--dns "A B"] static IPv4 instead of DHCP --ip6 ADDR/PREFIX --gateway6 GW6 static IPv6 as well (often fe80::1) --iface NAME the interface for --ip/--ip6 (default: the first wired one) + --no-autologin a desktop shows the greeter at boot (default: the first user is logged + in on its own, straight to the lock screen, so the screen can be + watched and unlocked from w3bs.org/fleet) + --fleet-token TOKEN join your fleet on w3bs.org/fleet at first boot (the fj_... token from + that page), sharing the desktop's screen there (view and control) --serial-console also a login on ttyS0 and kernel messages there --no-reboot do not offer to reboot at the end --list-disks print the disks the installer would offer and exit @@ -144,6 +149,7 @@ wired_interfaces() { DEFAULTS="" DISK="" YES="" ENCRYPT=1 HOSTNAME_="" DOMAIN="" TIMEZONE="" KEYMAP="" USERS="" FULLNAME="" PASSWORD_FILE="" ROOT_PASSWORD_FILE="" PASSPHRASE_FILE="" NO_REBOOT="" SERIAL="" STATIC_IP="" STATIC_GW="" STATIC_DNS="" STATIC_IP6="" STATIC_GW6="" NET_IFACE="" DESKTOP="" +AUTOLOGIN=1 FLEET_TOKEN="" while [ $# -gt 0 ]; do case "$1" in --defaults) DEFAULTS=1 ;; @@ -167,6 +173,8 @@ while [ $# -gt 0 ]; do --gateway6) STATIC_GW6=${2:-}; shift ;; --iface) NET_IFACE=${2:-}; shift ;; --serial-console) SERIAL=1 ;; + --no-autologin) AUTOLOGIN="" ;; + --fleet-token) FLEET_TOKEN=${2:-}; shift ;; --no-reboot) NO_REBOOT=1 ;; --desktop) DESKTOP=${2:-}; shift ;; --server) DESKTOP=none ;; @@ -270,6 +278,8 @@ USERS=$(echo $USERS) for u in $USERS; do [[ "$u" =~ ^[a-z_][a-z0-9_-]{0,30}$ ]] && [ "$u" != root ] || fail "invalid user name $u"; done [ -n "$FULLNAME" ] || ask FULLNAME "Full name" "W3BS User" case "$FULLNAME" in *:*) fail "the full name cannot contain ':'" ;; esac +case "$FLEET_TOKEN" in ''|fj_*) ;; *) fail "--fleet-token takes the fj_... token from w3bs.org/fleet" ;; esac +FIRST_USER=${USERS%% *} if [ -z "$DISK" ]; then if [ -n "$DEFAULTS" ]; then DISK=$(largest_disk) || true @@ -323,6 +333,8 @@ $(lsblk -no NAME,SIZE,FSTYPE "$DISK" 2>/dev/null | sed 's/^/ Users $USERS (zsh, sudo) Desktop $( [ "$DESKTOP" = hyprland ] && echo "W3BS DE $(cat /usr/share/w3bs-os/de/share/VERSION 2>/dev/null) on Hyprland 0.55 (Debian backports): greetd login, Firefox, TronBrowser" || echo "none (server)" ) Agent access $( [ "$DESKTOP" = hyprland ] && echo "Agentic Desktop Layer installed, agent access OFF until you turn it on in the tray" || echo "-" ) + Login $( [ "$DESKTOP" != hyprland ] && echo "-" || { [ -n "$AUTOLOGIN" ] && echo "$FIRST_USER logged in at boot, desktop locked (--no-autologin: greeter)" || echo "greeter"; } ) + Fleet $( [ -n "$FLEET_TOKEN" ] && echo "joins w3bs.org/fleet at first boot as $FIRST_USER$( [ "$DESKTOP" = hyprland ] && echo ", screen shared (view and control)" )" || echo "not joined (w3bs fleet join later)" ) SSH on (password logins for users; root by key only) Dev tools w3bs CLI, curl, wget, git, mise (node, bun, pnpm), readm3, hqterm + hqsh, threatcrush Box setup cli-tools root-ubuntu.sh at first boot: nginx pages, firewall, oh-my-zsh, tmux @@ -503,7 +515,7 @@ EOF greetd tuigreet waybar foot fuzzel sway-notification-center swaybg swayidle swaylock grim slurp wl-clipboard \ xwayland brightnessctl pavucontrol fonts-dejavu fonts-noto-core fonts-font-awesome thunar libnotify-bin \ pipewire pipewire-pulse wireplumber firefox-esr qt6-wayland polkitd xdg-desktop-portal-gtk sway \ - libgl1-mesa-dri dbus-user-session dbus-bin >>"$LOG" 2>&1 \ + libgl1-mesa-dri dbus-user-session dbus-bin wayvnc >>"$LOG" 2>&1 \ || fail "cannot install the desktop packages (see $LOG)" # The desktop is W3BS DE (w3bs.org/de): its files live in each user's home and update with # `w3bs-de update`; the copy on this ISO (same release) is kept in /usr/share/w3bs-os/de for @@ -566,6 +578,19 @@ vt = 1 command = "tuigreet --time --remember --asterisks --greeting 'W3BS OS · W3BS DE · log in as $_names (user name, then password)' --cmd /usr/local/bin/w3bs-hyprland" user = "_greetd" EOF + # The first user is logged in at boot, straight to the lock screen (w3bs-hyprland runs + # hyprlock when W3BS_LOCK_AT_START is set, and shares the session over VNC on a socket only + # that user can open): the console and w3bs.org/fleet show a lock screen, not a text greeter. + if [ -n "$AUTOLOGIN" ]; then + cat >>"$TARGET/etc/greetd/config.toml" <>"$LOG" 2>&1 in_target systemctl enable greetd.service >>"$LOG" 2>&1 || fail "cannot enable greetd" in_target systemctl set-default graphical.target >>"$LOG" 2>&1 @@ -642,6 +667,44 @@ TimeoutStartSec=3600 WantedBy=multi-user.target EOF in_target systemctl enable w3bs-first-boot.service >>"$LOG" 2>&1 +if [ -n "$FLEET_TOKEN" ]; then + # The fleet agent is a user service of the first user (lingering, so it runs without a + # login); on a desktop it relays the screen from the session's VNC socket. + install -d -m 0700 "$TARGET/etc/w3bs-os" + ( umask 077; printf '%s\n' "$FLEET_TOKEN" >"$TARGET/etc/w3bs-os/fleet-token" ) + _screen="" + [ "$DESKTOP" = hyprland ] && _screen='--screen control --vnc unix:/run/user/$uid/w3bs-vnc.sock' + cat >"$TARGET/usr/local/sbin/w3bs-os-fleet-join" <"$TARGET/etc/systemd/system/w3bs-fleet-join.service" <>"$LOG" 2>&1 +fi step "Installing developer tools (mise: node, bun, pnpm; readm3; hqterm; threatcrush)" cat >"$TARGET/etc/profile.d/w3bs-dev.sh" <<'EOF' diff --git a/os/debian/config/includes.chroot/usr/share/w3bs-os/desktop/w3bs-hyprland b/os/debian/config/includes.chroot/usr/share/w3bs-os/desktop/w3bs-hyprland index 7b31a64..bc79394 100755 --- a/os/debian/config/includes.chroot/usr/share/w3bs-os/desktop/w3bs-hyprland +++ b/os/debian/config/includes.chroot/usr/share/w3bs-os/desktop/w3bs-hyprland @@ -58,6 +58,42 @@ if [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/bus" ]; then systemctl --user import-environment XDG_CURRENT_DESKTOP XDG_SESSION_TYPE 2>/dev/null fi +rt=${XDG_RUNTIME_DIR:-/run/user/$(id -u)} +# Once this session's Hyprland is up: lock it if greetd logged us in on its own (initial_session +# sets W3BS_LOCK_AT_START, so the console shows a lock screen, not an open desktop), and share it +# over VNC on a socket only this user can open ($rt is 0700), which `w3bs fleet screen` relays to +# w3bs.org/fleet. Nothing listens on a TCP port. +session_helpers() { + mark=$rt/w3bs-session.mark + : >"$mark" + ( + i=0 + while [ "$i" -lt 90 ]; do + i=$((i + 1)); sleep 1 + sig=$(find "$rt/hypr" -mindepth 1 -maxdepth 1 -type d -newer "$mark" 2>/dev/null | head -n 1) + [ -n "$sig" ] && [ -S "$sig/.socket.sock" ] || continue + export HYPRLAND_INSTANCE_SIGNATURE="${sig##*/}" + sock=$(cd "$rt" && ls -t wayland-[0-9] 2>/dev/null | head -n 1) + [ -n "$sock" ] && export WAYLAND_DISPLAY="$sock" + if [ "${W3BS_LOCK_AT_START:-}" = 1 ]; then + # A plain Wayland client: no `hyprctl dispatch` (its argument is Lua in a Lua config). + hyprlock --config "$HOME/.local/share/w3bs-de/default/hypr/hyprlock.conf" >>"$log" 2>&1 & + fi + # wayvnc exits when the output it captures changes (the lock screen going away does it): + # start it again for as long as this Hyprland is running. + if command -v wayvnc >/dev/null 2>&1; then + while [ -S "$sig/.socket.sock" ]; do + rm -f "$rt/w3bs-vnc.sock" + wayvnc --unix-socket "$rt/w3bs-vnc.sock" >>"$log" 2>&1 + sleep 1 + done + rm -f "$rt/w3bs-vnc.sock" + fi + exit 0 + done + ) & +} + gpu() { for c in /sys/class/drm/card[0-9]*; do [ -e "$c/device" ] || continue @@ -109,6 +145,7 @@ EOF run() { start=$(date +%s) { echo "=== $(date) $*"; echo "vm=${vm:-none} drivers=$(gpu)"; } >>"$log" + [ -z "${W3BS_SESSION_CMD:-}" ] && session_helpers env "$@" $bus ${W3BS_SESSION_CMD:-/usr/local/bin/w3bs-de session} >>"$log" 2>&1 rc=$? # Copy Hyprland's own log (it lives under the runtime dir, gone at logout). diff --git a/os/debian/test/dtest.py b/os/debian/test/dtest.py index 34e20f5..d716d16 100644 --- a/os/debian/test/dtest.py +++ b/os/debian/test/dtest.py @@ -95,9 +95,10 @@ def check(name, ok, detail=""): vm.expect(r"[Pp]assphrase|unlock disk", 300); time.sleep(2); vm.send("w3bs-luks-pass\n") vm.expect(r"\S+ login: ", 600); time.sleep(3) if a.desktop: - # The greeter (tuigreet) on the screen: user name, then password. - time.sleep(8); vm.screenshot(os.path.join(a.out, f"greeter-{a.firmware}.png")) - vm.sendkeys("w3bs\n"); time.sleep(2); vm.sendkeys("w3bs-test-pass\n"); time.sleep(70) + # The first user is logged in at boot, straight to the lock screen (hyprlock): the + # password unlocks it. + time.sleep(90); vm.screenshot(os.path.join(a.out, f"locked-{a.firmware}.png")) + vm.sendkeys("w3bs-test-pass\n"); time.sleep(15) vm.screenshot(os.path.join(a.out, f"desktop-{a.firmware}.png")) vm.send("w3bs\n"); vm.expect(r"[Pp]assword: ", 60); vm.send("w3bs-test-pass\n") time.sleep(15); vm.send("PS1='> '; PROMPT='> '\n"); time.sleep(2) @@ -125,6 +126,12 @@ def check(name, ok, detail=""): check("Waybar", re.search(r"WAYBAR=\d", o) is not None, o) check("notification center (swaync)", re.search(r"SWAYNC=\d", o) is not None, o) check("Hyprland config has no errors", "CFGERR=0" in o, o) + rc, o = vm.sh("grep -c '^user = \"w3bs\"' /etc/greetd/config.toml | sed 's/^/AUTOLOGIN=/'; " + "grep -c 'Unlocked' ~/.local/state/w3bs-os/hyprland-session.log | sed 's/^/UNLOCKED=/'; " + "test -S /run/user/1000/w3bs-vnc.sock && echo VNCSOCK=1; stat -c VNCMODE=%a /run/user/1000", 60) + check("autologin at boot (greetd initial_session)", "AUTOLOGIN=1" in o, o) + check("boots to the lock screen, unlocked from the keyboard", re.search(r"UNLOCKED=[1-9]", o) is not None, o) + check("VNC on the session's private socket (fleet screen)", "VNCSOCK=1" in o and "VNCMODE=700" in o, o) rc, o = vm.sh("test -x ~/.local/opt/hqterm-desktop/app/hqterm-desktop -o -x ~/.local/opt/hqterm-desktop/hqterm-desktop.AppImage && echo HQDESK=1; grep -c '^Exec' ~/.local/share/applications/hqterm.desktop | sed 's/^/HQENTRY=/'", 60) check("hqterm desktop app + launcher entry", "HQDESK=1" in o and "HQENTRY=1" in o, o) rc, o = vm.sh("w3bs-de doctor; echo DOCTOR=$?", 120)