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
tailscaled3 s after the SSH session ends and then readsRunning. The timer needsAccuracySec=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-upblocks all inbound connections. That includes SSH and Tailscale SSH (--ssh), both checked.- A tailnet policy that tags the Frame (
tag:frame) and allows onlytag:frame:22keeps 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.