Files
saphid--frame-control/docs/tailscale.md
T

4.9 KiB

Tailscale: advanced remote-access setup

Tailscale is intended to make the frame SSH alias available off the home LAN, including to Frame Control and the scripts. Off-LAN access has not yet been verified; the checks below cover the direct LAN path only. Read the exposure section before enabling it: services normally restricted to loopback become reachable by other devices on your tailnet. 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).

These are source-checkout commands for macOS, not packaged-app menu actions or Windows PowerShell commands. The connection script currently uses macOS netcat options; this route is not supported on Linux. Install Git, OpenSSH, zsh, Python 3 and curl on your Mac; the desktop app's bundled Python is not available to these scripts. Then obtain the scripts:

git clone https://github.com/saphid/frame-control.git
cd frame-control

Enable Developer Mode on the awake headset and establish working SSH access first using the connection guide. Run from the checkout:

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.