# 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](https://partner.steamgames.com/doc/steamhardware/steamframe/setup), [debugging](https://partner.steamgames.com/doc/steamhardware/steamframe/debugging) | | A password is set in **Developer → Set User Password**. There is no default password. | Confirmed (Frame) | [setup](https://partner.steamgames.com/doc/steamhardware/steamframe/setup) | | The default user is **`steamos`**, not `deck` | Confirmed (Frame) | [debugging](https://partner.steamgames.com/doc/steamhardware/steamframe/debugging): `ssh steamos@frame` | | The default hostname is **`frame`**, and can be changed in **Steam Settings → System → Hostname** | Confirmed (Frame) | [setup](https://partner.steamgames.com/doc/steamhardware/steamframe/setup), [adb_lepton](https://partner.steamgames.com/doc/steamhardware/steamframe/adb_lepton) | | The IP address is shown in Quick Settings or **Steam Settings → Internet** | Confirmed (Frame) | [adb_lepton](https://partner.steamgames.com/doc/steamhardware/steamframe/adb_lepton) | | The rootfs is read-only. `sudo steamos-readonly disable` makes it writable. | Confirmed (Frame) | [debugging](https://partner.steamgames.com/doc/steamhardware/steamframe/debugging) | | `sudo pacman` works. Helper aliases `cdd` (Frame scripts dir), `cdl` (Steam logs), and `lepton` exist. | Confirmed (Frame) | [debugging](https://partner.steamgames.com/doc/steamhardware/steamframe/debugging) | | There's a full KDE Plasma Linux desktop inside the headset, reachable from the SteamVR dashboard | Confirmed (Frame, press) | [Road to VR review](https://roadtovr.com/valve-steam-frame-review/), [UploadVR](https://www.uploadvr.com/flatpaks-open-source-steam-frame/) | | On Deck, the manual route is Desktop Mode → Konsole → `passwd` → `sudo systemctl enable --now sshd` | Inferred (Deck) | [pimylifeup](https://pimylifeup.com/steam-deck-ssh/), [gist](https://gist.github.com/chphr/9c0791de6d2c659af3bf5890d9080973) | 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`) ```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) **Inferred from Valve's source ([steamos-devkit-service](https://gitlab.steamos.cloud/devkit/steamos-devkit-service), [steamos-devkit](https://gitlab.steamos.cloud/devkit/steamos-devkit) client); not yet verified on a Frame.** 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. - `POST /register` with `ssh-rsa 900b919520e4cf601998a71eec318fec` (a fixed token from Valve's client) shows an approve prompt inside the headset naming the comment (`frame-control@`). 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. Whether the Frame runs this service with Developer Mode on is the unverified part: `curl http://frame.local:32000/properties.json` answers the question. `~/.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](https://partner.steamgames.com/doc/steamhardware/steamframe/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](https://partner.steamgames.com/doc/steamhardware/steamframe/adb_lepton)) - **RDP**: xrdp with user `steamos` and the Developer Mode password (see [streaming.md](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.