From d65f7130d506dc9e4612e5a7bbcc05835ecc088b Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:59:01 +1000 Subject: [PATCH] Test against Valve's own Steam Frame OS from its recovery image tests/frame-container/frame-image.sh extracts rootfs-A from Valve's Frame recovery image (steamdeck-images.steamos.cloud/recovery), mounts it read-only with a throwaway writable layer and starts the image's own sshd, so the iPhone app can pair with and run its server on the real SteamOS for Frame userland. Verified: password pairing then key login in the image's sshd log, the power password check through the image's sudo, status reading SteamOS 0.3.0. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/iphone.md | 7 +++--- tests/frame-container/README.md | 25 ++++++++++++++++++-- tests/frame-container/frame-image.sh | 35 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100755 tests/frame-container/frame-image.sh diff --git a/docs/iphone.md b/docs/iphone.md index aba9ae4..371955c 100644 --- a/docs/iphone.md +++ b/docs/iphone.md @@ -78,9 +78,10 @@ screenshots, a file upload (checked on the Frame), a background install job, and the power password check (a wrong password is refused). The server on the Frame exits within seconds of the app closing. -Against a stand-in built on Valve's Holo Core aarch64 base -([tests/frame-container](../tests/frame-container)), since Valve publishes no -Frame OS image: pairing with the password (key added with the right +Against Valve's own Steam Frame OS (SteamOS 0.3.0 build 20260922.5152327, the +`rootfs-A` partition of the Frame recovery image, run with its own sshd; see +[tests/frame-container](../tests/frame-container)), and a Holo Core stand-in: +pairing with the password (key added with the right permissions, host key pinned, password stored nowhere), the power password check (a wrong or missing password refused; the right one reaches `systemctl`), a changed host key refused with "Pair with the Frame again", and a wrong diff --git a/tests/frame-container/README.md b/tests/frame-container/README.md index 88ea9e3..46c9363 100644 --- a/tests/frame-container/README.md +++ b/tests/frame-container/README.md @@ -1,6 +1,27 @@ -# A Frame stand-in for testing without the headset +# Testing without the headset -Valve publishes no Steam Frame OS image. This builds the closest thing: Valve and +## Valve's own Frame OS, from its recovery image + +Valve publishes a Steam Frame recovery image at +https://steamdeck-images.steamos.cloud/recovery/ (`steamframe-oobe-repair-*.img.bz2`, +~4 GB). `frame-image.sh` extracts its `rootfs-A` partition (btrfs), mounts it +read-only with a throwaway writable layer, and starts the image's own sshd on +port 2223, so the iPhone app can pair with, and run its server on, the real +SteamOS for Frame userland (Python, sudo, sshd, PAM). It needs Linux with btrfs, +e.g. Colima's VM on a Mac: + +```sh +colima start --arch aarch64 --vm-type vz +colima ssh -- sudo sh tests/frame-container/frame-image.sh ~/Downloads/steamframe-oobe-repair-.img.bz2 +# pair with 127.0.0.1:2223, user steamos, password frame-test-pw +``` + +The image's kernel is built for the Frame's Qualcomm chip, so this runs its +userland, not the whole OS: no SteamVR, Steam client, battery or Lepton. + +## Holo Core stand-in + +A lighter option: Valve and Collabora's [Holo Core aarch64 preview](https://www.collabora.com/news-and-blog/news-and-events/building-an-arch-linux-aarch64-port-for-holo-core.html) (the Arch Linux ARM64 base the Frame's SteamOS is built on) with the Frame's SSH surface: a `steamos` user with a password and sudo, OpenSSH taking keys and diff --git a/tests/frame-container/frame-image.sh b/tests/frame-container/frame-image.sh new file mode 100755 index 0000000..c53c389 --- /dev/null +++ b/tests/frame-container/frame-image.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# Run Valve's own Steam Frame OS, from its recovery image, as an SSH target for +# testing (see README.md). Run on a Linux host or VM with btrfs, as root: +# frame-image.sh steamframe-oobe-repair-.img.bz2 +# It extracts the rootfs-A partition, mounts it read-only, adds a throwaway +# writable layer, and starts the image's own sshd on port 2223 (user steamos, +# password frame-test-pw). systemctl only records what it's asked. +set -e +command -v bzcat >/dev/null && command -v python3 >/dev/null || { + command -v apt-get >/dev/null && apt-get install -y -qq bzip2 python3 >/dev/null; } +IMG=${1:?recovery .img.bz2}; RAW=${RAW:-$(dirname "$IMG")/frame-rootfs-A.img} +if [ ! -f "$RAW" ]; then + # Partition 3 (rootfs-A) from the image's GPT: start and size in 512-byte sectors. + set -- $(bzcat "$IMG" | head -c 1048576 | python3 -c ' +import struct,sys; d=sys.stdin.buffer.read(); e=d[1024+2*128:1024+3*128] +a,b=struct.unpack(" "$RAW" +fi +R=/mnt/frame; O=/var/lib/frame-ovl; M=/srv/frame +mkdir -p $R $O/upper $O/work $M +mountpoint -q $R || mount -o ro -t btrfs "$(losetup -f --show -r "$RAW")" $R +mountpoint -q $M || mount -t overlay overlay -o lowerdir=$R,upperdir=$O/upper,workdir=$O/work $M +for d in proc sys dev dev/pts; do mountpoint -q $M/$d || mount --rbind /$d $M/$d; done +mountpoint -q $M/run || mount -t tmpfs tmpfs $M/run +mountpoint -q $M/tmp || mount -t tmpfs tmpfs $M/tmp +mkdir -p $M/run/sshd $M/home/steamos $M/usr/local/bin +chroot $M chown 1000:1000 /home/steamos +echo 'steamos:frame-test-pw' | chroot $M chpasswd +chroot $M ssh-keygen -A >/dev/null +# No systemd here: systemctl records power requests instead of acting. +printf '#!/bin/sh\necho "systemctl $*" >> /tmp/power-requests.log\n' > $M/usr/local/bin/systemctl +chmod +x $M/usr/local/bin/systemctl +pkill -f "[s]shd -p 2223" 2>/dev/null || true +chroot $M /usr/bin/sshd -p 2223 -E /tmp/sshd.log +echo up