Files
saphid--frame-control/docs/ssh.md
T
saphidandClaude Opus 5.5 1580dae42e Record headset results for sideloading and devkit pairing
Tested on a Frame (BUILD_ID 20260922.6101926):

- Devkit pairing: the service runs and answers properties.json, but /register
  refuses with "please put the Steam client in pairing mode" unless Steam is on
  Settings > Developer > Pair new host. Both setup paths now say so and keep
  asking for 2 minutes before falling back to the password.
- Sideloading: registering, launching, quoted start paths and Remove work; a
  Windows exe runs under Proton 11 through FEX. An aarch64 build starts but
  outside the runtime container, and an x86-64 Linux build doesn't start
  because the x86-64 Steam Linux Runtime 4.0 isn't installed. The inspect note
  for x86-64 Linux builds now says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 22:31:05 +10:00

9.4 KiB

SSH into the Steam Frame

Confidence labels:

  • Confirmed (Frame): Valve's Steam Frame docs or a Frame-specific source.
  • Inferred (Deck/SteamOS): true on Steam Deck or SteamOS generally, but not checked on a Frame.
  • Guess: reasoned, with no source.

How access is turned on

Claim Confidence Source
Steam Settings → System → Enable Developer Mode enables SSH, ADB, and RDP Confirmed (Frame) setup, debugging
A password is set in Developer → Set User Password. There is no default password. Confirmed (Frame) setup
The default user is steamos, not deck Confirmed (Frame) debugging: ssh steamos@frame
The default hostname is frame, and can be changed in Steam Settings → System → Hostname Confirmed (Frame) setup, adb_lepton
The IP address is shown in Quick Settings or Steam Settings → Internet Confirmed (Frame) adb_lepton
The rootfs is read-only. sudo steamos-readonly disable makes it writable. Confirmed (Frame) debugging
sudo pacman works. Helper aliases cdd (Frame scripts dir), cdl (Steam logs), and lepton exist. Confirmed (Frame) debugging
There's a full KDE Plasma Linux desktop inside the headset, reachable from the SteamVR dashboard Confirmed (Frame, press) Road to VR review, UploadVR
On Deck, the manual route is Desktop Mode → Konsole → passwd → sudo systemctl enable --now sshd Inferred (Deck) pimylifeup, gist

On Deck, SSH needs the manual terminal steps. On the Frame, the Developer Mode UI handles both the password and the SSH service. That's why the headset-side checklist in the README involves no terminal at all.

Name resolution from a Mac

Valve's examples use a bare frame. That works on Windows through LLMNR/NetBIOS. On macOS, a bare single-label name usually doesn't resolve unless your router's DNS registers DHCP client names.

  • Verified on device (2026-09-25): avahi-daemon is running on the Frame and frame.local resolves from the Mac over mDNS.
  • scripts/connect.sh tries frame.local, then frame, then an mDNS browse for the devkit service (below). If none works, it tells you to re-run it with the IP. Once you have a working address, the Host frame alias means you just type ssh frame.
  • To check discovery yourself: dns-sd -G v4 frame.local (Ctrl-C to stop), or dscacheutil -q host -a name frame.local.
  • A DHCP reservation for the headset on your router makes the IP stable. That's the most reliable fallback.

Key-based login (done by scripts/connect.sh)

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_frame -N '' -C "mac->steam-frame"
ssh-copy-id -i ~/.ssh/id_ed25519_frame.pub steamos@frame.local

~/.ssh/config block (managed between marker lines by the script):

Host frame
  HostName frame.local
  User steamos
  IdentityFile ~/.ssh/id_ed25519_frame
  IdentityFile ~/.ssh/id_rsa_frame_devkit
  IdentitiesOnly yes
  ServerAliveInterval 30

The script only asks for the password if the pairing below doesn't work.

Password-free pairing (SteamOS devkit service)

From Valve's source (steamos-devkit-service, steamos-devkit client). Verified on a Frame 2026-09-26 (BUILD_ID 20260922.6101926): the service runs with Developer Mode on, properties.json answers with "login": "steamos", the headset advertises _steamos-devkit._tcp as frame, and /register needs pairing mode (below). The approve prompt and key install are not verified yet. SteamOS's devkit service is what Valve's Devkit Client uses to pair. scripts/connect.sh and ui/frame_connect.py try it first:

  • The headset serves HTTP on port 32000 and advertises mDNS _steamos-devkit._tcp. GET /properties.json gives the login user; the script uses it as User (unless you set FRAME_USER, or it says root), for the password fallback too, and keeps it on re-runs.
  • Open Steam Settings → Developer → Pair new host in the headset first. Otherwise /register answers at once with 403 "please put the Steam client in pairing mode: Settings -> Developer -> Pair new host" (verified). The scripts say so and keep asking for 2 minutes while you open it.
  • POST /register with ssh-rsa <key> <comment> 900b919520e4cf601998a71eec318fec (a fixed token from Valve's client) shows an approve prompt inside the headset naming the comment (frame-control@<your computer>). It waits 30 s, then installs the key for the device user and turns sshd on. The reply is 200 Registered, or 403 with {"error": ...} (declined, timed out, Steam not running).
  • It only accepts RSA keys, hence the second key, ~/.ssh/id_rsa_frame_devkit (3072-bit).
  • A host counts as found if port 22 or 32000 answers. With no host given, and frame.local/frame unreachable, it browses _steamos-devkit._tcp with dns-sd (macOS) or avahi-browse (Linux) for a few seconds if installed.
  • Port 32000 closed, a timeout, or an error: the script says why and falls back to copying the ed25519 key with the Developer Mode password, as before.

Anyone on your network can send the request, so only approve a prompt you started. curl http://<frame-ip>:32000/properties.json shows whether the service is up.

~/.ssh/authorized_keys lives under /home, which SteamOS keeps across OS updates (inferred from Deck; the Frame uses the same A/B image scheme).

Keeping sshd enabled across updates

  • Frame: SSH is tied to the Developer Mode toggle, so it should survive updates as long as Developer Mode stays on. (Inferred: Valve doesn't say how the toggle is implemented.)
  • Deck (for comparison): systemctl enable sshd usually persists because /etc is an overlay that survives updates. Changes under /usr do not.
  • Don't pacman -S anything you depend on for access. Packages installed into the read-only rootfs are wiped by OS updates on SteamOS. Use Flatpaks (--user) or ~/ for anything that needs to persist.

Hardening (optional: ./scripts/connect.sh --harden)

The script writes /etc/ssh/sshd_config.d/01-frame-keys-only.conf with PasswordAuthentication no and KbdInteractiveAuthentication no, then reloads sshd. First, it checks that key login works in BatchMode, so you can't lock yourself out.

  • Needs sudo (Developer Mode password), entered on the Mac.
  • It assumes /etc/ssh/sshd_config includes sshd_config.d/*.conf, which is the Arch default. The script checks for this and stops if the include is missing.
  • /etc drop-ins normally persist across SteamOS updates (inferred from Deck).
  • It doesn't affect RDP (xrdp) or sudo, which still use the password.
  • Undo: ssh frame 'sudo rm /etc/ssh/sshd_config.d/01-frame-keys-only.conf && sudo systemctl reload sshd'.

Other shells

  • ADB over USB-C to the native Linux OS: adb shell. Plug the headset into the Mac. Valve notes that USB power may be insufficient. Install with brew install android-platform-tools. This is useful if Wi-Fi SSH is broken. (Confirmed (Frame): debugging)
  • ADB over Wi-Fi reaches the Lepton (Android) container, not Linux: adb connect frame:5555. It only works while "Lepton Development" or an Android app is running. (Confirmed (Frame): adb_lepton)
  • RDP: xrdp with user steamos and the Developer Mode password (see streaming.md).

Fallback bootstrap one-liner

Use this only if the Developer Mode toggle doesn't give you SSH (for example, an OS build without it).

  1. On the Mac: ./scripts/serve-bootstrap.sh. It serves bootstrap-on-frame.sh, with your ~/.ssh/id_ed25519_frame.pub embedded, on port 8765, and prints the exact one-liner.
  2. On the Frame's Linux desktop, open Konsole and type the printed line, roughly curl -fsS mac.local:8765|bash (~30 characters). If mac.local doesn't resolve, the script prints an IP form instead.
  3. The bootstrap installs the key into ~steamos/.ssh/authorized_keys, and then runs sudo systemctl enable --now sshd. sudo asks for a password, and if none is set yet, it tells you to run passwd first. That means typing the password on the headset one more time.
  4. Stop the server on the Mac with Ctrl-C.

This is plain HTTP on your LAN, and it only serves a public key, so the content isn't secret. Anyone on the LAN who can spoof your Mac's address could serve a different script, though, so use it only on a trusted network.