Files
Omarchy-Stream/docs/FOLLOWUPS.md
Levi Woodard ab23107300 Add runtime status checker + headless/X11 docs; distro-support refinements
- status.sh: runtime health check (service state, boot wiring, display backend auto-detect, encoder, ports, web UI, /dev/uinput, pairing) ending in a g2g verdict or concrete TODO list

- docs: TROUBLESHOOTING §12 (headless graphical-session.target boot trap) + §13 (X11/NVENC path & stale wlr drop-in env conflict); ARCHITECTURE capture-backend comparison; FOLLOWUPS P3 (installer X11/Ubuntu support); README diagnostics pointer

- lib/{config,packages,permissions,service}.sh, files/sway-headless.service: in-progress Debian/Ubuntu + headless support refinements

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:57:03 +00:00

10 KiB
Raw Blame History

Outstanding follow-ups

Tracked work that isn't on main yet. Each item lists the symptom, current workaround, the fix sketch, and rough complexity so it can be picked up in any order.


P1 — Inhibit screensaver during streaming

Symptom: Omarchy's hypridle fires omarchy-screensaver after the host's idle timeout, even when a Moonlight client is actively streaming. Sunshine input arrives via /dev/uinput virtual devices, which hypridle's input watcher doesn't always treat as activity. From the client's perspective the stream "freezes" on the screensaver screen until the user wiggles input enough to break hypridle's threshold.

Current workaround: wiggle the mouse / tap a key in the stream window.

Fix sketch:

  1. In bin/sunshine-stream-do.sh, on stream start, take a systemd-inhibit lock or systemctl --user stop hypridle.service:

    # at the top, after env recovery
    if command -v systemctl >/dev/null && systemctl --user is-active --quiet hypridle.service; then
      touch "$STATE_DIR/hypridle-was-active"
      systemctl --user stop hypridle.service
    fi
    
  2. In bin/sunshine-stream-undo.sh, on disconnect, restore:

    if [[ -f "$STATE_DIR/hypridle-was-active" ]]; then
      systemctl --user start hypridle.service
      rm -f "$STATE_DIR/hypridle-was-active"
    fi
    

Complexity: low. ~10 lines split across the two hook scripts. Self-healing (if a stream crashes and undo doesn't run, next connect will be a no-op on the inhibit and undo will idempotently leave the marker file).

Alternative: pass --inhibit to systemd-inhibit and run sunshine wrapped in it. Cleaner separation but requires changing the service unit, which fights the AUR package every upgrade.


P1 — Auto-switch to the busiest workspace on connect

Symptom: when a client connects, the prep-cmd migrates the active workspace to HEADLESS-N. If active was empty (e.g. workspace 4) and the user's apps are on workspace 1, the stream shows an empty desktop until the user presses Super+1.

Current workaround: press Cmd+1 (or whatever maps to the apps workspace) in the stream.

Fix sketch: change the workspace-detection in bin/sunshine-stream-do.sh from activeworkspace to "workspace with the most windows":

# replace this:
PREV_WS="$(hyprctl activeworkspace -j 2>/dev/null | jq -r '.id // 1' || echo 1)"

# with this:
PREV_WS="$(hyprctl workspaces -j 2>/dev/null \
  | jq -r 'sort_by(-.windows) | .[0].id // 1')"

Complexity: trivial. 2-line patch.

Edge case: if every workspace is empty (no apps running), sort_by is still deterministic — picks whichever workspace happens to be first. That's fine; the user just lands on an empty workspace either way.


P2 — 1Password streams as a black rectangle

Symptom: the 1Password Linux app deliberately masks its window from screen-capture surfaces. Through Moonlight, the 1Password window outline is visible but the interior is solid black. Untested whether this is recoverable.

Current workaround: use the 1Password browser extension during the stream (it lives inside Chromium and captures normally), or unlock 1Password on the host before walking away (the SSH-agent / browser extension / op CLI all still work even though the desktop window is unviewable).

Things to test, in order of "least invasive":

  1. Launch 1Password with --disable-gpu --no-sandbox. Software rendering path may bypass the anti-capture flag.

    pkill -f 1password
    1password --disable-gpu --no-sandbox &
    
  2. Force XWayland: 1password --ozone-platform=x11. XWayland surfaces are captured through a different code path than native-Wayland Electron surfaces.

  3. Patch the launcher (~/.local/share/applications/1password.desktop) with whichever flag works.

  4. If neither works: this is by-design upstream behavior and the realistic answer is the browser-extension workaround. Document in client/README.md under "Limitations."

Complexity: low (test command-line flags) to medium (patching .desktop file properly).


P2 — Resolve <host>.lan from clients

Symptom: getent hosts <host>.lan returns nothing — there's no DNS or mDNS entry for the friendly LAN name. Clients work via raw IP (192.168.x.y), and the host cert SAN covers both the .lan name and the IP, so cert trust works either way. The friendly name just isn't reachable.

Current workaround: use the LAN IP, or add <IP> <host> <host>.lan to the Mac's /etc/hosts.

Fix sketch: this is a network-layer change, not a repo change.

  • Unifi: Settings → Networks → (your network) → DHCP → static reservation for the host machine + Settings → DNS or "Local domain name" → set to lan. Then the controller publishes A records for every reserved device.
  • pi-hole / dnsmasq: add an address=/<host>.lan/<IP> entry.
  • avahi-daemon: would publish via mDNS, but Macs and some Linux clients don't always pick it up. Less reliable than proper DNS.

Complexity: out-of-repo (depends on the user's network stack). Document the choice once made.


P2 — 1Password SSH-agent timeout breaks git signing

Symptom: long sessions interspersed with idle time cause 1Password's SSH agent to go to sleep. Subsequent git commit fails with:

error: 1Password: failed to fill whole buffer
fatal: failed to write commit object

Current workaround: touch the 1Password desktop app or run eval $(op signin) to revive the agent.

Fix sketch (none are perfect):

  1. Lengthen the agent timeout — 1Password app → Settings → Developer → SSH agent → bump the lock-after timeout. Cap is configurable but capped.
  2. Watchdog script — periodically ssh-add -l and warn the user if it fails. Adds noise.
  3. Per-repo signingkey config to a different key not held by 1P — defeats the purpose.

Complexity: low. Setting (1) is the realistic choice; (2) is overkill.


P3 — Cert renewal automation

Symptom: host certs are valid 365 days. The installer re-mints when <30 days remaining on the next install.sh run, but if you don't re-run the installer in 11 months the cert silently expires and the next run (somewhere between day 365 and day 395) re-mints — leaving a gap.

Current workaround: re-run install.sh periodically.

Fix sketch: ship a systemd --user timer that runs install.sh --doctor && install.sh --force-certs weekly. Tied to the same op-signin requirement, so it won't run unattended if 1P isn't unlocked — that's actually fine (failure is loud, not silent).

Complexity: low-medium. New .timer + .service units; tricky bit is making the timer fire only when both Hyprland and op are reachable.


P3 — Stale config keys produce harmless warnings

Symptom: Sunshine logs Unrecognized configurable option [nvenc_rc] (and nvenc_tune, nvenc_coder) on every startup. Recent Sunshine versions renamed these keys — the encoder still works because the rename is backward-compatible for the most important ones (nvenc_preset), but the specific tuning keys we set don't take effect.

Current workaround: ignore the warnings; encoder defaults still work.

Fix sketch: update lib/config.sh to emit the new key names. Need to verify the current spelling against the Sunshine version installed (likely nv_preset/nv_tune/nv_rc/nv_coder without the enc, but the user should sunshine --help to confirm). Same situation likely on AMD's amd_* keys — verify when first Framework install actually exercises that code path.

Complexity: trivial once the right key names are confirmed.


P3 — Single-user assumption in everything

The installer + scripts assume a single human-user / single-host model. Not a problem now, but if someone wants to share a JARVIS install across multiple Sunshine instances (one per logged-in user), the headless prestart script and output_name would collide.

Fix sketch: introduce a username-scoped output_name (e.g. HEADLESS-${USER}-1) and scope the prestart's dedupe to outputs matching that prefix. Substantial work; not justified without a real second user.


P3 — Installer support for the X11/NVENC (non-Hyprland) headless path

Symptom: install.sh only knows the Arch + Hyprland + wlr world. At least one real deployment is an Ubuntu host running the X11/NVENC path (headless Xorg on :0, capture = x11, NVIDIA TwinView virtual display) — set up entirely by hand. None of it is reproducible from the repo: not the xorg-headless.service unit, not the X11 sunshine.conf, not the DISPLAY=:0 service drop-in, not the default.target boot wiring (see TROUBLESHOOTING.md §1213).

Current workaround: configure those hosts manually using the recipes now documented in ARCHITECTURE.md ("Headless capture backends") and TROUBLESHOOTING.md §13.

Fix sketch:

  • A --backend x11|wlr flag (or auto-detect: Hyprland reachable → wlr, NVIDIA + no Wayland compositor → x11).
  • lib/config.sh: emit the X11 conf variant when backend is x11.
  • Ship an xorg-headless.service + xorg-headless.conf template (the ConnectedMonitor/ModeValidation block is GPU-output-specific — needs xrandr detection or a prompt).
  • lib/service.sh: install the default.target boot drop-in for headless hosts regardless of backend, and the DISPLAY=:0 env drop-in for x11.
  • Debian/Ubuntu package install path (apt + the .deb), since yay/AUR don't exist there. This is a larger lift than the flag itself.

Complexity: medium-high. The capture-backend split is moderate; full Debian packaging support is the bulk of the work.


Not on the list (intentionally)

  • TLS for the stream itself. Sunshine and Moonlight handle this with their own pinned-cert protocol; we don't touch it.
  • AV1 encoder. RTX 3070 Ti is HEVC-capable but not AV1; users with Ada/40-series can opt in via the web UI without code changes.
  • Remote-access layer (Tailscale / WireGuard / Cloudflare). LAN-only for now; the design assumes RFC1918 only and would need real DNS + DNS-01 cert issuance for proper remote.
  • Windows host support. Sunshine works on Windows but the installer is Arch-only by design.