- 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로 묶는 작업임 - 자체 웹 서버가
live.json을 제공하고steamos-atomupd의QueryUrl,ImagesUrl,MetaUrl을 바꾸면 기존 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로 보내 새 업데이트가 있는지 확인함
- 이 명령은 Python 프로그램
- 새 업데이트가 있으면 서버는 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.5와sources/jupiter-3.5에서 찾을 수 있음 - 작성 시점 stable 이미지 커널은
6.1.52-valve9-1-neptune-61이며, 해당 소스 tarball은 2.9GiB임 - tarball이 큰 이유는 전체 Linux Git tree가 들어 있기 때문임
- tarball 안에는
PKGBUILD,config,config-neptune,archlinux-linux-neptune/등이 포함됨 archlinux-linux-neptune/는 바로 작업할 수 있는 일반 working tree가 아니라 bare repository임
- tarball 안에는
- 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...]를 실행한 뒤 웹 호스트에 업로드함 - 이 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도 있음
- 작성 시점 stable 버전은
- rootfs 다운로드는
steamos-atomupd-client와 같은 순서를 따름- RAUC bundle인
.raucb파일을 다운로드함 - SquashFS 파일시스템인 번들에서
rootfs.img.caibx를 추출함 casync extract로.castrstore에서 조각을 받아rootfs.img를 생성함.castrstore URL은 RAUC bundle URL의.raucb를.castr로 바꾼 것임- 이 동작은
steamos-atomupd에 하드코딩되어 있음 - 자동화 스크립트는 fetch-current.sh에 있음
- RAUC bundle인
- 인접한
.img.zip과.img.zst파일은 rootfs가 아니라 별도 부팅 가능한 복구 이미지임- 복구 이미지에서 rootfs 파티션을 추출해 다음 단계에 쓸 수도 있음
- 하지만 RAUC와 casync로 받은 이미지와 bit-for-bit 동일하지 않았고, 다시 update bundle을 만들 때 어차피 해당 도구가 필요함
- rootfs를 수정하기 전에는 filesystem UUID를 바꿔야 함
- 기존 SteamOS 이미지에서 커스텀 이미지로 업데이트할 때 UUID를 바꾸지 않으면 서로 다른 두 filesystem이 같은 UUID를 갖게 됨
- 이 상태는 문제를 일으킬 수 있음
- 예시는
btrfstune -fu rootfs.img임
- Valve는 zstd 압축을 쓰는 Btrfs 이미지를 사용함
- 변경 중 압축을 유지하려면
mount -o compress=zstd rootfs.img rootfs로 마운트함 - SteamOS는 Btrfs의
readonlysubvolume 속성을 쓰므로btrfs property set -ts rootfs ro false로 해제함
- 변경 중 압축을 유지하려면
- Linux 커널 같은 패키지 수정은
/dev와/proc을 요구하는 스크립트를 트리거할 수 있음devtmpfs와proc을 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/$arch,SigLevel = 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과 함께 종료됨 - 수동 증가를 피하려고
N에HHMMSS또는 Unix timestamp를 넣는 방식이 가능함 manifest.json의buildid와os-release의BUILD_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도 자체 서버로 바꿈QueryUrlImagesUrlMetaUrl
- 5GiB Btrfs 이미지 공간을 넘지 않는 한 다른 변경도 가능함
- 예를 들어 네트워크에서
hostname.local로 SteamOS 장치를 찾고 싶으면rootfs/usr/lib/systemd/resolved.conf.d/00-disable-mdns.conf를 제거할 수 있음 /etcoverlay 설정으로도 override할 수 있지만 번거롭다고 판단함
- 예를 들어 네트워크에서
- 이미지 없이 쉽게 할 수 있는 변경은 rootfs에 넣지 않는 쪽이 원칙임
- Firefox를 rootfs에 설치할 수는 있음
- 하지만 Firefox 보안 업데이트마다 이미지를 다시 repack해야 함
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에는 세 파일이 필요함
manifest.raucmrootfs.img.caibx- filesystem UUID를 담은
UUID
manifest.raucm에는 update 정보와 rootfs 이미지 정보가 들어감compatible=steamos-amd64version=$versionsha256sizefilename=rootfs.img.caibx
UUID파일은blkid -s UUID -o value rootfs.img >bundle/UUID로 만듦- 세 파일을 준비한 뒤
rauc bundle을 실행함--signing-keyring,--cert,--key에 인증서와 키를 지정함- 결과물은
rootfs.img.raucb임
rootfs.img.raucb와rootfs.img.caibx는client.conf의ImagesUrl이 가리키는 웹 서버에 업로드함- 두 파일은 같은 디렉터리에 있어야 함
자체 업데이트 서버와 적용
QueryUrl과MetaUrl에 쓰는 웹 서버는 JSON 파일을 제공해야 함- 간단한 구성에서는
live.json하나로 충분함.minor.candidates[0].image객체는 이미지 안의/lib/steamos-atomupd/manifest.json과 같아야 함update_path는 업데이트 클라이언트가ImagesUrl뒤에 붙여 bundle을 내려받을 경로임
- 예시 Caddy 설정은
steamos-atomupd가QueryUrl과MetaUrl로 보내는 요청을live.json으로 rewrite함/updates를/live.json으로 rewrite함/meta/*/*/*/*.json과/meta/*/*/*/*/*.json을/live.json으로 rewrite함file_server browse를 사용함
- 실제 SteamOS의
QueryUrl과MetaUrl은 더 많은 로직이 있는 것으로 보이지만, 이 구성만으로도steamos-atomupd가 새 업데이트를 찾을 수 있음 - 이미 광고된 이미지가 현재 실행 중이면 업데이트를 피하는 로직이 있음
- 기존 SteamOS 설치를 자체 이미지로 업데이트하려면
/etc/rauc/keyring.pem과/etc/steamos-atomupd/client.conf를 수정하면 됨steamos-readonly disable은 필요 없음- 변경은
/etcoverlay에 들어감 steamos-update실행 후에는/var/lib/overlays/etc/upper에서 해당 변경을 정리하는 것을 고려함
- Valve 복구 이미지 중 하나를 수정해 rootfs를 자체 이미지로 교체하면 변형 SteamOS를 설치할 수도 있을 것으로 보이지만, 이 방법은 테스트되지 않았음