Two new docs filling the gaps in the prior set: docs/ARCHITECTURE.md - Component map + runtime flow diagrams (install-time and per-stream). - Cert pipeline walk-through end-to-end (CA bootstrap, op:// references, per-host mint, idempotency conditions). - State directory inventory (where things write at runtime). - Idempotency contract — explicit rules every script in this repo follows. - Full file map of the repo. docs/FOLLOWUPS.md - Promoted the punch list out of the TROUBLESHOOTING.md trailing section. - Each item now has: symptom, current workaround, fix sketch (with the actual code change, not vague intent), and a complexity estimate. - Tracks: screensaver inhibit, busiest-workspace auto-switch (2-line patch), 1Password black-rectangle workarounds (untested), host.lan DNS (out-of-repo), 1P SSH-agent timeout, cert renewal timer, stale config keys, single-user assumption. README.md - New "Documentation" section between Clients and Diagnostics points at each of the three doc files plus client/README.md, with a one-line description for each so readers can navigate without spelunking.
234 lines
8.7 KiB
Markdown
234 lines
8.7 KiB
Markdown
# 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`:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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":
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
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.
|
|
|
|
---
|
|
|
|
## 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.
|