1 ポイント 投稿者 GN⁺ 2024-01-01 | 1件のコメント | WhatsAppで共有
  • SteamOS 3 “Holo” は Steam Deck 向けの Arch ベースのディストリビューションだが、リビングPCの suspend 復帰問題 を直すにはカーネルコミットを巻き戻し、rootfs イメージまで自前でフォークする必要があった
  • 更新構造は、非アクティブなパーティションに新しい 読み取り専用 rootfs をインストールしてから再起動する A/B 方式で、/etc は overlayfs により変更分を保持する
  • Valve のカーネルパッチは、pacman ソースミラーにある linux-neptune-61-6.1.52.valve9-1.src.tar.gz のような ソース tarball から bare Git リポジトリを複製し、独自タグと PKGBUILD でパッケージをビルドする流れになっている
  • rootfs の再パッキングは、SteamOS の RAUC バンドルから rootfs.img.caibx を抽出してイメージ化し、Btrfs UUID の変更・パッケージ差し替え・buildid 変更・更新 URL と RAUC 証明書の差し替えを経て、再び RAUC bundle としてまとめる作業である
  • 独自 Web サーバーが live.json を提供し、steamos-atomupdQueryUrlImagesUrlMetaUrl を変更すれば、既存の SteamOS インストールも独自イメージへ更新できる

SteamOS をリビングPC向けにフォークした理由

  • SteamOS 3 “Holo” は、Valve Software の携帯型 PC ゲーム機 Steam Deck 向けの Arch ベース Linux ディストリビューション である
  • 更新方式は、新しい読み取り専用 rootfs を非アクティブなパーティションへダウンロードし、そのパーティションから再起動する A/B アトミックアップデート 構造である
  • ユーザーは steamos-devmode を実行して rootfs のロックを解除し、pacman データベースを正常化すれば、通常の Linux ディストリビューションのように扱える
  • 目標は、steamos-devmode で簡単に回避するのではなく、rootfs イメージ自体を修正できる きちんとしたフォーク を作ることだった
  • リビングPCでは SteamOS はほぼ動作したが、suspend 復帰 だけが失敗した
    • 同じコンピューターで mainline または stable カーネルを使う他のディストリビューションでは suspend 復帰が動作する
    • Valve のカーネルソースを見つけて git bisect を実行した結果、Steam Deck ハードウェアの suspend 復帰を直すためと思われるコミットが、この PC では問題を引き起こしていた
    • そのコミットを巻き戻して自前でカーネルをビルドする必要があったことが、この一連の作業の直接の理由だった
  • Arch などをそのまま使う選択肢もあったが、ゲーム実行用 Linux ディストリビューションを手直しするなら、Valve がテストしたパッケージ群に依存するほうを好んだ

SteamOS のパーティションと更新構造

  • SteamOS システムは 8 個のパーティション を使う
    • EFI system partition には stage 1 bootloader と A/B パーティションセット選択用メタデータがある
    • 各 A/B セットには stage 2 bootloader である GRUB、root filesystem、/var パーティションがある
    • 残りのディスク領域は単一の home パーティションが占める
  • 起動時には複数の pseudo-filesystem も追加でマウントされる
    • /var/log/root/nix など、ほぼ 12 個のディレクトリが /home/.steamos/offload から bind mount され、データを永続化する
  • /etc は overlayfs で処理される
    • 変更分は /var/lib/overlays/etc/upper に保存される
    • machine-id や NetworkManager の接続設定のように、通常 /etc に残るべき項目は保持される
    • 手を加えていない設定ファイルは更新されうる
    • この方式により、パッケージマネージャのロジックなしでも A/B パーティション構造で設定保持と更新を両立している
  • システム更新は、Steam クライアントまたはターミナルユーザーが steamos-update を実行すると開始される
    • このコマンドは Python プログラム steamos-atomupd-client を実行する
    • クライアントは現在の OS 情報とユーザーの更新チャネル設定を、/etc/steamos-atomupd/client.conf の URL に送って新しい更新があるか確認する
  • 新しい更新がある場合、サーバーは RAUC bundle のパスを返す
    • クライアントはバンドルをダウンロードして rauc install を実行する
    • RAUC はバンドル署名を検証し、rootfs.img.caibx を探す
    • casync extract で新しいイメージ断片をダウンロードし、非アクティブな rootfs パーティションに書き込む
    • post-install スクリプトは、アクティブな /var から非アクティブな /var へデータを選択的に同期し、EFI system partition の stage 1 bootloader 設定を変更して新しいパーティションセットから起動させる

Valve のカーネルソースからパッケージを作る

  • Valve は SteamOS で大きく修正した Linux カーネルを使っており、ソースはダウンロード可能である
  • 現在の SteamOS イメージのソースは、Valve の pacman ミラーにある sources/holo-3.5sources/jupiter-3.5 で見つかる
  • 執筆時点の stable イメージのカーネルは 6.1.52-valve9-1-neptune-61 で、そのソース tarball は 2.9GiB ある
  • tarball が大きい理由は、Linux の Git ツリー全体が含まれているためである
    • tarball の中には PKGBUILDconfigconfig-neptunearchlinux-linux-neptune/ などが含まれる
    • archlinux-linux-neptune/ は、そのまま作業できる通常の working tree ではなく bare repository である
  • PKGBUILD は git+ssh://git@gitlab.steamos.cloud/jupiter/linux-integration.git#tag=$_tag という形式の非公開 GitLab リポジトリをソースとして指している
    • 直接 clone したりコミットリンクをたどったりはできない
    • makepkg のソースから、各タグに対応する完全なコミット履歴入りスナップショットを取得できる
    • この構造のおかげで、リビングPCの suspend を壊しているコミットを bisect できた
  • 作業方法としては、bare repository を通常の working tree として clone し、独自ブランチとタグを維持する流れになる
    • 例としては、6.1.52-valve9 タグから my-branch を作るやり方である
    • 独自変更は別の Git ホストに上げ、PKGBUILD の source をそのリポジトリと独自タグに変更する
    • 例のリポジトリとして linux がある
  • makepkg でカーネルパッケージを作成できる
    • makepkg MAKEFLAGS=-j$(nproc)/etc/makepkg.conf の更新は、小さな VM でない場合に有用である
    • 見た範囲では、SteamOS 特化パッケージも最初の source に Git リポジトリを使う同様の構造だった
  • 後続の手順を簡単にするため、独自 pacman repo も構成した
    • パッケージをディレクトリに置き、repo-add $REPO_NAME.db.tar.zst [PACKAGES...] を実行してから Web ホストにアップロードする
    • この repo は、後で steamos-devmode を実行した場合でもツールが正常に動作する助けになる

root filesystem の取得とマウント

  • release engineering スクリプトは見つからなかったため、既存 root filesystem を必要に応じて 再パッキング する方式を選んだ
  • 説明もコメントもないスクリプトは fauxlo にある
  • SteamOS rootfs イメージを得る一般的な方法は、Steam Deck を購入するか Steam Deck 復旧イメージをダウンロードすることだが、どちらも Steam End User License Agreement への同意が必要である
  • 現在のリリース版は、更新システムの fallback URL と見られる snapshot JSON で確認できる
    • 執筆時点の stable バージョンは 20231122.1 だった
    • preview チャネル用の別 snapshot JSON もある
  • rootfs のダウンロードは steamos-atomupd-client と同じ手順に従う
    • RAUC bundle である .raucb ファイルをダウンロードする
    • SquashFS ファイルシステムのバンドルから rootfs.img.caibx を抽出する
    • casync extract.castr store から断片を取得し、rootfs.img を生成する
    • .castr store の URL は RAUC bundle URL の .raucb.castr に置き換えたものである
    • この動作は steamos-atomupd にハードコードされている
    • 自動化スクリプトは fetch-current.sh にある
  • 隣接する .img.zip.img.zst ファイルは rootfs ではなく、別の起動可能な 復旧イメージ である
    • 復旧イメージから rootfs パーティションを抽出して次の手順に使うこともできる
    • ただし、RAUC と casync で取得したイメージとは bit-for-bit で同一ではなく、更新バンドルを再作成するには結局それらのツールが必要になる
  • rootfs を変更する前に filesystem UUID を変更する必要がある
    • 既存の SteamOS イメージからカスタムイメージへ更新する際、UUID を変えないと異なる 2 つの filesystem が同じ UUID を持つことになる
    • この状態は問題を引き起こしうる
    • 例は btrfstune -fu rootfs.img である
  • Valve は zstd 圧縮を使う Btrfs イメージ を使っている
    • 変更中も圧縮を維持するには mount -o compress=zstd rootfs.img rootfs でマウントする
    • SteamOS は Btrfs の readonly subvolume 属性を使うため、btrfs property set -ts rootfs ro false で解除する
  • Linux カーネルのようなパッケージ変更は、/dev/proc を必要とするスクリプトを起動する場合がある
    • devtmpfsproc を rootfs 配下にマウントする
    • 起動済みシステムでマウントされるディレクトリへの書き込みが発生しないよう、/tmp/run/var/home には tmpfs をマウントする
    • chroot 内で名前解決ができるよう、ホストの /etc/resolv.conf を bind mount する

パッケージ差し替えとイメージメタデータの変更

  • 独自 repository は /etc/pacman.conf の最初の repo 項目として追加する
    • こうすると Valve repo により新しいバージョンのパッケージがあっても独自パッケージが優先される
    • 後で steamos-devmode を実行しても独自パッケージを再インストールできる
  • 例の repo stanza では [fauxlo]Server = https://fauxlo.ili.fyi/pacman/$archSigLevel = Never を使う
    • SigLevel = Never はパッケージ署名がなくても許可する
    • GPG 署名済みパッケージをインストールするには pacman keyring を埋める必要がある
    • /etc/pacman.d/gnupg の空 keyring をいじる代わりに、tmpfs 上の新しい keyring を作って埋める方法が使われている
  • パッケージのインストールは pacman --sysroot rootfs --noconfirm -Sy linux-neptune-61 の形で行う
    • 実際のスクリプトでは -y を避け、独自 repo データベースだけを pacman の後段で同期している
    • これにより他の repository の状態を元イメージがビルドされた時点に固定できる
    • イメージ diff に現れる変更を減らすための選択である
  • steamos-atomupd は現在のイメージバージョンと build ID を /lib/steamos-atomupd/manifest.json から読み取り、なければ /etc/os-release を使う
    • サーバーが提供する更新の build ID が現在のイメージと同じなら更新を拒否する
    • どのイメージを実行中かを識別するのにも役立つ
  • build ID は必ず YYYYMMDD.N 形式 でなければならない
    • 形式が合わないと steamos-atomupd は Python traceback とともに終了する
    • 手動で増分管理を避けるため、NHHMMSS や Unix timestamp を入れる方法が使える
    • manifest.jsonbuildidos-releaseBUILD_ID を一緒に変更する
    • この作業用の Bash スクリプト断片は repack.sh にある
  • RAUC は信頼設定に X.509 証明書 を使う
    • 信頼する証明書は /etc/rauc/keyring.pem にある
    • 単純な self-signed certificate で十分である
    • 新しい証明書を rootfs/etc/rauc/keyring.pem にインストールする
  • rootfs/etc/steamos-atomupd/client.conf の URL も独自サーバー向けに変更する
    • QueryUrl
    • ImagesUrl
    • MetaUrl
  • 5GiB の Btrfs イメージ容量を超えない限り、ほかの変更も可能である
    • 例えば、ネットワーク上で hostname.local によって SteamOS デバイスを見つけたいなら、rootfs/usr/lib/systemd/resolved.conf.d/00-disable-mdns.conf を削除できる
    • /etc overlay 設定で override することもできるが、面倒だと判断した
  • イメージなしで簡単にできる変更は、原則として rootfs に入れないほうがよい
    • Firefox を rootfs にインストールすることはできる
    • ただし Firefox のセキュリティ更新のたびにイメージを再パックし直す必要がある

rootfs のアンマウントと RAUC bundle の作成

  • 変更が終わったら filesystem を再び read-only としてマークする
    • btrfs property set -ts rootfs ro true
  • 未使用ブロックは fstrim -v rootfs で discard する
  • アンマウントには umount --recursive rootfs が便利である
    • 先にマウントした pseudo-filesystem もまとめて処理できる
  • RAUC bundle を作る前に casync store と blob index を生成する
    • 例は casync make --store=rootfs.img.castr bundle/rootfs.img.caibx rootfs.img である
  • RAUC bundle には 3 つのファイルが必要である
    • manifest.raucm
    • rootfs.img.caibx
    • filesystem UUID を含む UUID
  • manifest.raucm には更新情報と rootfs イメージ情報を記述する
    • compatible=steamos-amd64
    • version=$version
    • sha256
    • size
    • filename=rootfs.img.caibx
  • UUID ファイルは blkid -s UUID -o value rootfs.img >bundle/UUID で作成する
  • 3 つのファイルを準備したら rauc bundle を実行する
    • --signing-keyring--cert--key に証明書と鍵を指定する
    • 生成物は rootfs.img.raucb である
  • rootfs.img.raucbrootfs.img.caibx は、client.confImagesUrl が指す Web サーバーへアップロードする
    • 2 つのファイルは同じディレクトリに置く必要がある

独自更新サーバーと適用

  • QueryUrlMetaUrl に使う Web サーバーは JSON ファイルを提供する必要がある
  • 単純な構成では live.json 1 つで十分である
    • .minor.candidates[0].image オブジェクトは、イメージ内の /lib/steamos-atomupd/manifest.json と一致している必要がある
    • update_path は、更新クライアントが ImagesUrl の後ろに付けて bundle をダウンロードするパスである
  • 例の Caddy 設定では、steamos-atomupdQueryUrlMetaUrl に送るリクエストを live.json へ rewrite する
    • /updates/live.json に rewrite する
    • /meta/*/*/*/*.json/meta/*/*/*/*/*.json/live.json に rewrite する
    • file_server browse を使う
  • 実際の SteamOS の QueryUrlMetaUrl には、より多くのロジックがあるようだが、この構成だけでも steamos-atomupd は新しい更新を見つけられる
  • すでに告知済みのイメージが現在実行中なら更新を避けるロジックがある
  • 既存の SteamOS インストールを独自イメージへ更新するには、/etc/rauc/keyring.pem/etc/steamos-atomupd/client.conf を修正すればよい
    • steamos-readonly disable は不要である
    • 変更は /etc overlay に入る
    • steamos-update 実行後は、/var/lib/overlays/etc/upper からそれらの変更を整理することを検討してよい
  • Valve の復旧イメージの 1 つを修正し、rootfs を独自イメージに差し替えれば、改変版 SteamOS をインストールすることもできそうだが、この方法は未検証である

1件のコメント

 
GN⁺ 2024-01-01
Hacker News のコメント
  • 自分が持っているデバイスのソフトウェア/OSを深くカスタマイズするこういう記事は好き。Steam Deckでは Tivoization を心配しなくていい点もうれしい
    記事でいちばん興味深かったのは /nix パーティションだった。Steam Deckが nixpkgs をサポートしているとは知らなかったが、さらに調べてみると、デフォルトインストールではないものの、OS全体をフォークしなくてもデバイスに載せることはできるようだ

    • Nixはもともと、どんな *nix OSにも「OS全体をフォーク」せずにインストールできた
      Nixストアは書き込み可能な任意の場所に置けるし、$PATH をシンボリックリンクのディレクトリに向けるよう変更すればいい
    • Steam Deckでnixpkgsの何を使っているのか知っている人はいる?自分も nixpkgs をかなり使っているので気になるが、残念ながらSteam Deckは持っていない
  • 本当に丁寧で興味深い記事。個人的には絶対ここまではやらないと思う
    Linuxを触った経験もRaspberry Piを使っていた頃がすべてで、それもせいぜい1%くらいなので、筆者はすごいと思う

    • 自分も筆者と似たような状況だった。かなり長い間、かなり特殊な理由で自前で Red Hatカーネル をビルドしなければならなかった。RMRRチェックを回避して、GPUをWindows VMにパススルーするためだった
      https://github.com/kiler129/relax-intel-rmrr に似ているが、自分のリポジトリではない
      根本原因はメーカーのROMアップデートでしか解決できないが、自分が使っている古いDL360はHPEのサポートが終了している
      パッチ自体は1行の変更だが、カーネル更新が面倒。SRPMを取得する必要があり、GitリポジトリがないのでSRPMを展開してパッチを適用し、再ビルドしてインストールしなければならない
    • TVが欲しいなら、近いうちに本当に選択肢がなくなるかもしれない
  • すでに SteamOS の要素をベースに、PCとコントローラー利用向けに調整されたディストリビューションがある。ChimeraOSはSteam Deck向けの追加ツールであるEmuDeckまで含んでいて、自分の環境ではかなり問題なく動いている

  • unRaid NASサーバー上でSteam HeadlessをよくできたDockerイメージとして動かし、WindowsノートPCからMoonlightのようなクライアントで接続するためにGPUを注文した
    うまくいけば、ほとんどアイドル状態のNASがあるのにゲーミングデスクトップ用ハードウェアをまた買うよりずっとよい。ただし使っていないときはNvidiaカードの電力設定をアイドル状態に保つ必要がある。おそらく nvidia-persistenced の呼び出しで可能だと期待している
    1: https://github.com/Steam-Headless/docker-steam-headless

    • 自分もほぼ同じことを GOW で動かそうとして、かなり時間を費やした。思ったよりずっと難しく、Xサーバー設定を合わせるにはHDMIダミープラグまで必要だった
      1: https://github.com/games-on-whales/gow
    • 別の代替案として、GPUパススルー 付きのKVMを立ち上げ、cloud-initでSunshineとゲームを実行するか、単にモニターを直接使ってもよい
      https://kubevirt.io/user-guide/virtual_machines/host-devices...
      宣言的なクラウドネイティブのゲーム起動だなんて!
      kubectl apply -f crysis.yaml
    • よさそう。今はSunshine + Moonlightを使っているが、近いうちに Steam Headlessの性能 をテストしてみる予定
    • かなり興味深い。こういう形でローカルネットワーク上でストリーミングするとき、入力遅延 や映像品質の限界は感じる?
    • いいね。Civilizationのような ターン制ホットシートゲーム をサーバーで動かしておき、ブラウザからリモート接続して、友人たちといつでもどこでも長いターン制ゲームができる構成をずっと想像していた
  • 今日 RAUC(https://rauc.io/) を知った。ValveがA/Bアップデート方式をどう実装しているのか気になっていた

  • Netscapeの 流れ星favicon がちょっと恋しくない?

  • 興味深い記事。A/Bアップグレードは少しやりすぎに見える。問題が起きたらライブディストリビューションを起動するか、古いバージョンのリカバリーシステムを別パーティションにインストールすればいいのだから
    ここ数年NixOSを使ってからArchに戻ったが、その前も長くArchを使っていたし、筆者の懸念は外れているように思う
    Archは確かに非常に真面目で成熟したディストリビューションであり、Valveより信頼できる
    Archへ移った理由は パッケージ品質 だ。メインリポジトリは本当に素早く更新され、AURには有用なパッケージが多い

    • あなたや私はライブディストリビューションを起動できるが、圧倒的多数のコンピューターユーザーはできない。Valveは明らかに平均的なユーザー向けに作ることに集中しており、Linuxディストリビューションは、どれだけ好きでも、まだそれをうまくできていない
      アップグレード失敗後にシステムが自動で復旧することは、現時点の 低管理OS ではほぼ必須に近い
    • Steam Deckユーザーに、壊れたアップグレードを直すために ライブディストリビューションの起動 が必要だと期待すべきではない。シームレスに、裏側で自動処理されるべきだ
    • 可能ではあるだろうが、今はそういうことを不要にする技術があり、ディスクもそこまで高くないのだから、あえてやらない理由はない
    • Steam Deckは本質的にはビデオゲーム用の Chromebook に近いので、ChromeOSの壊れにくいパーティション方式は合理的なアイデアに見える
    • Archのパッケージ品質が理由でNixOSから移ったという点について、例を挙げてもらえる?
      自分は概して NixOSパッケージ の品質は高いと思っている
  • 最近、ゲーム用携帯機の Legion Go を手に入れて、Linux にもう少し触れるようになっている。以前は終わりのないいじり回しによる時間の無駄に見えたし、実際に使いたいものとの互換性も限られていたので避けていた
    不変ファイルシステムや、従来の Linux があらゆる任意のソフトウェアに root 権限を簡単に渡してしまうという話を見て、興味が湧いた
    今は NixOS を使っているが、確かにいじり回す時間の無駄になり得る一方で、探索用としてはよい。さまざまな構成要素を簡単に試せて、維持しないと決めたら ~/.config の汚染程度を除けば完全に削除できる。インストール前にパッチを当てるのも些細なことなので、ゲーム用携帯機のような特殊なハードウェアで Linux を使えるようにするカーネルパッチも簡単に追加できる
    Jovian という NixOS コミュニティが、Valve の任意の SteamOS tarball 群を GitHub のタグ付きコミットとして再構成しているので、Valve 社員のようにソースを見て回れる。Nix 設定に数行追加するだけで、NixOS の上に自分用の SteamOS コピーをインストールできるようにしている
    彼らは明らかに Linux の専門家で、ソースを見ると、電源ボタンの位置をハードコードせずに検査する、といった単純な調整を除けば、Valve のパッケージを手つかずの状態で受け取っていることが確認できる
    純粋な SteamOS 体験が欲しいが Valve の更新システムのミラーを自前で運用したくない、あるいは 3GB の tarball を受け取らずに Valve のソースを探索したいなら、Jovian を試す価値がある
    インストール手順: https://jovian-experiments.github.io/Jovian-NixOS/getting-st...
    Valve ソースミラー: https://github.com/orgs/Jovian-Experiments/repositories?type...

    • 私も Steam Deck で Jovian-NixOS を問題なく快適に使っているので、強くおすすめする
  • bazzite.gg もこれをとてもうまくやっている。AMD ハードウェアでは 120Hz VRR がそのまま動作し、HDR 対応もアルファテストできる

    • Bazzite は初めて聞いた
      「Bazzite は Steam Deck の代替 OS として使える OCI イメージであり、デスクトップ PC、リビングのホームシアター PC、複数の携帯型 PC 向けの、すぐゲームできる SteamOS 風環境だ」
      https://github.com/ublue-os/bazzite/
      興味がなくても README は読む価値がある。含まれているものの一覧が非常に長く、特にゲーマーやストリーマーにとってかなり格好よく便利そうな項目が多い
    • Bazzite と 不変 Linux 全般が興味深い
      HN コメントひとつで簡潔かつ完璧に説明できるほど深く掘り下げてはいないが、要点は、root に読み取り専用の検証済み Linux ディストリビューションを置き、その上にパッケージをレイヤーとして載せる方式だ。サーバー側のコンテナから多くの着想を得た構造になっている
      従来の Linux よりも安全で、信頼でき、再現可能で、カスタマイズしやすいものを目指している。欲しいパッケージをコンテナマニフェストに書いておけば、アップグレードが出たときにアップグレードを実行し、その上にパッケージを再インストールする
    • より関係がある点は、Bazzite を比較的簡単にフォークして、足りないパッケージや必要な設定を自分のカスタムイメージに追加し、インフラ作業の大半を GitHub Actions に任せられることだ
      https://universal-blue.org/guide/fork-your-own/
      そして不変 OS なので、問題が起きたら以前のイメージにロールバックすることもできる
    • Steam の年末振り返りによると、2023年は初めて Linux だけでゲームをした年で、今年のリリース作も一部含まれていた
      ほとんどは Steam Deck か、GPU パススルーを適用した仮想マシンで Bazzite を動かしてプレイしたが、本当によくできていた
    • Bazzite がもっと有名でないのが驚きだ。自分が夢見ていたまさにそれなのに、最近まで存在すら知らなかった
  • リビング PC ということは、もう HTPC や「メディアセンター」という表現はあまり使わないのかな?