Add Steam screenshots, Tailscale remote access, VR-video fixes

- Screenshots: list the Frame's Steam screenshots, open them in the
  viewer, and save new ones to ~/Pictures/SteamFrame. Ids are validated
  before any shell, and copies land atomically.
- Tailscale: scripts/tailscale-on-frame.sh installs a userspace tailscaled
  as a lingering systemd --user service with no sudo, SHA-256 checked, safe
  to re-run, with --uninstall. docs/tailscale.md covers setup and warns that
  in userspace mode every Frame port, including loopback-only DevTools and
  ADB, is reachable from the tailnet.
- push-vr-video.sh: filenames starting with "-" are safe, symlinks are
  followed, and a real Videos\VR directory triggers a warning.
- Tests cover the screenshot routes (19 total).

Docs keep placeholder addresses for the headset and tailnet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-25 23:11:14 +10:00
1 parent 99653151c4
commit eabf4cd1f9
10 files changed
+427 -54

No files matched your search

+91
View File
@@ -0,0 +1,91 @@
# Tailscale: use the Frame from anywhere
With Tailscale on the Frame, the `frame` SSH alias works off the home LAN, and
so does everything built on it: Frame Control, the scripts and the Mac app.
When the Mac and the Frame are on the same network, Tailscale connects them
directly, so there's no relay in the way (`tailscale ping frame` → `via
192.168.1.50:41641`, 20 ms).
```sh
scripts/tailscale-on-frame.sh # install or update, then approve the login URL
scripts/connect.sh frame.<tailnet>.ts.net # point the alias at Tailscale (the script prints this)
scripts/tailscale-on-frame.sh --uninstall
```
## How it's installed
There's no Tailscale Flatpak, and the rootfs is read-only. So the script
installs Tailscale's static arm64 build in the `steamos` user's home and runs
`tailscaled --tun=userspace-networking` as a systemd **user** service. It
doesn't need sudo, and SteamOS updates don't touch it.
| Path | What |
|---|---|
| `~/.local/share/tailscale/<version>/` | `tailscale`, `tailscaled` (SHA-256 checked against pkgs.tailscale.com) |
| `~/.local/share/tailscale/current` | Symlink to the active version |
| `~/.local/share/tailscale/state/` | Node key and state |
| `~/.local/bin/tailscale` | CLI wrapper that points at the daemon's socket (`$XDG_RUNTIME_DIR/tailscale/tailscaled.sock`) |
| `~/.config/systemd/user/tailscaled.service` | The service |
Lingering (`loginctl enable-linger`) is on, so the service starts at boot
without anyone logging in. polkit allowed that without sudo. Re-running the
script is safe. It restarts `tailscaled` only if the version or unit changed,
and then does so detached after 3 s, because the SSH session may itself run
over Tailscale.
To update, run the script again; it installs the latest stable version. To
manage the node, use `ssh frame '~/.local/bin/tailscale status'` (or `set`,
`down`, `up`).
**Verified 2026-09-25 (SteamOS 0.3.0, build 20260922.6101926, Tailscale
1.102.4):**
- First install and login approval. This ran an earlier revision of the script,
which restarted the daemon unconditionally. The node is `frame`,
with a 100.x.y.z tailnet address.
- SSH works over Tailscale: the Frame serves the same ED25519 host key as it
does on `frame.local`.
- Frame Control's status and Get games work through the alias.
- The current script: a re-run with nothing changed doesn't restart anything,
and a re-run with a changed unit restarts `tailscaled` 3 s after the SSH
session ends and then reads `Running`. The timer needs
`AccuracySec=100ms`; the default of 1 min made it fire up to a minute late.
The first-install guard was checked on its own.
**Not verified:**
- A clean first install and login with the current script end to end. It would
mean removing the node from the tailnet.
- Reaching the Frame from outside the home network. Only the direct LAN path
was tested.
- The service coming up after a reboot. That's **inferred** from linger plus
`WantedBy=default.target`; the Frame hasn't been rebooted since.
## Exposure: every port is on the tailnet
In userspace mode, `tailscaled` passes inbound tailnet connections to the
Frame's **loopback**. Any device on the tailnet can therefore reach **every**
listening port, including ones meant to be local-only. Checked from the Mac on
2026-09-25:
| Port | Service | Normally |
|---|---|---|
| 22 | sshd | LAN |
| 8080 | Steam client DevTools (full control of the Steam client and account session) | loopback only |
| 27062 | SteamVR `vrserver` | loopback only |
| 5555 | Lepton ADB (unauthenticated shell into Android) | LAN |
| 3389 | xrdp | LAN |
The user accepted this on 2026-09-25, since the tailnet only holds their own
devices. Other options:
- `tailscale set --shields-up` blocks **all** inbound connections. That
includes SSH and Tailscale SSH (`--ssh`), both checked.
- A tailnet policy that tags the Frame (`tag:frame`) and allows only
`tag:frame:22` keeps the other ports private. This is an admin-console
change.
- Kernel-mode Tailscale (a root install, e.g. systemd-sysext) wouldn't expose
loopback-only ports, but it needs sudo and may not survive SteamOS updates.
If the Mac's Tailscale is off, the alias won't resolve. Use
`scripts/connect.sh frame.local` to go back to the LAN name.