Add ARCHITECTURE + FOLLOWUPS docs and README pointers
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.
This commit is contained in:
233
docs/FOLLOWUPS.md
Normal file
233
docs/FOLLOWUPS.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user