136 Commits
Author SHA1 Message Date
theupriser ec174cc7b7 Merge pull request #21 from theupriser/feature/iso-release-notes
Sync git.upriser.nl mirror / sync (push) Skipped
Feature/iso release notes
2026-09-30 13:33:50 +02:00
theupriser 66d4b6c91d feat(ci): release title CachyOS <base> with Steamify <version> on GitHub and on the mirror
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 13:33:03 +02:00
theupriser 63b2bb96a0 fix(ci): the badge links to the public Gitea address (the runner's own server URL is http://server:3000)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 13:30:36 +02:00
theupriser e430e0bb01 Merge pull request #20 from theupriser/feature/iso-release-notes
Sync git.upriser.nl mirror / sync (push) Skipped
feat(ci): generated release notes (Steamify changelog and commits since the last release)
2026-09-30 13:30:21 +02:00
theupriser dac15ff9e4 feat(ci): release notes: wording when there is no earlier release to compare with
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 13:23:02 +02:00
theupriser 71d429915e feat(ci): release notes: clearer wording, workflow (ci) and docs commits left out of the ISO's change list, checksum section
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 13:22:47 +02:00
theupriser acde716b86 feat(ci): release notes are generated: the base version, Steamify's changelog section and the commits since the previous release; the mirror's release uses the same text 2026-09-30 13:20:22 +02:00
theupriser d077348ca2 Merge pull request #19 from theupriser/feature/iso-build-badge
Sync git.upriser.nl mirror / sync (push) Skipped
feat(ci): build badge in the GitHub release (running, succeeded, failed)
2026-09-30 13:07:49 +02:00
theupriser 2686b20fd6 feat(ci): a build badge in the GitHub release: running, then succeeded / failed / cancelled from the mirror's build
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 13:07:30 +02:00
theupriser f1689f5c6c Merge pull request #18 from theupriser/feature/iso-steamify-ref
Sync git.upriser.nl mirror / sync (push) Skipped
feat(ci): build the ISO with a Steamify branch (steamify_ref)
2026-09-30 13:06:52 +02:00
theupriser 2494c1ad2e feat(ci): build the ISO with a Steamify branch (input steamify_ref): a release branch tests its own version before it is released 2026-09-30 12:44:49 +02:00
theupriser 46ecbac514 Merge pull request #17 from theupriser/bugfix/iso-dispatch-build
Sync git.upriser.nl mirror / sync (push) Skipped
fix(ci): start the mirror's build by dispatch (a replaced tag starts no run)
2026-09-30 12:42:25 +02:00
theupriser bc75c692c6 fix(ci): dispatch the mirror's build with the full ref (refs/tags/<tag>) and show the server's error
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 12:40:28 +02:00
theupriser a0fcce57e8 fix(ci): the mirror's build is started by dispatch after the mirror has the tag (a replaced tag starts no run by itself)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 12:38:22 +02:00
theupriser d6b63937ae Merge pull request #16 from theupriser/bugfix/iso-mirror-wait
Sync git.upriser.nl mirror / sync (push) Skipped
fix(ci): wait for the mirror to drop the replaced tag
2026-09-30 12:37:06 +02:00
theupriser 6222bf7c5c fix(ci): wait for the mirror to drop the old tag by the tag list (the single-tag URL always answers 404 there) 2026-09-30 12:35:46 +02:00
theupriser 38bc1c1aa9 Merge pull request #15 from theupriser/feature/iso-kind-input
Sync git.upriser.nl mirror / sync (push) Skipped
feat(ci): the kind of ISO build as an input, daily names, 10 releases per kind
2026-09-30 11:39:30 +02:00
theupriser 588627a14d feat(ci): ISOs are named by the day like CachyOS (steamify-cachyos-<ver>-<YYMMDD>), one per day and kind, at most 10 dev and 10 real releases kept
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 11:35:22 +02:00
theupriser 38ac8f1cba feat(ci): iso-1-github-tag.yml takes the kind of build (release, dev, auto) so it always runs from master 2026-09-30 11:32:25 +02:00
theupriser ceadf20a1b Merge pull request #14 from theupriser/feat/steamify
Sync git.upriser.nl mirror / sync (push) Skipped
Feat/steamify
2026-09-30 10:43:25 +02:00
theupriser 11e2ba62e9 ci: the Gitea flow only builds and releases; the VM tests run on GitHub
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 10:27:07 +02:00
theupriser 504f066a3f ci: the ISO's install test runs on Steam Machine hardware and on a plain PC (3 VMs at a time each, the plain PC after)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 10:12:52 +02:00
theupriser a7f84cd5a0 ci: the test jobs use no actions (the runner's shared action clone broke parallel jobs); logs go into the job log
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 10:03:17 +02:00
theupriser 21e52ec81c ci: keep the install and serial logs of the boot loader VMs
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 09:43:12 +02:00
theupriser f37dcf6aa6 ci: the ISO's install test is a plain matrix again (one job per loader, 5 GB to install)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 09:40:59 +02:00
theupriser 5dea6ac7d9 ci: the ISO flow only tests that the ISO installs correctly (vmbootloadertest.sh --quick)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 09:38:42 +02:00
theupriser ea7cbfd179 ci: drop the suites stage from the ISO flow (steamify-cachyos tests steamify.sh in its own vmtest.yml)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 09:33:09 +02:00
theupriser 02c207c9e5 ci: two fixed boot loader lanes (the runner ignores max-parallel)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 09:02:23 +02:00
theupriser 13e4bbd0d6 ci: sync the git.upriser.nl mirror on every branch and tag
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 08:54:43 +02:00
theupriser 55a1c6ab61 ci: sync the git.upriser.nl mirror on pushes to master, feat/steamify and release branches (the mirror has no interval of its own)
Sync git.upriser.nl mirror / sync (push) Skipped
2026-09-30 08:54:06 +02:00
theupriser 85205dd614 ci: the suites' base VM is installed from the ISO this run built (Steamify step skipped); no CachyOS ISO download or cache 2026-09-30 08:46:11 +02:00
theupriser 34e3d2ae4a ci: the CachyOS ISO cache per CachyOS release again (the Steamify ISO of the run goes to the jobs as an artifact) 2026-09-30 08:43:23 +02:00
theupriser 448e2a5d79 ci: the CachyOS ISO cache gets an entry per workflow run 2026-09-30 08:43:07 +02:00
theupriser 2b72d90d30 ci: cache keys per ISO week instead of per run (no new multi-GB entry on every run) 2026-09-30 08:38:04 +02:00
theupriser af30018bd9 ci: cache pacman packages (build, test, suites), the VMs' packages and the CachyOS ISO 2026-09-30 08:29:41 +02:00
theupriser a74500b692 ci: a new run of the ISO workflow cancels the running one (one concurrency group, not per tag) 2026-09-30 08:28:27 +02:00
theupriser 779ee972cf ci: a suites stage after the boot loader tests (vmtest.sh: cli, menu, hw, installer, toggles on a plain CachyOS VM) 2026-09-30 08:27:34 +02:00
theupriser cfacfb2493 ci: 6 GB to install, 4 GB to test, 2 vCPUs: lean with headroom 2026-09-30 08:25:53 +02:00
theupriser d002c7d332 ci: lean test VMs: 2 vCPUs and 4 GB each, also to install 2026-09-30 08:25:28 +02:00
theupriser 028416b760 ci: 6 GB for the installer VM (VM_INSTALL_MEM) 2026-09-30 08:25:14 +02:00
theupriser 29ec7d2cde ci: boot loader test VMs keep their own RAM defaults (8 GB to install, 4 GB to test) 2026-09-30 08:24:27 +02:00
theupriser 8a98315bd5 ci: boot loader test VMs with 3 vCPUs each (the runner has 8 threads) 2026-09-30 08:24:06 +02:00
theupriser edb58b5ba9 ci: boot loader tests two at a time (8 GB VMs, ~20 GB of RAM) 2026-09-30 08:21:08 +02:00
theupriser e165202075 feat(ci): Gitea flow is build -> test (boot loader test per loader, parallel) -> release; drop upstream build.yml
iso-2-gitea-build.yml now has three jobs. The ISO goes between them as an artifact; the test job runs steamify-cachyos-dev's vmbootloadertest.sh --install for limine, systemd-boot and grub (needs /dev/kvm on the runner, fails at once with a reason without it); the release is only made when every test passed.
2026-09-30 08:20:51 +02:00
theupriser 2b7d2cde83 Merge pull request #13 from theupriser/feat/steamify
Desktop ISO / Build (push) Skipped
Desktop ISO / Testing (grub / btrfs) (push) Skipped
Desktop ISO / Testing (grub / ext4) (push) Skipped
Desktop ISO / Testing (grub / f2fs) (push) Skipped
Desktop ISO / Testing (grub / xfs) (push) Skipped
Desktop ISO / Testing (grub / zfs) (push) Skipped
Desktop ISO / Testing (limine / btrfs) (push) Skipped
Desktop ISO / Testing (limine / ext4) (push) Skipped
Desktop ISO / Testing (limine / f2fs) (push) Skipped
Desktop ISO / Testing (limine / xfs) (push) Skipped
Desktop ISO / Testing (limine / zfs) (push) Skipped
Desktop ISO / Testing (refind / btrfs) (push) Skipped
Desktop ISO / Testing (refind / ext4) (push) Skipped
Desktop ISO / Testing (refind / f2fs) (push) Skipped
Desktop ISO / Testing (refind / xfs) (push) Skipped
Desktop ISO / Testing (refind / zfs) (push) Skipped
Desktop ISO / Testing (systemd-boot / btrfs) (push) Skipped
Desktop ISO / Testing (systemd-boot / ext4) (push) Skipped
Desktop ISO / Testing (systemd-boot / f2fs) (push) Skipped
Desktop ISO / Testing (systemd-boot / xfs) (push) Skipped
Desktop ISO / Testing (systemd-boot / zfs) (push) Skipped
Release feat/steamify: renamed ISO workflows, mirror sync via secrets
2026-09-30 08:14:57 +02:00
theupriser 44354f99fa refactor(ci): name the ISO workflows after their order and host, secrets for the mirror's URL and token
- iso-release.yml -> iso-1-github-tag.yml (ISO 1/2: tag and release, GitHub)
- steamify-iso.yml -> iso-2-gitea-build.yml (ISO 2/2: build and publish, Gitea)
- the mirror sync takes its URL and token from GIT_UPRISER_URL / GIT_UPRISER_TOKEN and retries
- docs and comments follow the new names
2026-09-30 08:11:31 +02:00
theupriser f408f70e76 docs: use the renamed steamify-cachyos-live-iso repo name
- README clone instructions and the packagechooserq patch comment
2026-09-30 07:47:47 +02:00
theupriser c421549151 Merge pull request #12 from theupriser/bugfix/agents-release-pipeline
docs: AGENTS: the release pipeline
2026-09-29 23:05:57 +02:00
theupriser 7b7f3e6485 docs: AGENTS: the release pipeline (GitHub tag, ISO built and attached on the Gitea mirror) 2026-09-29 23:05:50 +02:00
theupriser fbbc22472e Merge pull request #11 from theupriser/bugfix/iso-sudo-env
fix: sudo mkarchiso keeps the release tag's variables
2026-09-29 22:39:46 +02:00
theupriser 85d49cf5c5 fix: sudo mkarchiso keeps STEAMIFY_ISO_VERSION and STEAMIFY_BUILD_STAMP (sudo dropped them: the ISO was named local, labelled LOCAL) 2026-09-29 22:39:39 +02:00
theupriser 68764f39d2 Merge pull request #10 from theupriser/bugfix/iso-no-own-clock
fix: the ISO build reads no clock of its own
2026-09-29 22:19:35 +02:00
theupriser 3ab89a638d fix: the ISO build reads no clock of its own: file name, label and boot menu come from the release tag only (GitHub's time); without a tag: local 2026-09-29 22:19:28 +02:00
theupriser 81961beaed Merge pull request #9 from theupriser/bugfix/iso-time-utc
fix(ci): the ISO release's time in UTC
2026-09-29 22:18:38 +02:00
theupriser 0a413a3d95 fix(ci): the ISO release's time in UTC again (GitHub's clock when the release starts; the name says UTC) 2026-09-29 22:18:31 +02:00
theupriser e78bbb2660 Merge pull request #8 from theupriser/bugfix/iso-step-name
feat(ci): ISO releases: not on every push, Dutch time, annotated tags
2026-09-29 22:14:37 +02:00
theupriser d3956eef17 feat: the ISO's boot menu and /etc/steammachine-iso-build name its release tag too (were the build date): the tag decides everything 2026-09-29 22:12:12 +02:00
theupriser f19dd0867a feat: the ISO's file is named after its release tag (steamify-cachyos-2.9.3-dev.2026.09.29-2230-x86_64.iso), so the GitHub release links straight to the ISO and its SHA-256 on the mirror 2026-09-29 22:11:32 +02:00
theupriser fc4c3ec95d feat(ci): ISO releases by a Steamify release or by hand (not every push), Dutch time in the name, tag and label, annotated tags (their own date: the mirror sorts releases on the same commit in order) 2026-09-29 22:09:33 +02:00
theupriser 1ca1a71ed2 fix(ci): the Steamify step's name (the version isn't known yet when Gitea evaluates it) 2026-09-29 21:57:34 +02:00
theupriser 5837d7343a Merge pull request #7 from theupriser/feature/iso-release-from-github-tag
Steamify ISO release (tag) / tag (push) Skipped
feat(ci): ISO releases start on GitHub, the Gitea mirror attaches the ISO
2026-09-29 21:39:08 +02:00
theupriser ff0ec9f11e feat(ci): ISO releases start on GitHub (tag + notes, iso-release.yml); the Gitea mirror builds on that tag and attaches the ISO (a release on the mirror survives its syncs only with a tag from GitHub); steamify-prepare.sh takes an exact STEAMIFY_VERSION 2026-09-29 21:39:00 +02:00
theupriser da1524961b Merge pull request #6 from theupriser/feature/iso-label
Steamify ISO (Gitea release) / iso (push) Successful in 17m41s
feat: the ISO's label follows the Steamify version and the build time
2026-09-29 20:53:29 +02:00
theupriser a11266e2b7 feat: the ISO's label follows the Steamify version on it and the build time (STEAMIFY_2_9_1_20260929_1432, was COS_<year><month>); the release workflow names the same moment 2026-09-29 20:53:22 +02:00
theupriser 1a55d5bb02 Merge pull request #5 from theupriser/feature/iso-version-follows-steamify
feat(ci): the ISO's version follows Steamify, and master releases
2026-09-29 20:51:44 +02:00
theupriser 5a62fc818c feat(ci): the ISO's release name gets the build time too (v<steamify>-<date>-<HHMM>, UTC): every build its own release 2026-09-29 20:51:37 +02:00
theupriser a0ffe148f5 feat(ci): the ISO's version follows Steamify: master releases v<steamify>-<date>, feat/steamify a test build v<steamify>-dev.<date>; CachyOS's own ISO workflow only runs in CachyOS's repo 2026-09-29 20:51:13 +02:00
theupriser d5c7085b82 Merge pull request #4 from theupriser/feature/gitea-iso-release
feat(ci): build and release the Steamify ISO on the Gitea mirror
2026-09-29 20:44:47 +02:00
theupriser ac79172c51 feat(ci): the Steamify ISO is built and released on the Gitea mirror (v<date>, notes from CHANGELOG.md; GitHub skips it) 2026-09-29 20:44:32 +02:00
theupriser f71ef695db Merge pull request #3 from theupriser/fix/steamify-install-var-log
fix: Steamify's install-time logs are readable after the first boot
2026-09-29 19:08:54 +02:00
theupriser 631a4e4dd8 fix: steamify-install leaves /var/log mounted (the caller reads its log afterwards; unmounting it hid the log again) 2026-09-29 18:47:59 +02:00
theupriser faa57cdb7f fix: steamify-install mounts the new system's /var/log (@log) first, so its logs aren't hidden under the mount after boot 2026-09-29 18:13:00 +02:00
theupriser ca74e10eee Merge pull request #2 from theupriser/fix/systemd-boot-efi-entry
fix: systemd-boot gets a working EFI boot entry
2026-09-29 17:45:38 +02:00
theupriser df50367973 fix: the systemd-boot entry is made with efibootmgr from the live system (bootctl in the chroot wrote HD(0,GPT,0000...), which the firmware can't load) 2026-09-29 17:10:15 +02:00
theupriser d325209317 docs: CHANGELOG: CachyOS's 26.08 notes as they were, the Steamify Live ISO 1.0.0 notes as a subsection under it 2026-09-29 16:58:00 +02:00
theupriser 9a58cf7c00 docs: CHANGELOG: Steamify's changes go in the CachyOS version they are built on (26.08), not a version of their own 2026-09-29 16:56:30 +02:00
theupriser 1d25d1c7f6 docs: CHANGELOG: the name is CachyOS with Steamify Live ISO 2026-09-29 16:54:39 +02:00
theupriser 40a6b89610 docs: CHANGELOG: Steamify ISO 1.0.0 (unreleased), with the systemd-boot boot entry fix 2026-09-29 16:54:20 +02:00
theupriser 5a8bec572e fix: systemd-boot gets its EFI boot entry (bootctl skips the EFI variables in the installer's chroot) 2026-09-29 16:52:29 +02:00
theupriser e6148f9b5d Merge pull request #1 from theupriser/feature/limit-desktop-bootloader
feat: The installer only offers KDE Plasma and the supported boot loaders
2026-09-29 08:14:05 +02:00
theupriser 3cf29a814e feat: The installer only offers KDE Plasma, and the boot loaders Steamify supports (Limine, systemd-boot, GRUB; not rEFInd) 2026-09-29 07:57:50 +02:00
theupriser d52cd58420 docs: README: other desktops are not supported (was "don't work yet") 2026-09-28 19:47:37 +02:00
theupriser 112b9bf5d6 docs: README: choose KDE Plasma, other desktops don't work with Steamify yet 2026-09-28 19:46:45 +02:00
theupriser 02e75027e1 docs: README: how to build the Steamify ISO yourself (podman) 2026-09-28 19:33:54 +02:00
theupriser e3000ce8c7 fix: Only the installer says Steamify (CachyOS with Steamify Installer); the live session keeps CachyOS's own name 2026-09-28 19:14:17 +02:00
theupriser ce1cf71498 fix: The live session's About this System keeps CachyOS's own name (no renaming) 2026-09-28 19:12:35 +02:00
theupriser 8e7ec49a15 feat: The live session's About this System says CachyOS Linux with Steamify 2026-09-28 18:21:29 +02:00
theupriser 06d2fb564c feat: The installer is called CachyOS - Steamify Installer (branding productName, set after the launcher's reinstall); TODO: Hello's title, radio buttons stay 2026-09-28 18:18:09 +02:00
theupriser f33c47ec49 docs: TODO: the boot choice switch is still open (misread) 2026-09-28 18:13:38 +02:00
theupriser 95e05a577c docs: TODO: the boot choice switch is done 2026-09-28 18:13:25 +02:00
theupriser f46596d8e0 docs: TODO: full ISO install with the Steamify page passed 2026-09-28 18:12:16 +02:00
theupriser 6917f3d8e4 docs: TODO: the Steamify Summary works 2026-09-28 17:56:09 +02:00
theupriser 37aff659bd docs: AGENTS: qmptype.py types > and friends now 2026-09-28 17:38:10 +02:00
theupriser 0cc736776a fix: The launcher downloads Steamify and writes the page's rows (Items.qml, items.json) where the page reads them ($sdir was empty in its own local); --list errors go to its log 2026-09-28 17:37:48 +02:00
theupriser a817b8a558 feat: The Steamify page's Summary is an unordered list: every chosen item's name and where it starts (gaming mode or desktop) 2026-09-28 16:57:45 +02:00
theupriser 8246209a7e feat: The Steamify page's Summary line shows the chosen items' labels and where it starts, not raw ids (patch on packagechooserq, applied by build-calamares-modules.sh) 2026-09-28 16:56:45 +02:00
theupriser 62402ce4c8 feat: packagechooserq is built with the ISO (build-calamares-modules.sh) instead of a committed .so; the Steamify page is left out when Calamares' version differs from the one it was built for 2026-09-28 16:41:06 +02:00
theupriser ad246253c7 docs: move operational lessons from TODO.md into a new AGENTS.md
TODO.md should stay a plain task list; the Calamares module/build
gotchas, QMP-driving quirks, ISO permission pitfall and build-VM SSH
flakiness notes now live in AGENTS.md instead.
2026-09-28 15:42:57 +02:00
theupriser 5133323237 docs: TODO - note 2.7.0/2.8.0 are released, ISO builder already tracks latest 2026-09-28 15:40:45 +02:00
theupriser 17ecdc1a26 docs: update TODO with today's packagechooserq work and lessons learned 2026-09-28 15:39:18 +02:00
theupriser 219aef176a fix: make steamify.sh executable on the ISO
steamify-prepare.sh fetches it with curl -o, which doesn't carry the
executable bit, and profiledef.sh's file_permissions never listed this
path. calamares-online.sh execs it directly ("$sbin" --defaults --list)
to regenerate Items.qml/items.json at boot; without +x that failed
silently (stderr redirected), so items.json was never written and the
Summary step's prettyStatus() override always fell back to the raw,
comma-joined id list.
2026-09-28 15:28:56 +02:00
theupriser eb58ef24b4 feat: bake packagechooserq module into the ISO, fix Steamify page styling
packagechooserq is excluded from cachyos-calamares-next's own build, so its
.so + module.desc are added straight into airootfs (matching the module
built against the current cachyos-calamares-next). Also:

- SteamifyPage.qml: border-only row highlight (was a translucent teal fill
  that made white text unreadable), themed page background (was falling
  through to Calamares' default white), a sub-option "└" marker, more right
  padding so the row border clears the scrollbar.
- calamares-online.sh: also write items.json next to Items.qml, so
  packagechooserq's prettyStatus() can show the Summary step's choice as
  labels instead of raw, comma-joined ids.
2026-09-28 14:56:15 +02:00
theupriser 71c2ef0459 docs: TODO: packagechooser multi-select needs Shift/Ctrl-click, not usable 2026-09-28 13:45:19 +02:00
theupriser d84daa45dc docs: TODO: netinstall is the target module (real checkboxes); packagechooser works but looks wrong; keyboard-layout and Calamares-in-separate-tab gotchas 2026-09-28 13:41:38 +02:00
theupriser 6b69c1f619 docs: TODO correction - packagechooser mode:optionalmultiple works for the Steamify page, confirmed from global storage; no netinstall detour needed 2026-09-28 13:30:22 +02:00
theupriser 161c037600 docs: TODO: packagechooserq module isn't installed here; packagechooser is single-select regardless of mode; netinstall is the confirmed multi-checkbox module 2026-09-28 13:24:52 +02:00
theupriser 28fe327abe fix: buildiso.sh's EXIT trap no longer reports a fake error after a successful build 2026-09-28 13:06:49 +02:00
theupriser f58fa9bb1b feat: the Steamify page fetches the newest steamify.sh + rows before Calamares starts; fix layout warning and Theme.qml copy 2026-09-28 12:50:58 +02:00
theupriser 754a6a14da feat: Steamify installer page in progress (CachyOS look, rows from steamify.sh --defaults --list, after the packages page); TODO 2026-09-28 11:41:00 +02:00
theupriser 78e7c869b9 fix: the build finishes cleanly as root (chown with an empty $USER aborted it) and with the steamify-cachyos ISO name (no rename of a missing cachyos ISO) 2026-09-28 11:17:15 +02:00
theupriser 9c779c1d73 docs: TODO: the Steamify commit's hash after the author fix 2026-09-28 10:32:23 +02:00
theupriser 8569209af3 docs: TODO: the Steamify page passes the ticked ids (--options); build and install the ISO in the test VM 2026-09-28 10:08:49 +02:00
theupriser 63b1a769d5 fix: steamify-install passes --options (Steamify 2.7.0 dropped --skip); TODO: the Steamify page passes the ticked ids, build in the test VM 2026-09-28 10:08:19 +02:00
theupriser 9deb00708c docs: TODO: toggle switches like the Steamify app preferred 2026-09-27 22:21:28 +02:00
theupriser 71ce968129 docs: TODO: toggle switches like the Steamify app are fine too 2026-09-27 22:21:13 +02:00
theupriser 20230a4e5e docs: TODO: Steamify page with checkboxes, all ticked (the packagechooser page selects only one); PR status 2026-09-27 22:15:12 +02:00
theupriser 81f2eea63d feat: Steamify's choices in the installer: a Steam Machine page (what to leave out) and a Start in page (gaming mode or desktop), passed to steamify.sh --defaults 2026-09-27 21:59:38 +02:00
theupriser c6212a774b fix: The live session's name is set after the packages (a package overwrote airootfs' os-release) 2026-09-27 21:50:24 +02:00
theupriser abb534613e fix: Live power-off module: a fresh build folder per kernel, no clean in the same make (kbuild re-ran itself endlessly) 2026-09-27 21:31:57 +02:00
theupriser 309b22f634 feat: Live session: power-off fix built for the ISO's kernels, Vapor look and layout at the live login 2026-09-27 21:26:19 +02:00
theupriser f514c24b82 docs: TODO: 2.6.0 regression test on an existing install passed 2026-09-27 21:18:42 +02:00
theupriser c5c435c574 fix: The live session is called Steamify CachyOS, based on CachyOS 2026-09-27 21:07:39 +02:00
theupriser 2e35628ba8 feat: The live session is called Steamify, based on CachyOS (os-release, shown by Hello) 2026-09-27 21:04:44 +02:00
theupriser 05c04a013b feat: The ISO is called steamify-cachyos 2026-09-27 21:03:05 +02:00
theupriser 04793bd07d docs: TODO: done and open items separated, PR status checked 2026-09-27 20:59:51 +02:00
theupriser 43f17ec062 docs: TODO: regression test 2.6.0 on an existing install before merging 2026-09-27 20:55:58 +02:00
theupriser 0cabdbd8a7 docs: TODO: first desktop login from the ISO install passed 2026-09-27 20:52:32 +02:00
theupriser 974405ff96 docs: TODO: the VM install test passed 2026-09-27 20:49:53 +02:00
theupriser f42859ac6a fix: The installer's sudo rule is read last, or CachyOS's %wheel rule after it asks for the password 2026-09-27 20:26:45 +02:00
theupriser ae21fa9973 docs: TODO: Steamify wizard pages in the installer; LED check isn't a concern 2026-09-27 20:15:54 +02:00
theupriser bd16c4123d docs: TODO: what's in progress (build, install test, Steamify PR, hardware test) and later 2026-09-27 20:15:54 +02:00
theupriser c719f02f6d docs: TODO: power-off fix and Vapor theme in the live session, drop the Boost workaround later 2026-09-27 20:15:54 +02:00
theupriser f663ce8794 fix: CachyOS Hello installs it: version-tag is CachyOS's newest release (Hello calls newer ones testing ISOs), the build date goes to /etc/steammachine-iso-build 2026-09-27 20:07:56 +02:00
theupriser 56f5707c86 fix: The Steamify step is added by calamares-online.sh, which copies CachyOS's settings over /etc/calamares/settings.conf 2026-09-27 20:06:11 +02:00
theupriser f49504c7ce fix: Boost 1.91's libraries on the ISO, so cachyos-calamares-next (linked against 1.91) starts while the repos ship 1.92 2026-09-27 20:01:29 +02:00
theupriser 331c799ea7 feat: Steamify's default setup for the new user during the install (Calamares step before cleanup) 2026-09-27 19:29:04 +02:00
26 changed files with 1440 additions and 149 deletions

No files matched your search

-138
View File
@@ -1,138 +0,0 @@
name: Desktop ISO
on:
push:
branches:
- master
pull_request:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
container:
image: archlinux:base-devel
options: --privileged
name: Build
outputs:
builddate: ${{ steps.date.outputs.builddate }}
steps:
- name: Get build date
id: date
run: |
echo "builddate=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
- name: Clone CachyOS-Live-ISO
id: clone
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Add CachyOS keyring
id: keyring
run: |
pacman-key --init
pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com
pacman-key --lsign-key F3B607488DB35A47
sed -i '/^\[core\]$/i[cachyos]\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n' /etc/pacman.conf
- name: Install dependencies
id: dependencies
run: |
sudo pacman -Syu --noconfirm archiso cachyos-keyring mkinitcpio-archiso squashfs-tools grub
- name: Create build user
id: user-archiso
run: |
useradd builder -m
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
chmod -R a+rw .
- name: Build ISO (${{ steps.date.outputs.builddate }})
id: build
run: |
sudo -H -E -u builder ./buildiso.sh
- name: Upload ISO to artifacts
id: upload
uses: actions/upload-artifact@v4
with:
name: cachyos-desktop-linux-${{ steps.date.outputs.builddate }}
path: out/desktop/*
quicktest:
name: Testing (${{ matrix.bootloader }} / ${{ matrix.filesystem }})
runs-on: ubuntu-latest
needs: build
container:
image: archlinux:base-devel
options: --privileged
strategy:
fail-fast: false
max-parallel: 4
matrix:
bootloader: [grub, systemd-boot, refind, limine]
filesystem: [btrfs, xfs, ext4, f2fs, zfs]
steps:
- name: Clone CachyOS-Live-ISO
id: clone
uses: actions/checkout@v6
- name: Install dependencies
id: dependencies
run: |
pacman -Syu --noconfirm git ffmpeg imagemagick tesseract-data-eng qemu-desktop
- name: Create build user
id: user
run: |
useradd builder -m
echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
chmod -R a+rw .
- name: Build quickemu
id: quickemu
run: |
sudo -u builder git clone https://aur.archlinux.org/quickemu.git
cd quickemu
sudo -H -E -u builder makepkg --syncdeps --noconfirm
pacman -U --noconfirm quickemu*.pkg.tar.zst
cd ..
- name: Clone quicktest
id: clone-quicktest
uses: actions/checkout@v6
with:
repository: 'quickemu-project/quicktest'
path: 'quicktest'
- name: Download ISO
id: download
uses: actions/download-artifact@v4
with:
path: machines/cachyos-dailylive
merge-multiple: true
- name: Run quicktest
id: quicktest
run: |
./quicktest/quicktest test_install_calamares cachyos dailylive
env:
QT_NOTIFY: "false"
QT_OPEN_RESULTS: "false"
QT_QUICKGET_SKIP: "true"
QT_TESTCASES_DIR: "./testcases"
QUICKEMU_DISPLAY: "none"
QUICKEMU_VM_DIR: "./machines"
TEST_BOOTLOADER: "${{ matrix.bootloader }}"
TEST_FILESYSTEM: "${{ matrix.filesystem }}"
- name: Upload results to artifacts
id: upload-results
if: always()
uses: actions/upload-artifact@v4
with:
name: cachyos-desktop-linux-results-${{needs.build.outputs.builddate}}-${{ matrix.bootloader }}-${{ matrix.filesystem }}
path: results/*
+26
View File
@@ -0,0 +1,26 @@
name: Sync git.upriser.nl mirror
# Trigger the Gitea pull-mirror immediately instead of waiting for its interval,
# so Gitea builds/tests right after any branch or tag lands on GitHub (the mirror has no sync interval of its own).
on:
push:
branches: ['**']
tags: ['*']
workflow_dispatch:
jobs:
sync:
# Gitea also reads .github/workflows on the mirror; only run on GitHub
if: github.server_url == 'https://github.com'
runs-on: ubuntu-latest
steps:
- name: Trigger mirror-sync
env:
GITEA_URL: ${{ secrets.GIT_UPRISER_URL }}
GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }}
# Gitea "owner/repo"; defaults to the same path as on GitHub
GITEA_REPO: ${{ vars.GITEA_REPO || github.repository }}
run: |
curl --fail-with-body -sS --retry 5 --retry-delay 10 --retry-all-errors -X POST \
-H "Authorization: token ${GIT_UPRISER_TOKEN}" \
"${GITEA_URL%/}/api/v1/repos/${GITEA_REPO}/mirror-sync"
+151
View File
@@ -0,0 +1,151 @@
# The Steamify ISO's release, step 1 of 2 (GitHub): names it and creates its tag and a release with the notes.
# The Gitea mirror (git.upriser.nl) gets the tag with its next sync (triggered here when GIT_UPRISER_TOKEN is set),
# builds the ISO on that tag and attaches it to its own release for it (iso-2-gitea-build.yml). The ISO lives there:
# GitHub releases take at most 2 GB per file, the ISO is bigger, and a release on the mirror only survives its
# syncs when its tag comes from here.
# Name: the Steamify version the ISO gets (its newest release) plus the time (UTC, GitHub's clock when the release starts):
# kind release: v<steamify>-<YYMMDD>, a real release; kind dev: a pre-release v<steamify>-dev-<YYMMDD> (like CachyOS: the
# day, no time). One ISO per day and kind: the newest build of a day replaces the earlier one. At most 10 dev and 10 real
# releases are kept (the oldest go, here and, with the mirror's next sync, there).
name: ISO 1/2 · Tag and release (GitHub)
# Always run from master. Started by steamify-cachyos (its bundle.yml, secret ISO_DISPATCH_TOKEN), which says what to
# build: a version published from main -> kind release, a version on a release branch -> kind dev. Or by hand (Run
# workflow); `auto` is a release on master and a dev build on any other branch. Not started by pushes.
on:
workflow_dispatch:
inputs:
kind:
description: "release (a real ISO), dev (a pre-release test build) or auto (release on master, dev elsewhere)"
type: choice
options: [auto, release, dev]
default: auto
steamify_ref:
description: "Steamify branch or tag to build the ISO with (empty: the newest published release). A release branch tests its own version."
type: string
default: ""
permissions:
contents: write
env:
GITEA_URL: ${{ secrets.GIT_UPRISER_URL }}
GITEA_REPO: theupriser/steamify-cachyos-live-iso
jobs:
tag:
if: github.server_url == 'https://github.com'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Tag and release
env:
GH_TOKEN: ${{ github.token }}
GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }}
run: |
steamify="$(gh release view -R theupriser/steamify-cachyos --json tagName -q .tagName)"
steamify="${steamify#v}"
# A branch or tag of Steamify to build with (a release branch before its release), else the newest release.
sref="${{ inputs.steamify_ref }}"
if [ -n "$sref" ]; then
steamify="$(gh api "repos/theupriser/steamify-cachyos/contents/steamify.sh?ref=$sref" -q .content | base64 -d | sed -n 's/^VERSION=//p' | head -n 1)"
[ -n "$steamify" ] || { echo "::error::no VERSION in steamify.sh at $sref"; exit 1; }
fi
stamp="$(date -u +%y%m%d)" day="$(date -u +%Y.%m.%d)"
base="$(head -n 1 CHANGELOG.md | sed 's/^# *//')" # the CachyOS version (first line of the changelog)
kind="${{ inputs.kind }}"
if [ -z "$kind" ] || [ "$kind" = auto ]; then
if [ "${{ github.ref }}" = refs/heads/master ]; then kind=release; else kind=dev; fi
fi
if [ "$kind" = release ]; then
tag="v$steamify-$stamp" pre="" name="CachyOS $base with Steamify $steamify ($day UTC)"
else
tag="v$steamify-dev-$stamp" pre="--prerelease" name="CachyOS $base with Steamify $steamify (test build $day UTC)"
fi
# The ISO's file on the mirror is named after the tag (iso-2-gitea-build.yml, profiledef.sh), so its
# download link is known now.
file="steamify-cachyos-${tag#v}-x86_64.iso" dl="$GITEA_URL/$GITEA_REPO/releases/download/$tag"
# The notes are generated, nothing is written by hand:
# - the CachyOS base version (first line of CHANGELOG.md, the fork's upstream changelog),
# - the Steamify version's own changelog section (steamify-cachyos CHANGELOG.md at the ref or tag it is built from),
# - this repository's commits since the previous release of the same kind.
steamify_notes="$(gh api "repos/theupriser/steamify-cachyos/contents/CHANGELOG.md?ref=${sref:-v$steamify}" -q .content 2> /dev/null | base64 -d 2> /dev/null |
awk -v v="$steamify" 'index($0, "## " v " ") == 1 {f = 1; next} f && /^## / {exit} f')"
[ "$kind" = release ] && want_pre=false || want_pre=true
[ "$kind" = release ] && kind_name="real release" || kind_name="dev build"
prev="$(gh release list -L 50 --json tagName,isPrerelease,createdAt -q "[.[] | select(.isPrerelease == $want_pre and .tagName != \"$tag\")] | sort_by(.createdAt) | reverse | .[0].tagName // empty")"
# Commits that change the ISO itself: not the workflow ones (type or scope ci, e.g. ci: / fix(ci):) and not docs:.
if [ -n "$prev" ] && git rev-parse -q --verify "refs/tags/$prev" > /dev/null; then
range="$prev..HEAD"; limit=40; since="Since \`$prev\`:"
else
range="HEAD"; limit=12; since="The latest commits (there is no earlier $kind_name to compare with):"
fi
changes="$(git log --no-merges --format='%s' "$range" | grep -Ev '^(ci|docs)(\(.*\))?:|^[a-z]+\(ci\):' | sed 's/^/- /' | head -n "$limit" || true)"
[ -n "$changes" ] || changes="- No changes to the ISO itself."
notes="![ISO build](https://img.shields.io/badge/ISO_build-running-yellow)
**CachyOS $base with Steamify $steamify${sref:+ (branch $sref)} built in.** Install CachyOS and turn the PC into a Steam Machine in one go.
The ISO is built from \`$(git rev-parse --short HEAD)\` (${{ github.ref_name }}). It is about 3 GB, more than a GitHub release can hold (2 GB per file), so it is published on the mirror about 20 minutes after this release is created:
**Download: [$file]($dl/$file)** ([SHA-256]($dl/$file.sha256), [release page]($GITEA_URL/$GITEA_REPO/releases/tag/$tag))
### Steamify $steamify
${steamify_notes:-There is no changelog section for this version.}
### Changes in this ISO
$since
$changes
### Sources
[Steamify](https://github.com/theupriser/steamify-cachyos/tree/${sref:-v$steamify}), this repository at this tag, and the packages' sources at [CachyOS](https://github.com/CachyOS) and [Arch Linux](https://archlinux.org/packages/)."
# One ISO per day and kind: the newest build of a day replaces the earlier one (space), here and on the mirror.
if git ls-remote --exit-code --tags origin "refs/tags/$tag" > /dev/null 2>&1 || gh release view "$tag" > /dev/null 2>&1; then
echo "$tag exists: replacing it"
gh release delete "$tag" --cleanup-tag -y 2> /dev/null || true
git push origin ":refs/tags/$tag" 2> /dev/null || true
fi
# An annotated tag: its own date, so the mirror sorts releases on the same commit in order (a
# lightweight tag only has its commit's date).
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$tag" -m "$name" "$GITHUB_SHA" && git push origin "refs/tags/$tag"
gh release create "$tag" --verify-tag $pre --title "$name" --notes "$notes"
echo "Released $tag"
echo "ISO_TAG=$tag" >> "$GITHUB_ENV"
echo "STEAMIFY_REF=$sref" >> "$GITHUB_ENV"
# Retention (space): at most 10 dev and 10 real releases; the oldest go, release and tag. The mirror drops the
# tag with its release and ISO at its next sync (the step below).
keep=10
for pre_flag in true false; do
gh release list -L 200 --json tagName,isPrerelease,createdAt \
-q "[.[] | select(.isPrerelease == $pre_flag)] | sort_by(.createdAt) | reverse | .[$keep:][] | .tagName" |
while read -r old; do echo "Retention: removing $old"; gh release delete "$old" --cleanup-tag -y; done
done
# The mirror takes the tag (sync), then the build on the mirror is started explicitly (its dispatch API): a tag that
# is replaced (same name, a new tag object) starts no run there by itself. Needs a Gitea token with repository write
# access as the secret GIT_UPRISER_TOKEN; without it the mirror picks the tag up later and nothing is built.
- name: Sync the Gitea mirror and start its build
env:
GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }}
run: |
if [ -z "$GIT_UPRISER_TOKEN" ]; then echo "No GIT_UPRISER_TOKEN: the mirror is not synced and no build is started."; exit 0; fi
api="$GITEA_URL/api/v1/repos/$GITEA_REPO"
curl -fsS --retry 5 --retry-delay 10 --retry-all-errors -X POST -H "Authorization: token $GIT_UPRISER_TOKEN" "$api/mirror-sync"
echo "Mirror sync started"
want="$(git rev-parse "refs/tags/$ISO_TAG")"
for _ in $(seq 60); do
got="$(curl -s "$api/tags/$ISO_TAG" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))' 2> /dev/null)"
[ "$got" = "$want" ] && break
sleep 5
done
[ "$got" = "$want" ] || { echo "::error::the mirror does not have $ISO_TAG ($want) after 5 minutes"; exit 1; }
# ref: the full name (refs/tags/...), as in Gitea's API documentation; --fail-with-body shows the server's reason.
curl -sS --fail-with-body --retry 3 -X POST -H "Authorization: token $GIT_UPRISER_TOKEN" -H "Content-Type: application/json" \
-d "{\"ref\":\"refs/tags/$ISO_TAG\",\"inputs\":{\"steamify_ref\":\"$STEAMIFY_REF\"}}" "$api/actions/workflows/iso-2-gitea-build.yml/dispatches"
echo "Started the mirror's build for $ISO_TAG"
+247
View File
@@ -0,0 +1,247 @@
# The Steamify ISO's release, step 2 of 2 (the Gitea mirror, git.upriser.nl, on its own runner): builds the
# ISO for a release tag that iso-1-github-tag.yml created on GitHub (it comes in with the mirror's sync, and a
# release on the mirror only survives its syncs when its tag comes from GitHub), then attaches the ISO, its
# checksums and package list to the mirror's release for that tag. The tag names the Steamify version and
# the time: v<steamify>-<YYYY.MM.DD>-<HHMM> (master) or v<steamify>-dev.<...> (feat/steamify, a pre-release);
# the ISO gets exactly that Steamify release, and its label the same moment. GitHub skips it.
# Two jobs: build the ISO -> release it. The VM tests run on GitHub (steamify-cachyos vmtest.yml, the newest ISO release of
# this mirror), where the runners are bigger.
# The runner needs privileged containers (archiso mounts loop devices) and ~20 GB of disk; Gitea's
# [repository.release] FILE_MAX_SIZE must allow the ISO (default 2 GB).
name: ISO 2/2 · Build and publish (Gitea)
# Started by iso-1-github-tag.yml through the mirror's dispatch API, with the tag as the ref (a replaced tag starts no
# run by itself, so nothing is triggered by tag pushes).
on:
workflow_dispatch:
inputs:
steamify_ref:
description: "Steamify branch or tag to build with (set by iso-1; empty: the release the tag names)"
type: string
default: ""
# One run at a time for the whole workflow (not per tag): a new run stops the running one and takes over.
concurrency:
group: steamify-iso
cancel-in-progress: true
jobs:
build:
name: Build the ISO
if: github.server_url != 'https://github.com'
runs-on: ubuntu-latest
container:
image: docker.io/cachyos/cachyos:latest
options: --privileged
defaults:
run:
shell: bash
outputs:
steamify: ${{ steps.tag.outputs.steamify }}
steps:
# First only what the cache action needs (node, git), then the caches, then the rest from the restored cache.
- name: Node and git
id: node
run: |
pacman-key --init && pacman-key --populate
pacman -Syu --noconfirm --needed nodejs git
# The caches' keys: one entry per ISO week (an exact hit saves nothing, a new week restores the old one and saves once).
echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT"
- name: Cache - pacman packages
uses: actions/cache@v4
with:
path: /var/cache/pacman/pkg
key: pacman-build-${{ steps.node.outputs.week }}
restore-keys: pacman-build-
- name: Tools
run: pacman -S --noconfirm --needed archiso mkinitcpio-archiso squashfs-tools grub sudo curl jq
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Version and time from the tag
id: tag
run: |
tag="${{ github.ref_name }}"
[[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-(dev-)?([0-9]{6})$ ]] ||
{ echo "::error::$tag is not a Steamify ISO release tag (v<steamify>-[dev-]<YYMMDD>)"; exit 1; }
m=("${BASH_REMATCH[@]}")
{
echo "STEAMIFY_VERSION=${m[1]}"
# The day (YYMMDD, UTC) for the ISO's label (profiledef.sh) and the release's name: the tag's.
echo "STEAMIFY_BUILD_STAMP=${m[3]}"
echo "ISO_PRERELEASE=$([ -n "${m[2]}" ] && echo true || echo false)"
# The ISO's file name: the tag without its v (profiledef.sh).
echo "STEAMIFY_ISO_VERSION=${tag#v}"
} >> "$GITHUB_ENV"
echo "steamify=${m[1]}" >> "$GITHUB_OUTPUT"
- name: Steamify on the ISO (the version the tag names)
run: |
ref="${{ inputs.steamify_ref }}"
if [ -n "$ref" ]; then
# A branch (a release branch before its release): its checkout goes on the ISO.
git clone -q --depth 1 --branch "$ref" https://github.com/theupriser/steamify-cachyos.git /tmp/steamify-src
./steamify-prepare.sh /tmp/steamify-src
else
./steamify-prepare.sh
fi
- name: Build
run: |
./build-live-modules.sh
./build-calamares-modules.sh
./buildiso.sh -p desktop -w || ./buildiso.sh -p desktop -c -w
- name: Keep the ISO for the tests and the release
uses: christopherhx/gitea-upload-artifact@v4
with:
name: iso
path: |
out/desktop/*.iso
out/desktop/*.iso.sha256
out/desktop/*.iso.sha1
out/desktop/*.pkgs.txt
retention-days: 3
release:
name: Release
needs: build
runs-on: ubuntu-latest
container:
image: docker.io/cachyos/cachyos:latest
defaults:
run:
shell: bash
steps:
- name: Tools
run: |
pacman-key --init && pacman-key --populate
# nodejs: the actions below run in this container
pacman -Syu --noconfirm --needed git nodejs curl jq
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Version and time from the tag
run: |
tag="${{ github.ref_name }}"
[[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-(dev-)?([0-9]{6})$ ]] ||
{ echo "::error::$tag is not a Steamify ISO release tag (v<steamify>-[dev-]<YYMMDD>)"; exit 1; }
m=("${BASH_REMATCH[@]}")
{
echo "STEAMIFY_VERSION=${m[1]}"
# The day (YYMMDD, UTC) for the ISO's label (profiledef.sh) and the release's name: the tag's.
echo "STEAMIFY_BUILD_STAMP=${m[3]}"
echo "ISO_PRERELEASE=$([ -n "${m[2]}" ] && echo true || echo false)"
# The ISO's file name: the tag without its v (profiledef.sh).
echo "STEAMIFY_ISO_VERSION=${tag#v}"
} >> "$GITHUB_ENV"
- uses: christopherhx/gitea-download-artifact@v4
with:
name: iso
path: out/desktop
- name: Release name and notes
id: rel
env:
TOKEN: ${{ github.token }}
run: |
iso="$(ls out/desktop/*.iso | head -n 1)"
[ -f "$iso" ] || { echo "::error::no ISO in out/desktop"; exit 1; }
tag="${{ github.ref_name }}" b="$STEAMIFY_BUILD_STAMP"
stamp="20${b:0:2}.${b:2:2}.${b:4:2}"
base="$(head -n 1 CHANGELOG.md | sed 's/^# *//')" # the CachyOS version, as in the GitHub release's title
if [ "$ISO_PRERELEASE" = true ]; then
name="CachyOS $base with Steamify $STEAMIFY_VERSION (test build $stamp UTC)"
else
name="CachyOS $base with Steamify $STEAMIFY_VERSION ($stamp UTC)"
fi
# One ISO per day and kind: a release of this tag from an earlier build of the day is replaced.
api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
if curl -fsS -H "Authorization: token $TOKEN" "$api/releases/tags/$tag" -o /tmp/rel.json; then
echo "Replacing the earlier release of $tag"
curl -fsS -X DELETE -H "Authorization: token $TOKEN" "$api/releases/$(jq -r .id /tmp/rel.json)" || true
fi
{ echo "publish=true"; echo "name=$name"; } >> "$GITHUB_OUTPUT"
section="$(awk '/^## CachyOS with Steamify Live ISO/ {found = 1; next} found && /^#/ {exit} found' CHANGELOG.md)"
src="${{ inputs.steamify_ref }}"; src="${src:-v$STEAMIFY_VERSION}"
# The same notes as the GitHub release (one source: iso-1 generated them), without its badge line; the
# checksum line is added here. Fallback: a short text.
if curl -fsS "https://api.github.com/repos/theupriser/steamify-cachyos-live-iso/releases/tags/$tag" -o /tmp/ghrel.json; then
jq -r '.body // ""' /tmp/ghrel.json | sed '/ISO build\]/d' > release-notes.md
else
echo "Built from \`$(git rev-parse --short HEAD)\` ($tag) with Steamify $STEAMIFY_VERSION." > release-notes.md # fallback when GitHub cannot be reached
fi
{
echo
echo "### Checksum"
echo "SHA-256 of the ISO: \`$(cut -d' ' -f1 "$iso.sha256")\` (label \`STEAMIFY_${STEAMIFY_VERSION//./_}_$b\`)."
} >> release-notes.md
cat release-notes.md
- name: Publish ${{ github.ref_name }}
if: steps.rel.outputs.publish == 'true'
uses: akkuman/gitea-release-action@v1.3.7
with:
token: ${{ github.token }}
tag_name: ${{ github.ref_name }}
name: ${{ steps.rel.outputs.name }}
body_path: release-notes.md
prerelease: ${{ env.ISO_PRERELEASE }}
files: |
out/desktop/*.iso
out/desktop/*.iso.sha256
out/desktop/*.iso.sha1
out/desktop/*.pkgs.txt
# The result goes back to GitHub: the badge at the top of the GitHub release (started as "running" by iso-1) becomes
# succeeded / failed / cancelled and links to this run. Needs a GitHub token as the secret GH_RELEASE_TOKEN here on the
# mirror (fine-grained, this repository only, Contents: read and write); without it the badge stays "running".
report:
name: Report the result to GitHub
needs: [build, release]
if: ${{ always() && github.server_url != 'https://github.com' }}
runs-on: ubuntu-latest
container:
image: docker.io/cachyos/cachyos:latest
steps:
- name: Badge in the GitHub release
env:
GH_TOKEN: ${{ secrets.GH_RELEASE_TOKEN }}
BUILD: ${{ needs.build.result }}
RELEASE: ${{ needs.release.result }}
run: |
[ -n "$GH_TOKEN" ] || { echo "No GH_RELEASE_TOKEN: GitHub's release keeps its 'running' badge."; exit 0; }
pacman -Sy --noconfirm --needed curl python > /dev/null
tag="${{ github.ref_name }}"
if [ "$BUILD" = success ] && [ "$RELEASE" = success ]; then st=succeeded col=brightgreen
elif [ "$BUILD" = cancelled ] || [ "$RELEASE" = cancelled ]; then st=cancelled col=lightgrey
else st=failed col=red; fi
run="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
api="https://api.github.com/repos/theupriser/steamify-cachyos-live-iso/releases"
curl -fsS -H "Authorization: Bearer $GH_TOKEN" "$api/tags/$tag" -o /tmp/rel.json || { echo "::warning::no GitHub release for $tag"; exit 0; }
ST="$st" COL="$col" RUN="$run" RUN_ID="${{ github.run_id }}" python3 - << 'PY'
import json, os, re
d = json.load(open("/tmp/rel.json"))
body = d.get("body") or ""
# This runner reports its internal address (http://server:3000) as the server URL: the public one is in the
# release page link that iso-1 wrote into the notes.
m = re.search(r"\]\((https?://[^)\s]+)/releases/tag/", body)
run = f"{m.group(1)}/actions/runs/{os.environ['RUN_ID']}" if m else os.environ["RUN"]
badge = f"[![ISO build](https://img.shields.io/badge/ISO_build-{os.environ['ST']}-{os.environ['COL']})]({run})"
pat = re.compile(r"\[?!\[ISO build\]\([^)]*\)(\]\([^)]*\))?")
body = pat.sub(lambda m: badge, body, count=1) if pat.search(body) else badge + "\n\n" + body
json.dump({"body": body}, open("/tmp/patch.json", "w"))
open("/tmp/rel.id", "w").write(str(d["id"]))
PY
curl -fsS -X PATCH -H "Authorization: Bearer $GH_TOKEN" -H "Content-Type: application/json" -d @/tmp/patch.json "$api/$(cat /tmp/rel.id)" > /dev/null
echo "GitHub release $tag: ISO build $st"
+6
View File
@@ -21,3 +21,9 @@ archiso/airootfs/etc/environment
archiso/airootfs/etc/pacman.d/cachyos-mirrorlist archiso/airootfs/etc/pacman.d/cachyos-mirrorlist
archiso/airootfs/etc/edition-tag archiso/airootfs/etc/edition-tag
archiso/airootfs/etc/version-tag archiso/airootfs/etc/version-tag
archiso/airootfs/usr/local/share/steamify/
archiso/airootfs/usr/lib/libboost_*
archiso/airootfs/etc/steammachine-iso-build
archiso/airootfs/usr/lib/modules/
archiso/airootfs/etc/modules-load.d/steamify-fremont-poweroff.conf
archiso/airootfs/usr/lib/calamares/modules/packagechooserq/
+148
View File
@@ -0,0 +1,148 @@
# AGENTS.md
Guidance for AI coding agents working on this repository (the Steam Machine
live ISO: CachyOS + the Steamify installer step). See `README.md` and
`TODO.md` for what's built and what's left; this file is operational
knowledge — how to test/build/debug it without rediscovering the same
things every session. `TODO.md` should stay a plain task list; lessons and
gotchas belong here instead.
Full build/test workflow (VM scripts, snapshots, cache setup) lives in the
`steam-machine-iso` and `cachyos-vm-testing` skills in `steamify-cachyos-dev`
— load those first. This file is the overflow: things worth knowing that
don't fit a skill's step-by-step flow.
## Calamares modules
- Verify a module is actually installed (`ls /usr/lib/calamares/modules`)
before writing QML/config for it. A `.conf` file existing is not proof —
CachyOS's own `cachyos-calamares-next` build explicitly skips
`packagechooserq` (`-DSKIP_MODULES=...`).
- Read a module's *actual* logged global-storage writes (Calamares debug
log, `-D6`), not its UI label text, to know what a `mode`/`method` really
does.
- `packagechooser`'s multi-select only works with Shift/Ctrl-click (a plain
click replaces the selection) and its UI has no visible checkboxes — not
usable as the Steamify page. Use `packagechooserq` (custom QML,
qtplugin/legacy) or `netinstall` (checkbox tree) instead.
- `packagechooserq` is built out of tree against the installed Calamares
CMake config (`/usr/lib/cmake/Calamares`) with
`-DWITH_QML=ON -DQT_VERSION_SUFFIX=-qt6 -Dqtname=Qt6 -DWITH_QT6=ON`;
`find_package(KF6CoreAddons)` must come before `find_package(Calamares)`
or the build fails with "KF6::CoreAddons target not found". Its `Config`
class (and `prettyStatus()`'s raw `Install option: <ids>` text) is shared
with the plain `packagechooser` module — override `prettyStatus()` in
`PackageChooserQmlViewStep.cpp` itself (not `Config.cpp`) to change only
this module's Summary display without affecting `packagechooser@desktop`
etc. elsewhere in the installer.
- Both `packagechooserq` (QML swap) and `netinstall` (config swap) are
testable live in a booted ISO VM with no rebuild: edit
`/etc/calamares/modules/*.conf` + `settings.conf`
(`steamify-cachyos-dev/share/sq.sh` / `sn.sh`), relaunch
`pkexec-wrapper calamares -D6` in a **separate Konsole tab** (it blocks
the tab it runs in; typing into that same tab just echoes inertly into
its blocked stdin). A C++ module change needs an actual rebuild
(out-of-tree, in the build VM) and redeploying the `.so` — no shortcut.
## Shell pitfalls
`local a=/x b="$a/y"` expands every value before assigning any, so `b` is
`/y`: `calamares-online.sh` wrote the downloaded `steamify.sh` to
`/steamify.sh` and `items.json` to a nonexistent `/qml/`, silently (stderr
went to `/dev/null`), so the Steamify page always ran on build-time rows and
the Summary had no labels. Declare a variable in its own `local` before
using it, and send a step's errors to the launcher's log, not `/dev/null`.
## ISO permissions
`steamify-prepare.sh` fetches `steamify.sh` with `curl -o`, which doesn't
carry the executable bit, and `archiso/profiledef.sh`'s `file_permissions`
map is the only other place permissions get fixed up on the ISO — any path
not listed there stays at whatever `curl`/`cp` gave it. `calamares-online.sh`
execs `steamify.sh` directly (not via `bash`) to regenerate `Items.qml`/
`items.json` at boot; without +x that failed *silently* (its stderr is
redirected to `/dev/null`), so the page kept working off build-time
fallbacks with no visible error. When something the ISO downloads or copies
at build time needs to run directly (not `bash script.sh`), add it to
`file_permissions` — don't assume the source already had the right mode.
## Driving a live/installer session via QMP
- Don't screenshot-poll for VM readiness. Pass `VM_SERIAL=<file>` to
`run.sh`/`vmisoboot.sh` (logs `console=ttyS0` to a plain text file) and
grep that instead — near-zero tokens vs. repeated screenshot + image
analysis. Only screenshot at real decision points (to show the user, or
to check a layout), not as a polling mechanism.
- The live session's keyboard layout keeps drifting to Dutch (e.g. after
Calamares' Welcome page's keyboard preview, or after opening System
Settings), which silently breaks `qmptype.py`'s US-layout character map
(`/` → `-`, `&`/`>` fail outright) with no error for the common
substitutions. Check with a no-symbols probe (`echo test123`) after any
language/keyboard step. Typing `setxkbmap us` via QMP does fix it.
- `qmptype.py` types `&`, `>`, `<`, `?`, `{`, `}` since 2026-09-28 (before,
`>` raised "no key for" and left half a command in the prompt: send
Ctrl+C before retyping). Its map is US-layout, see above.
- Send QMP keystrokes one command at a time with a beat (~1-2s) in between.
Firing several `qmptype.py` calls back-to-back can interleave with the
guest's own prompt redraw and garble the input (seen as commands merging
or a stray leading character).
- Calamares page Next/Back buttons move vertically as page content
grows/shrinks: re-screenshot and re-locate the button after every page
change, don't reuse a fixed y-coordinate.
- A second `pkexec-wrapper calamares -D6` while an earlier failed
instance's window is still open just prints "Calamares is already
running.": close that window first (Cancel/Afbreken, then confirm the
"really cancel?" dialog).
## Build VM SSH flakiness
The build VM's guest sshd applies OpenSSH's `PerSourcePenalties`: repeated
quick reconnect attempts within its window make `ssh`/`scp` fail instantly
with "Connection closed" (verbose: "Not allowed at this time"), and further
quick retries seem to extend the penalty rather than reset it. Don't
retry-loop through it — that makes it worse. Either back off for a good
while (minutes, not seconds) with zero attempts in between, or just restart
the VM, which clears its in-memory penalty state immediately and is
usually faster than waiting it out.
Large `scp` transfers (e.g. the ~3.2GB built ISO) over this same flaky link
can report "Connection closed" while actually still having copied fully —
check the destination file's size/timestamp before assuming a real
failure, and retry the copy (it's idempotent) rather than the whole build.
## Releases (GitHub tag, ISO built and attached on the Gitea mirror)
Full write-up, with every trap: `steamify-iso-release` in steamify-cachyos-dev. What matters here:
- `.github/workflows/iso-1-github-tag.yml` (GitHub, `workflow_dispatch` only: by hand, or started by a new Steamify
release, whose `bundle.yml` needs the secret `ISO_DISPATCH_TOKEN`) names the release like CachyOS does, by the day (GitHub's UTC
date, no time): `vX.Y.Z-YYMMDD` (real) or `vX.Y.Z-dev-YYMMDD` (dev), file `steamify-cachyos-X.Y.Z-[dev-]YYMMDD-x86_64.iso`, an **annotated** tag and a
release with notes and the direct download link. One ISO per day and kind: a second build the same day deletes
the earlier release and tag (GitHub) and tags again; the mirror takes the new tag object, iso-2 replaces the old mirror release. Retention: at most 10 dev and 10 real
releases are kept (space); the oldest are deleted with `gh release delete --cleanup-tag`, and the mirror drops their tags, releases and ISOs at its next sync. The workflow always runs from `master`; its input `steamify_ref` (a Steamify branch or tag, empty = the newest published release) builds the ISO with that checkout and names it after that branch's `VERSION` (a release branch tests its own version before it is released); its input `kind` (`release`, `dev`, or `auto`: release on master, dev on other branches) decides: `release` is a real release, `dev` a `dev-` pre-release. steamify-cachyos' bundle.yml passes it: a version published from main -> `release`, a version on a release branch -> `dev`.
- `.github/workflows/iso-2-gitea-build.yml` (Gitea, `workflow_dispatch` with the tag as the ref, started by iso-1's last step through the mirror's dispatch API after the mirror has the new tag object; a tag push starts nothing, because a replaced tag of the same name starts no run; skipped on GitHub) runs two jobs for that
tag: build the ISO, then attach it to the mirror's release. It is not tested there (the runner is small): the VM tests
run on GitHub, in steamify-cachyos `vmtest.yml`, on the newest ISO release of the mirror. The tag decides everything: Steamify version (`steamify-prepare.sh`
takes `STEAMIFY_VERSION`), file name (`STEAMIFY_ISO_VERSION` -> `iso_version` in `profiledef.sh`), label
(`STEAMIFY_BUILD_STAMP`, YYMMDD), boot menu and `/etc/steammachine-iso-build`. The build reads no clock; without a
tag everything says `local`.
- `util-iso.sh` runs `sudo mkarchiso`: sudo drops the environment, so `--preserve-env=STEAMIFY_ISO_VERSION,
STEAMIFY_BUILD_STAMP` is required (without it the ISO comes out `-local-`).
- Never create a release on the mirror by hand or from a Gitea-only tag: the pull mirror's next sync deletes tags
GitHub doesn't have, and the release with them. GitHub releases can't hold the ISO (2 GB per file).
- Gitea needs `[repository.release] FILE_MAX_SIZE` above the ISO (413 otherwise) and the runner
`container: privileged: true` (archiso mounts `/proc`); the workflow's `--privileged` option is ignored.
- Caches (`actions/cache`, the runner's cache server): pacman's package cache per job kind (build, test) and the VMs' packages (`VM_CACHE`, `~/vms/pkg-cache`), keys per ISO week. Each job first installs only node and git, restores the caches, then installs the rest.
- CachyOS's own `Desktop ISO` workflow (`build.yml`) is removed here; the VM tests live in steamify-cachyos (GitHub).
- **Build badge in the GitHub release:** iso-1 puts `![ISO build](...running-yellow)` at the top of the release notes;
iso-2's last job `report` (`if: always()`) replaces it with succeeded / failed / cancelled, linked to the Gitea run,
through the GitHub API with the Gitea secret `GH_RELEASE_TOKEN` (fine-grained, this repo, Contents read and write).
Without the secret the badge stays "running". Gitea's own badge can't show these runs (they run on a tag ref).
- **Release notes are generated, not written by hand** (iso-1): the CachyOS base version (first line of CHANGELOG.md), the
Steamify version's own changelog section (steamify-cachyos CHANGELOG.md at the ref or tag it is built from) and this
repository's commits since the previous release of the same kind (newest 40). iso-2 takes the same text from the GitHub
release for the mirror's release (one source), without the badge line, and adds the checksum line. The old hand-written
"## CachyOS with Steamify Live ISO" section in CHANGELOG.md is no longer used.
- **Release title:** `CachyOS <base> with Steamify <version> (<YYYY.MM.DD> UTC)` (dev: `(test build <date> UTC)`), the same on GitHub
and on the mirror; `<base>` is the first line of CHANGELOG.md (e.g. 26.08).
+40
View File
@@ -26,6 +26,46 @@
* Correctly handle VM environments * Correctly handle VM environments
* Correctly quote pacman arguments * Correctly quote pacman arguments
## CachyOS with Steamify Live ISO
CachyOS 26.08 with Steamify built in: install CachyOS and turn the PC into a Steam Machine in one go.
Its version is the Steamify version on it plus the build date and time (UTC, when the release starts on GitHub; a release on `master` is `v<Steamify version>-<date>-<HHMM>`, e.g. `v2.9.3-2026.09.29-2015`). A new Steamify release starts one (a test build for now); by hand: Run workflow on "ISO 1/2 · Tag and release (GitHub)".
Releases: GitHub creates the tag and a release with the notes; the ISO itself (over GitHub's 2 GB limit) is built on the Gitea mirror and attached to its release for that tag (git.upriser.nl).
**Features:**
* **ISO:** named after its release tag (`steamify-cachyos-2.9.3-2026.09.29-2030-x86_64.iso`; built by hand, without a tag: `local`), labelled after the Steamify version on it and the build time (`STEAMIFY_2_9_1_20260929_1432`, was CachyOS's `COS_<year><month>`); CachyOS Hello installs it like CachyOS's own
release (the ISO's own release, its tag without the v, is in `/etc/steammachine-iso-build` and the boot menu)
* **Installer (CachyOS - Steamify Installer):**
* A Steamify page after the packages page: which Steam Machine parts to set up (rows from
`steamify.sh --defaults --list`, the newest Steamify fetched before Calamares starts) and where
the PC starts (gaming mode or the desktop)
* The Summary lists every chosen item and where the PC starts
* The new user's Steam Machine setup runs during the install (`steamify-install`, a Calamares step
before cleanup): the first boot is already a Steam Machine
* Only what Steamify supports is offered: KDE Plasma, and the boot loaders Limine, systemd-boot and
GRUB (not rEFInd)
* **Live session:** keeps CachyOS's own name; the power-off fix is built for the ISO's kernels, and
the login has the Vapor look
**Fixes:**
* **systemd-boot:** the installed system gets its "Linux Boot Manager" EFI boot entry, first in the
boot order. cachyos-installer runs `bootctl install` in a chroot, where bootctl leaves the EFI
variables alone, so the entry was missing: the PC could only boot through the disk's fallback path
(`\EFI\BOOT\BOOTX64.EFI`). Most PCs find it early and boot normally, but firmware that tries the
network first waits minutes on every boot (QEMU/OVMF: PXE, HTTP boot and the EFI shell come first),
and anything else that writes the fallback file (another OS) takes the boot over.
`steamify-install` now registers it with `efibootmgr` from the live system, with the ESP's real
disk and partition (bootctl in the chroot writes an entry without a partition, which the firmware
can't load); the output is in `/var/log/steamify-bootentry.log`
* **Installer:** Steamify's install-time logs (`/var/log/steamify-install.log`,
`steamify-bootentry.log`) are readable after the first boot: `/var/log` is its own btrfs subvolume
(`@log`) that wasn't mounted yet when Steamify ran, so they ended up hidden under it
* **Installer:** Steamify's setup never asks for a password (its sudo rule is read after CachyOS's
`%wheel` rule); Calamares starts with the Boost version it was linked against
* **Build:** finishes cleanly as root and reports no error after a successful build; the Steamify page's
Calamares module is built with the ISO and left out when Calamares' version differs
# 26.07 # 26.07
**Features:** **Features:**
+60
View File
@@ -1,3 +1,63 @@
# Steamify CachyOS ISO for the Steam Machine
A CachyOS desktop ISO for the Valve Steam Machine with
[Steamify](https://github.com/theupriser/steamify-cachyos) built in:
- The live session has the Steam Machine drivers (power-off fix).
- The installer ("CachyOS with Steamify Installer") has a Steamify page where
you pick the options and whether the PC starts in gaming mode or on the
desktop. Steamify is applied during the install.
> **Choose KDE Plasma in the installer.** Steamify only works with KDE Plasma
> (the default). With another desktop (GNOME, Hyprland, Cosmic, ...) Steamify
> is not supported.
## Building the ISO yourself
You need about 20 GB of free space, and a Linux PC with
[podman](https://podman.io) (or Docker: replace `podman` with `docker`). The
build runs in CachyOS's own container, so the PC doesn't have to run CachyOS.
```bash
git clone -b feat/steamify https://github.com/theupriser/steamify-cachyos-live-iso
cd steamify-cachyos-live-iso
# Put Steamify on the ISO: the newest release. To use your own checkout,
# pass its path: ./steamify-prepare.sh ~/steamify-cachyos
./steamify-prepare.sh
mkdir -p ~/iso-cache # downloaded packages, kept for the next build
sudo podman run --rm -t --privileged --network=host \
--pids-limit=-1 --ulimit nofile=65536:65536 \
-v ~/iso-cache:/var/cache/pacman/pkg -v "$PWD":/iso -w /iso \
docker.io/cachyos/cachyos:latest bash -c '
pacman-key --init && pacman-key --populate &&
pacman -Syu --noconfirm --needed archiso mkinitcpio-archiso git squashfs-tools grub sudo &&
./build-live-modules.sh && ./build-calamares-modules.sh &&
./buildiso.sh -p desktop -w'
```
The ISO is written to `out/desktop/steamify-cachyos-<date>.iso`. Write it to
a USB stick (for example with `dd` or Fedora Media Writer) and boot the Steam
Machine from it.
**Why these steps:**
- `build-live-modules.sh` builds the power-off fix for the ISO's kernels.
Without it, the Steam Machine starts up again right after shutting down.
- `build-calamares-modules.sh` builds the installer module that shows the
Steamify page. CachyOS's installer package leaves it out.
- `--pids-limit=-1` and `--ulimit nofile=...` are needed because pacman runs
out of processes or open files in the container otherwise (GPGME errors).
- `--network=host` gives the container DNS.
- `-w` removes the build folder afterwards. Before building again, run
`sudo rm -rf build out`.
To build on CachyOS directly, without a container, install the same packages
with pacman and run the three scripts with `sudo`.
---
The notes below are from CachyOS's original repository.
These are the basic needed files and folders to build CachyOS system. These are the basic needed files and folders to build CachyOS system.
### buildiso ### buildiso
+89
View File
@@ -0,0 +1,89 @@
# To do (Steam Machine edition)
## Open
1. **Steamify installer page** (2026-09-28, working end to end; see
`AGENTS.md` for the module/build/testing details and gotchas behind
this). `packagechooserq` (custom QML) is built with each ISO by
`build-calamares-modules.sh` into `archiso/airootfs/usr/lib/calamares/modules/packagechooserq/`.
`netinstall` (checkbox tree) stays as a manually-swappable fallback
(`steamify-cachyos-dev/share/sn.sh` + `nisteamify.conf`) for when the
module can't load, but isn't wired into `calamares-online.sh`
automatically yet.
- **Still open**:
- A Python job (e.g. `steamifychoice`, before `packages@online`) to
normalize either page's choice (packagechooserq's
`packagechooser_steamifypage` GS key, or netinstall's
packageOperations markers) into one `steamifyChoice` GS key, so
`shellprocess_steamify.conf` doesn't need to know which page ran.
2. **Names**: only the installer says Steamify ("CachyOS with Steamify
Installer", productName set by calamares-online.sh); the live session,
Hello and About this System keep CachyOS's own names (legal clarity). The
ISO file stays `steamify-cachyos-<date>.iso`.
3. **Check the names** after the next build: installer title, Hello and
About this System say CachyOS.
4. ~~Steamify PR for `feat/defaults-options`~~ — done: 2.7.0 (`--options`,
`--boot`, `steamify.sh --boot` standalone) and 2.8.0
(`--defaults --list`, what the Steamify page's rows come from) are both
released. **The ISO builder is up to date with this**: no checkout
argument needed, `steamify-prepare.sh` (no args) and `calamares-online.sh`
at boot both just fetch the newest release, so the ISO always has 2.8.0+
without a rebuild for a Steamify-side change alone. Only pass
`steamify-prepare.sh <checkout>` / `vmisobuild.sh --steamify <path>` when
testing an *unreleased* Steamify change.
5. **Build in the test VM** instead of on the Steam Machine
(steam-machine-iso skill), and install the result unattended with
`scripts/vminstall.sh --iso` (vm-install skill; the Calamares Steamify
step then needs `steamify-install` run from the live script).
6. **Test on the real Steam Machine** from a USB stick (gamescope, LEDs, CEC,
power-off) before calling the ISO usable.
## Done
- **Full ISO install with the Steamify page** (2026-09-28, ISO VM,
`--fremont`, Start in: desktop): Summary listed the choices, the
install step ran Steamify 2.8.0 with every item OK, `--boot desktop`
applied, `exit: 0`; the installed system boots into the desktop.
- **Steamify Summary** (2026-09-28, ISO VM): an unordered list of the chosen
items' names plus "Starts in gaming mode/the desktop"
(`patches/packagechooserq-steamify-summary.patch`), with `items.json`
written by the launcher (its `$sdir` bug fixed, `0cc7367`).
- **packagechooserq built with the ISO** (2026-09-28):
`build-calamares-modules.sh` builds it in the build container/VM from
CachyOS's Calamares source against the current `cachyos-calamares-next`
(no prebuilt `.so` in git), with `built-for`; `calamares-online.sh` leaves
the Steamify page out when the Calamares it runs differs (the step then
installs everything on) instead of risking "Initialization Failed".
- **Steamify 2.6.0** released (`--defaults`, install-time mode).
- **Live session** (VM, `--fremont`): Vapor look and layout (Steam Deck
wallpaper) at the live login; the power-off module is built for both ISO
kernels and loaded at boot, in the VM it returns "No such device" (no
AMDI0030 GPIO controller), as designed. Whether it keeps the real Steam
Machine off is part of the hardware test.
- **Regression test 2.6.0 on an existing desktop install** (test VM from
ssh-ready, `--fremont`): full first run, re-apply, notifications/CEC/theme
off and on again. No errors; user units enabled and running, theme applied
and restored live, no first-login autostart with a session.
- **ISO name**: `steamify-cachyos-<date>-x86_64.iso` (`iso_name` in
`archiso/profiledef.sh`); the volume label stays `COS_<yyyymm>`.
- **VM install from the ISO (2026-09-27, `f42859a`):** a VM install from the ISO (Hello's
Install, no manual fixes) ran Steamify's step: every component OK,
`exit: 0`, SDDM autologin into gamescope. First desktop login (session switched to plasma in the VM) passed too:
Vapor layout, the app opened with everything on. The first-login script
runs the newest *release*, so it only works once 2.6.0 is released (in the
test it was pointed at the bundle).
The VM's text
console doesn't show (virgl): read the installed disk with
`qemu-nbd -r` + `mount -o ro,rescue=nologreplay,subvol=@` (logs in `@log`).
## Later
- **Branding**: only the installer carries Steamify's name; check with the
CachyOS team whether that and the ISO file name are fine with them.
- **Release**: a CI job that builds the ISO (privileged container, flags from
the steam-machine-iso skill) and hosts it (>2 GB, not a GitHub release asset).
- **Drop the Boost 1.91 workaround** in `steamify-prepare.sh` once
`cachyos-calamares-next` is rebuilt against the repos' Boost.
Build and test details: the `steam-machine-iso` skill in steamify-cachyos-dev.
@@ -0,0 +1,10 @@
# Steamify: its page (the app's switches, /usr/local/share/steamify-installer/
# SteamifyPage.qml). Legacy method: the page's choice (the ids that are on,
# plus "boot" for Boot into Desktop) goes to global storage as
# packagechooser_steamifypage, for shellprocess_steamify.conf.
---
method: legacy
qmlFilename: /usr/local/share/steamify-installer/SteamifyPage.qml
labels:
step: "Steamify"
packageChoice: gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff
@@ -0,0 +1,10 @@
# Steamify: the Steam Machine setup for the new user, before the first boot.
---
dontChroot: true
timeout: 3600
script:
- "-/usr/local/bin/steamify-install ${ROOT} ${USER} \"${gs[packagechooser_steamifypage]}\""
i18n:
name: "Setting up the Steam Machine (Steamify)"
@@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Steamify live theme
Exec=/usr/local/bin/steamify-live-theme
OnlyShowIn=KDE;
X-KDE-autostart-phase=2
NoDisplay=true
@@ -33,6 +33,24 @@ main() {
echo "USING ISO VERSION: ${ISO_VERSION}" echo "USING ISO VERSION: ${ISO_VERSION}"
sudo pacman -Sy --noconfirm cachyos-calamares-next sudo pacman -Sy --noconfirm cachyos-calamares-next
# The installer's window title ("%1 Installer") and welcome text use the
# branding's productName; the reinstall above restored CachyOS's.
sudo sed -i 's/^\( productName: *\)CachyOS$/\1CachyOS with Steamify/' \
/usr/share/calamares/branding/cachyos/branding.desc
# Only what Steamify supports, like CachyOS's handheld edition does:
# KDE Plasma as the desktop, and the boot loaders Steamify knows how to
# update (Limine, systemd-boot, GRUB; not rEFInd). Drops every other
# ` - id:` item block; `default:` (KDE-Desktop, limine) stays valid.
local keep_items='/^ - id: / { id = $3; gsub(/"/, "", id); skip = id == "" || index(" " keep " ", " " id " ") == 0 }
/^[^ ]/ { skip = 0 }
!skip'
local chooser keep
for chooser in "desktop:KDE-Desktop" "bootloader:limine systemd-boot grub"; do
keep="${chooser#*:}"
chooser="/etc/calamares/modules/packagechooser_${chooser%%:*}.conf"
awk -v keep="$keep" "$keep_items" "$chooser" | sudo tee "$chooser.new" > /dev/null &&
sudo mv "$chooser.new" "$chooser"
done
# Get Hardware Informations # Get Hardware Informations
inxi -F > "$log" inxi -F > "$log"
@@ -44,7 +62,64 @@ main() {
########## System: $SYSTEM ########## System: $SYSTEM
EOF EOF
# Steamify: try the newest release before Calamares starts, so the page
# and the install step both show/use it; keep the ISO's copy (from
# steamify-prepare.sh) on any failure (no network, GitHub unreachable).
# Separate statements: `local` expands all its values before assigning
# any, so "$sdir" would still be empty in the same one.
local sdir=/usr/local/share/steamify
local sbin="$sdir/steamify.sh" sqml="$sdir/qml"
local tmp; tmp="$(mktemp)"
if curl -fsSL --max-time 20 https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh -o "$tmp" &&
grep -q -- '--defaults' "$tmp"; then
sudo install -Dm755 "$tmp" "$sbin"
fi
rm -f "$tmp"
tmp="$(mktemp -d)"
if curl -fsSL --max-time 20 https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.tar.gz |
tar -xz -C "$tmp" --strip-components=2 ui/qml/Theme.qml ui/qml/Texts.qml 2>/dev/null; then
sudo cp "$tmp/Theme.qml" "$tmp/Texts.qml" "$sqml/"
fi
rm -rf "$tmp"
# The page's rows, from whichever steamify.sh ended up on the ISO
# (regenerated even when the download failed: the fallback Items.qml
# doesn't know this machine, e.g. no VRAM region or no Steam account yet).
# JSON is valid JS array-literal syntax, so the output goes straight into
# the singleton as-is (--list guarantees one line of only its own JSON
# punctuation and text fields Steamify itself writes: nothing here can
# break out of the property).
tmp="$(mktemp)"
if "$sbin" --defaults --list > "$tmp" 2>> "$log" && [[ -s "$tmp" ]]; then
{ printf 'pragma Singleton\nimport QtQuick\n\nQtObject {\n readonly property var rows: '
cat "$tmp"
printf '\n}\n'
} | sudo tee "$sqml/Items.qml" > /dev/null
# Same rows, plain JSON: packagechooserq's prettyStatus() override
# reads this to show the Summary step's choice as labels, not ids.
sudo cp "$tmp" "$sqml/items.json"
fi
rm -f "$tmp"
sudo cp "/usr/share/calamares/settings_${mode}.conf" /etc/calamares/settings.conf sudo cp "/usr/share/calamares/settings_${mode}.conf" /etc/calamares/settings.conf
# Steamify: its page (packagechooserq@steamifypage, right after the
# packages page) and its step (modules/shellprocess_steamify.conf) before
# the installer cleans up after itself.
# The page's module is built for one cachyos-calamares-next
# (build-calamares-modules.sh); the pacman -Sy above may have brought
# another, which it might not load in ("Initialization Failed" blocks the
# installer). Then leave the page out: the step installs the page's
# default, everything on.
local built installed
built="$(cat /usr/lib/calamares/modules/packagechooserq/built-for 2>/dev/null)"
installed="$(pacman -Q cachyos-calamares-next | cut -d' ' -f2)"
local page=(-e 's/^ - netinstall$/&\n - packagechooserq@steamifypage/')
if [[ -z "$built" || "$built" != "$installed" ]]; then
echo "Steamify page left out: built for cachyos-calamares-next ${built:-?}, running $installed." >> "$log"
page=()
fi
sudo sed -i -e 's/^- id: cleanup_calamares$/- id: steamifypage\n module: packagechooserq\n config: packagechooserq_steamifypage.conf\n\n- id: steamify\n module: shellprocess\n config: shellprocess_steamify.conf\n\n&/' \
"${page[@]}" \
-e 's/^ - shellprocess@cleanup_calamares$/ - shellprocess@steamify\n&/' /etc/calamares/settings.conf
exec pkexec-wrapper calamares -D6 >> $log exec pkexec-wrapper calamares -D6 >> $log
} }
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# steamify-install <target root> <user> <choice>: runs Steamify's setup for
# the new user inside the installed system (Calamares, after the users and
# services steps), as chosen on the installer's Steamify page: <choice> is the
# ids that are on, comma-separated, plus "boot" for Boot into Desktop, or
# "none" (packagechooser_steamifypage). Steamify refuses root and asks sudo
# per command, so the
# user gets a NOPASSWD rule for this run only, named to be read last: sudo
# takes the last matching rule, and a later %wheel rule would ask again. Never fails the install: a
# problem leaves the log and Steamify can be run again from the desktop.
root="$1" user="$2" choice="${3:-}"
# Not filled in by Calamares (empty, or the literal ${gs[...]}): the page's
# default, everything on.
[[ -z "$choice" || "$choice" == *'${'* ]] && choice=gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff
boot=gamescope options=""
for id in ${choice//,/ }; do
case "$id" in
boot) boot=desktop ;;
none) ;;
*) options+="${options:+,}$id" ;;
esac
done
# /var/log is its own btrfs subvolume (@log) that the installer doesn't mount at $root/var/log: what is
# written there now (these logs, Steamify's) would end up hidden under the mount after boot. Mount it
# from the new system's fstab and leave it mounted: the caller reads the log afterwards, and unmounts
# all of $root at the end anyway (Calamares' umount step, the test's poweroff).
if [[ -n "$root" && -d "$root/var/log" ]] && ! mountpoint -q "$root/var/log"; then
read -r dev _ _ opts _ < <(awk '$2 == "/var/log" && $1 !~ /^#/' "$root/etc/fstab" 2>/dev/null)
[[ -n "${dev:-}" ]] && mount -o "$opts" "$dev" "$root/var/log" 2>/dev/null
fi
# systemd-boot: cachyos-installer runs `bootctl install` in a chroot, and bootctl leaves the EFI
# variables alone there: no "Linux Boot Manager" boot entry, so the firmware tries the network and
# its other entries first and finds the disk's fallback path last (or never). bootctl can't be made
# to do it from the chroot either (it writes the entry without the partition: HD(0,GPT,0000...)).
# So register it from the live system, with the ESP's real disk and partition; efibootmgr puts it first.
if [[ -n "$root" && -f "$root/boot/EFI/systemd/systemd-bootx64.efi" && -d /sys/firmware/efi/efivars ]]; then
{
esp="$(findmnt -no SOURCE "$root/boot")"
disk="/dev/$(lsblk -no PKNAME "$esp" | head -n 1)"
part="$(cat "/sys/class/block/${esp##*/}/partition")"
echo "ESP: $esp (disk $disk, partition $part)"
for n in $(efibootmgr | sed -n 's/^Boot\([0-9A-Fa-f]\{4\}\)\*\{0,1\} \(Fallback \)\{0,1\}Linux Boot Manager.*/\1/p'); do
efibootmgr -q -b "$n" -B
done
efibootmgr --create --disk "$disk" --part "$part" --label "Linux Boot Manager" --loader '\EFI\systemd\systemd-bootx64.efi'
} > "$root/var/log/steamify-bootentry.log" 2>&1
fi
log="$root/var/log/steamify-install.log"
rule="$root/etc/sudoers.d/zz-steamify-install"
[[ -d "$root" && -n "$user" ]] || exit 0
# Everything switched off on the page: nothing to set up.
[[ -n "$options" ]] || { echo "Nothing chosen on the Steamify page." > "$log"; exit 0; }
install -Dm755 /usr/local/share/steamify/steamify.sh "$root/var/tmp/steamify.sh"
printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$user" > "$rule"
chmod 440 "$rule"
arch-chroot "$root" runuser -u "$user" -- env -i HOME="/home/$user" USER="$user" LOGNAME="$user" \
PATH=/usr/local/bin:/usr/bin TERM=dumb LANG=C.UTF-8 \
bash -c 'cd ~ && bash /var/tmp/steamify.sh --defaults --options "$0" --boot "$1" < /dev/null' "$options" "$boot" > "$log" 2>&1
echo "exit: $?" >> "$log"
rm -f "$rule" "$root/var/tmp/steamify.sh"
exit 0
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# The live session in the Vapor look, with its SteamOS desktop layout.
# CachyOS's own layout comes from /etc/skel, which the live user gets before
# this runs; lookandfeeltool needs plasmashell up to replace it.
for _ in $(seq 30); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 1; done
sleep 2
lookandfeeltool -a com.valve.vapor.desktop --resetLayout
@@ -0,0 +1,20 @@
// The Steamify page's rows when `steamify.sh --defaults --list` can't be run
// (see calamares-online.sh, which writes the real list over this copy in
// /usr/local/share/steamify/qml at the start of the installer).
pragma Singleton
import QtQuick
QtObject {
readonly property var rows: [
{ id: "gaming", label: "SteamOS conversion", hint: "boot into gaming mode, Steam on the desktop", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "boot", label: "Boot into", hint: "", kind: "choice", parent: "gaming", on: false, selectable: true },
{ id: "theme", label: "Install SteamOS theme", hint: "Vapor look (cachyos-vapor)", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "glyphs", label: "Install Steam Deck/Machine icons", hint: "Deck button icons in gaming mode", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "single", label: "Single user mode", hint: "no password, lock screen or log out (SDDM)", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "launcher", label: "Steamify shortcut", hint: "the app on the desktop, Steamify Terminal in the launcher", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "notify", label: "Update notifications", hint: "a notification when there's a new Steamify", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "cec", label: "HDMI-CEC", hint: "use Steam with the TV remote, TV on/off with the PC", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "machine", label: "Steam Machine support", hint: "LED bar driver, hardware settings in Steam", kind: "toggle", parent: "", on: true, selectable: true },
{ id: "poweroff", label: "Power-off fix", hint: "the Steam Machine stays off after shutting down", kind: "toggle", parent: "machine", on: true, selectable: true }
]
}
@@ -0,0 +1,200 @@
// Steamify's page in Calamares (packagechooserq@steamifypage, method legacy),
// styled like the rest of CachyOS's installer: its palette, standard
// controls, CachyOS's turquoise for the selected row. Left the options,
// right what the selected (or hovered) one does.
// The rows come from Steamify itself: calamares-online.sh writes
// ../steamify/qml/Items.qml from `steamify.sh --defaults --list` (the newest
// Steamify, else the ISO's), so new options show up without a new ISO; the
// explanations are the app's (Texts.qml, same folder).
// The choice goes to Calamares' global storage as packagechooser_steamifypage:
// the ids that are on, comma-separated, plus "boot" for Boot into Desktop
// ("none" when nothing is on); steamify-install passes it to
// `steamify.sh --defaults --options ... --boot ...`.
import io.calamares.core 1.0
import io.calamares.ui 1.0
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../steamify/qml"
Item {
id: page
width: parent.width
height: parent.height
SystemPalette { id: pal; colorGroup: SystemPalette.Active }
readonly property color accent: "#00CED1"
Rectangle { anchors.fill: parent; color: pal.window }
// Only what can be set up here (Steamify leaves out the rest).
readonly property var rows: Items.rows.filter(function (r) { return r.selectable; })
property var want: ({})
property string boot: "gamescope"
property int sel: 0
function label(r) { return (Texts.items[r.id] || {}).label || r.label; }
function hint(r) { return (Texts.items[r.id] || {}).hint || r.hint; }
function shown(r) { return !r.parent || !!want[r.parent]; }
// Steamify's own rules: a sub-option needs its parent (turning a parent
// off turns its sub-options off); single user mode needs the conversion.
function toggle(id) {
var w = Object.assign({}, want);
w[id] = !w[id];
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
if (w[id] && r.id === id && r.parent) w[r.parent] = true;
if (!w[id] && r.parent === id && r.kind === "toggle") w[r.id] = false;
if (w[id] && r.parent === id && r.kind === "toggle") w[r.id] = true; // opt-out sub-options
}
if (id === "single" && w.single) w.gaming = true;
if (id === "gaming" && !w.gaming) { w.single = false; boot = "gamescope"; }
want = w;
}
function choice() {
var on = [];
for (var i = 0; i < rows.length; i++)
if (rows[i].kind === "toggle" && want[rows[i].id]) on.push(rows[i].id);
if (want.gaming && boot === "desktop") on.push("boot");
// ", " (not ","): Calamares' packagechooser Summary step shows this
// value raw ("Install option: <value>"); steamify-install still
// parses it fine (${choice//,/ } + bash word-splitting collapses
// the extra space).
return on.length ? on.join(", ") : "none";
}
onWantChanged: config.packageChoice = choice()
onBootChanged: config.packageChoice = choice()
Component.onCompleted: {
// Preselected like Steamify's first run.
var w = {};
for (var i = 0; i < rows.length; i++) if (rows[i].kind === "toggle") w[rows[i].id] = rows[i].on;
want = w;
config.packageChoice = choice();
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
Label {
Layout.fillWidth: true
text: "Steamify sets up the SteamOS experience while CachyOS installs. Everything can be changed later with the Steamify app."
wrapMode: Text.WordWrap
color: pal.windowText
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 16
ListView {
id: list
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: 55
clip: true
spacing: 4
boundsBehavior: Flickable.StopAtBounds
model: page.rows
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
delegate: Rectangle {
id: row
required property var modelData
required property int index
readonly property var it: modelData
readonly property bool vis: page.shown(it)
readonly property bool selected: vis && index === page.sel
width: list.width - 25
height: vis ? 52 : 0
visible: vis
radius: 4
color: pal.base
border.width: selected ? 2 : 1
border.color: selected ? page.accent : Qt.rgba(pal.windowText.r, pal.windowText.g, pal.windowText.b, 0.15)
MouseArea {
anchors.fill: parent; hoverEnabled: true
// Hovering explains a row too, like selecting it.
onEntered: page.sel = row.index
onClicked: { page.sel = row.index; if (row.it.kind === "toggle") page.toggle(row.it.id); }
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: row.it.parent ? 12 : 12
anchors.rightMargin: 12
spacing: 8
Label {
visible: !!row.it.parent
text: "└"
color: pal.text
opacity: 0.6
font.pointSize: 11
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Label { Layout.fillWidth: true; text: page.label(row.it); font.bold: true; elide: Text.ElideRight; color: pal.text }
Label { Layout.fillWidth: true; text: page.hint(row.it); elide: Text.ElideRight; color: pal.text; opacity: 0.7; font.pointSize: 8.5 }
}
Switch {
visible: row.it.kind === "toggle"
checked: !!page.want[row.it.id]
onToggled: { page.sel = row.index; page.toggle(row.it.id); }
}
RowLayout {
visible: row.it.kind === "choice"
spacing: 4
RadioButton { text: "Gaming mode"; checked: page.boot === "gamescope"
onClicked: { page.sel = row.index; page.boot = "gamescope"; } }
RadioButton { text: "Desktop"; checked: page.boot === "desktop"
onClicked: { page.sel = row.index; page.boot = "desktop"; } }
}
}
}
}
// What the selected (or hovered) option does.
Rectangle {
id: detail
readonly property var it: page.rows[page.sel] || ({})
readonly property var tx: Texts.items[it.id] || ({})
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: 45
radius: 4
color: pal.base
border.width: 1
border.color: Qt.rgba(pal.windowText.r, pal.windowText.g, pal.windowText.b, 0.15)
onItChanged: flick.contentY = 0
Flickable {
id: flick
anchors.fill: parent
anchors.margins: 16
clip: true
contentHeight: col.implicitHeight
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ColumnLayout {
id: col
width: flick.width - 12
spacing: 10
Label { Layout.fillWidth: true; text: detail.it.id ? page.label(detail.it) : ""; font.bold: true; font.pointSize: 13
wrapMode: Text.WordWrap; color: pal.text }
Label { Layout.fillWidth: true; text: detail.tx.body || page.hint(detail.it) || ""; wrapMode: Text.WordWrap; color: pal.text }
Label { visible: (detail.tx.changes || []).length > 0; text: "What it changes"; font.bold: true; color: pal.text; topPadding: 4 }
Repeater {
model: detail.tx.changes || []
Label { required property string modelData; Layout.fillWidth: true; text: "• " + modelData
wrapMode: Text.WordWrap; color: pal.text }
}
}
}
}
}
}
}
+1
View File
@@ -90,6 +90,7 @@ linux-cachyos
linux-cachyos-nvidia-open linux-cachyos-nvidia-open
linux-cachyos-zfs linux-cachyos-zfs
linux-firmware linux-firmware
cachyos-vapor
linux-firmware-marvell linux-firmware-marvell
lsb-release lsb-release
lsof lsof
+21 -4
View File
@@ -1,11 +1,20 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC2034 # shellcheck disable=SC2034
iso_name="cachyos" iso_name="steamify-cachyos"
iso_label="COS_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)" # The label follows the release tag, which GitHub names (iso-1-github-tag.yml): the Steamify version on the ISO
# (steamify-prepare.sh put it there) plus the tag's day, e.g. STEAMIFY_2_9_3_260929 (ISO 9660:
# at most 32 characters, A-Z 0-9 _). STEAMIFY_BUILD_STAMP (YYMMDD, UTC) comes from the tag; the build
# reads no clock of its own. Built by hand, without a tag: LOCAL.
_steamify="$(sed -n 's/^VERSION=//p' "${BASH_SOURCE[0]%/*}/airootfs/usr/local/share/steamify/steamify.sh" 2>/dev/null | head -n 1)"
_stamp="${STEAMIFY_BUILD_STAMP:-LOCAL}"
iso_label="STEAMIFY_${_steamify:+${_steamify//./_}_}${_stamp}"
iso_label="${iso_label:0:32}"
iso_publisher="CachyOS <https://cachyos.org>" iso_publisher="CachyOS <https://cachyos.org>"
iso_application="CachyOS Live/Rescue DVD" iso_application="Steamify CachyOS Live (based on CachyOS)"
iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" # The file name (<iso_name>-<iso_version>-x86_64.iso) is the release tag without its v (STEAMIFY_ISO_VERSION
# from the tag: steamify-cachyos-2.9.6-dev-260930-x86_64.iso). Built by hand, without a tag: local.
iso_version="${STEAMIFY_ISO_VERSION:-local}"
install_dir="arch" install_dir="arch"
buildmodes=('iso') buildmodes=('iso')
## GRUB ## GRUB
@@ -27,6 +36,8 @@ file_permissions=(
["/root/.gnupg"]="0:0:700" ["/root/.gnupg"]="0:0:700"
["/usr/local/bin/choose-mirror"]="0:0:755" ["/usr/local/bin/choose-mirror"]="0:0:755"
["/usr/local/bin/dmcheck"]="0:0:755" ["/usr/local/bin/dmcheck"]="0:0:755"
["/usr/local/bin/steamify-install"]="0:0:755"
["/usr/local/bin/steamify-live-theme"]="0:0:755"
["/usr/local/bin/calamares-online.sh"]="0:0:755" ["/usr/local/bin/calamares-online.sh"]="0:0:755"
["/usr/local/bin/remove-nvidia"]="0:0:755" ["/usr/local/bin/remove-nvidia"]="0:0:755"
["/usr/local/bin/removeun"]="0:0:755" ["/usr/local/bin/removeun"]="0:0:755"
@@ -34,4 +45,10 @@ file_permissions=(
["/usr/local/bin/prepare-live-desktop.sh"]="0:0:755" ["/usr/local/bin/prepare-live-desktop.sh"]="0:0:755"
["/usr/local/bin/nvidia-module-loader"]="0:0:755" ["/usr/local/bin/nvidia-module-loader"]="0:0:755"
["/usr/local/bin/pkexec-wrapper"]="0:0:755" ["/usr/local/bin/pkexec-wrapper"]="0:0:755"
# steamify-prepare.sh writes this via curl -o, which doesn't carry the
# executable bit; calamares-online.sh runs it directly ("$sbin" --defaults
# --list) to regenerate Items.qml/items.json at boot, so without this it
# silently fails (stderr redirected) and the Steamify page/Summary step
# fall back to build-time data.
["/usr/local/share/steamify/steamify.sh"]="0:0:755"
) )
+51
View File
@@ -0,0 +1,51 @@
#!/bin/bash
# build-calamares-modules.sh: builds Calamares' packagechooserq module (the
# QML page the Steamify page runs in) for the ISO. CachyOS's
# cachyos-calamares-next leaves it out of its own build, so it's built here,
# out of tree, from CachyOS's Calamares source against the installed
# cachyos-calamares-next, and installed into airootfs. Run as root in the
# build container, before buildiso.sh: the package comes from the same repos
# at the same time as the ISO's, so the module matches the ISO's Calamares.
# Records the version it was built for next to it (calamares-online.sh
# compares that with the Calamares it ends up with).
set -euo pipefail
cd "$(dirname "$0")"
dest=archiso/airootfs/usr/lib/calamares/modules/packagechooserq
src_url=https://github.com/CachyOS/cachyos-calamares/archive/refs/heads/cachyos.tar.gz
pacman -S --needed --noconfirm cachyos-calamares-next cmake extra-cmake-modules \
qt6-declarative qt6-tools qt6-svg kcoreaddons yaml-cpp base-devel patch >/dev/null
work="$(mktemp -d)"
mkdir -p "$work/src" "$work/cmake"
# The module and the packagechooser sources it shares (Config, PackageModel),
# plus the CMake helper its CMakeLists includes, which Calamares doesn't install.
curl -fsSL "$src_url" -o "$work/calamares.tar.gz"
tar -xzf "$work/calamares.tar.gz" --strip-components=3 -C "$work/src" \
cachyos-calamares-cachyos/src/modules/packagechooserq cachyos-calamares-cachyos/src/modules/packagechooser
tar -xzf "$work/calamares.tar.gz" --strip-components=2 -C "$work/cmake" \
cachyos-calamares-cachyos/CMakeModules/AppStreamHelper.cmake
# The Steamify page's Summary line shows labels instead of ids.
patch -s -d "$work/src" -p1 < patches/packagechooserq-steamify-summary.patch
# KF6CoreAddons before Calamares: Calamares' CMake config needs its target.
cat > "$work/src/CMakeLists.txt" << EOF
cmake_minimum_required(VERSION 3.16)
project(steamify_packagechooserq LANGUAGES CXX)
set(WITH_QML ON)
set(WITH_QT6 ON)
set(qtname Qt6)
set(QT_VERSION_SUFFIX -qt6)
find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH \${ECM_MODULE_PATH} /usr/lib/cmake/Calamares $work/cmake \${CMAKE_MODULE_PATH})
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Qml Quick)
find_package(KF6CoreAddons REQUIRED)
find_package(Calamares REQUIRED)
add_subdirectory(packagechooserq)
EOF
cmake -S "$work/src" -B "$work/build" -DCMAKE_BUILD_TYPE=Release > "$work/cmake.log" 2>&1 ||
{ tail -30 "$work/cmake.log"; exit 1; }
make -s -C "$work/build" -j"$(nproc)"
install -Dm755 "$work/build/packagechooserq/libcalamares_viewmodule_packagechooserq.so" "$dest/libcalamares_viewmodule_packagechooserq.so"
install -Dm644 "$work/build/packagechooserq/module.desc" "$dest/module.desc"
pacman -Q cachyos-calamares-next | cut -d' ' -f2 > "$dest/built-for"
rm -rf "$work"
echo "packagechooserq built for cachyos-calamares-next $(cat "$dest/built-for")."
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# build-live-modules.sh: builds Steamify's power-off fix for the live ISO's
# kernels, so the Steam Machine stays off after shutting down from the live
# session (recent kernels keep a firmware wake bit set; see Steamify's
# patches/steamify-fremont-poweroff.c). Run as root in the build container,
# after steamify-prepare.sh and before buildiso.sh: the headers come from the
# same repos at the same time as the ISO's kernels, so the versions match.
# The module checks for Fremont itself and does nothing on other hardware.
set -euo pipefail
cd "$(dirname "$0")"
src=archiso/airootfs/usr/local/share/steamify/steamify-fremont-poweroff.c
[[ -f "$src" ]] || { echo "No $src: run steamify-prepare.sh first." >&2; exit 1; }
pacman -S --needed --noconfirm base-devel clang llvm lld linux-cachyos-headers linux-cachyos-lts-headers >/dev/null
for build in /usr/lib/modules/*/build; do
kver="$(basename "$(dirname "$build")")"
# A fresh folder per kernel: `make clean modules` in one call makes
# kbuild re-run itself endlessly for an external module.
work="$(mktemp -d)"
cp "$src" "$work/"
echo 'obj-m += steamify-fremont-poweroff.o' > "$work/Makefile"
# CachyOS's kernels differ in compiler (linux-cachyos is clang-built):
# build with the one the kernel was built with.
llvm=()
grep -q '^CONFIG_CC_IS_CLANG=y' "$build/.config" && llvm=(LLVM=1)
make -s -C "$build" M="$work" "${llvm[@]}" modules
install -Dm644 "$work/steamify-fremont-poweroff.ko" \
"archiso/airootfs/usr/lib/modules/$kver/extra/steamify-fremont-poweroff.ko"
rm -rf "$work"
echo "Power-off fix built for $kver."
done
install -Dm644 /dev/stdin archiso/airootfs/etc/modules-load.d/steamify-fremont-poweroff.conf <<< 'steamify-fremont-poweroff'
+5 -1
View File
@@ -76,6 +76,10 @@ for sig in TERM HUP QUIT; do
done done
trap 'trap_exit INT "$(gettext "Aborted by user! Exiting...")"' INT trap 'trap_exit INT "$(gettext "Aborted by user! Exiting...")"' INT
trap 'trap_exit USR1 "$(gettext "An unknown error has occurred. Exiting...")"' ERR trap 'trap_exit USR1 "$(gettext "An unknown error has occurred. Exiting...")"' ERR
trap 'trap_exit EXIT "$(gettext "An unknown error has occurred. Exiting...")"' EXIT # Steamify: unconditional, so it printed "An unknown error has occurred"
# (and tried to self-kill with the bogus signal name EXIT) after every
# build, success included. Only report it, and only exit through
# trap_exit's self-kill, on an actual nonzero exit.
trap 'ec=$?; if [[ $ec -ne 0 ]]; then trap_exit EXIT "$(gettext "An unknown error has occurred. Exiting...")"; else umount_fs; fi' EXIT
run_build "${build_list_iso}" run_build "${build_list_iso}"
@@ -0,0 +1,81 @@
diff -ruN a/packagechooserq/PackageChooserQmlViewStep.cpp b/packagechooserq/PackageChooserQmlViewStep.cpp
--- a/packagechooserq/PackageChooserQmlViewStep.cpp 2026-09-28 16:56:21
+++ b/packagechooserq/PackageChooserQmlViewStep.cpp 2026-09-28 16:57:29
@@ -17,6 +17,12 @@
#include "utils/System.h"
#include "utils/Variant.h"
+#include <QFile>
+#include <QHash>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserQmlViewStepFactory, registerPlugin< PackageChooserQmlViewStep >(); )
PackageChooserQmlViewStep::PackageChooserQmlViewStep( QObject* parent )
@@ -35,9 +41,61 @@
QString
PackageChooserQmlViewStep::prettyStatus() const
{
- //QString option = m_pkgc;
- //return tr( "Install option: %1" ).arg( option );
- return m_config->prettyStatus();
+ // Steamify page (steamify-cachyos-live-iso): its choice is Steamify's
+ // item ids ("boot" = start in the desktop, "none" = nothing on); show
+ // their labels, from the rows calamares-online.sh writes next to the
+ // page, instead of the raw ids. Any other use keeps the stock text.
+ const QString choice = m_config->packageChoice();
+ QFile rows( QStringLiteral( "/usr/local/share/steamify/qml/items.json" ) );
+ if ( moduleInstanceKey().id() != QStringLiteral( "steamifypage" ) || choice.isEmpty()
+ || !rows.open( QIODevice::ReadOnly ) )
+ {
+ return m_config->prettyStatus();
+ }
+ QHash< QString, QString > labels;
+ const auto items = QJsonDocument::fromJson( rows.readAll() ).array();
+ for ( const auto& item : items )
+ {
+ const auto row = item.toObject();
+ labels.insert( row.value( QStringLiteral( "id" ) ).toString(),
+ row.value( QStringLiteral( "label" ) ).toString() );
+ }
+ if ( labels.isEmpty() )
+ {
+ return m_config->prettyStatus();
+ }
+ QStringList on;
+ bool desktop = false, gaming = false;
+ for ( const auto& part : choice.split( QLatin1Char( ',' ) ) )
+ {
+ const QString id = part.trimmed();
+ if ( id.isEmpty() || id == QStringLiteral( "none" ) )
+ {
+ continue;
+ }
+ if ( id == QStringLiteral( "boot" ) )
+ {
+ desktop = true;
+ continue;
+ }
+ gaming = gaming || id == QStringLiteral( "gaming" );
+ on << labels.value( id, id ).toHtmlEscaped();
+ }
+ if ( gaming )
+ {
+ on << ( desktop ? tr( "Starts in the desktop" ) : tr( "Starts in gaming mode" ) );
+ }
+ if ( on.isEmpty() )
+ {
+ return tr( "Steamify: <strong>nothing</strong>" );
+ }
+ QString status = tr( "Steamify:" ) + QStringLiteral( "<ul>" );
+ for ( const auto& item : on )
+ {
+ status += QStringLiteral( "<li>" ) + item + QStringLiteral( "</li>" );
+ }
+ status += QStringLiteral( "</ul>" );
+ return status;
}
bool
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# steamify-customize.sh <live root>: changes to the live system after its
# packages are installed (mkarchiso runs it, see modify_mkarchiso in
# util-iso.sh); files in airootfs are copied before the packages, which
# overwrite them.
set -euo pipefail
root="$1"
# Nothing yet: the live session keeps CachyOS's own name (only the installer
# says "CachyOS with Steamify", see calamares-online.sh).
exit 0
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# steamify-prepare.sh [steamify checkout]: puts the Steamify bundle on the
# ISO (archiso/airootfs/usr/local/share/steamify/steamify.sh), used by the
# installer's Steamify step, and what its Steamify page shows (qml/). Built from a checkout when given, else the
# newest release, or exactly release $STEAMIFY_VERSION (e.g. 2.9.2: the ISO release workflow, whose tag names
# the version). Run before buildiso.sh.
set -euo pipefail
rel="https://github.com/theupriser/steamify-cachyos/releases/${STEAMIFY_VERSION:+download/v$STEAMIFY_VERSION}"
[[ -n "${STEAMIFY_VERSION:-}" ]] || rel+="latest/download"
src_ref="${STEAMIFY_VERSION:+v$STEAMIFY_VERSION}"; src_ref="${src_ref:-main}"
dest="$(cd "$(dirname "$0")" && pwd)/archiso/airootfs/usr/local/share/steamify/steamify.sh"
mkdir -p "$(dirname "$dest")"
if [[ -n "${1:-}" ]]; then
(cd "$1" && .github/tools/bundle.sh "$dest")
else
curl -fsSL "$rel/steamify.sh" -o "$dest"
fi
# The power-off fix's source, for build-live-modules.sh (live session).
poweroff="$(dirname "$dest")/steamify-fremont-poweroff.c"
if [[ -n "${1:-}" ]]; then
cp "$1/patches/steamify-fremont-poweroff.c" "$poweroff"
else
curl -fsSL "https://raw.githubusercontent.com/theupriser/steamify-cachyos/$src_ref/patches/steamify-fremont-poweroff.c" -o "$poweroff"
fi
# The installer's Steamify page (/usr/local/share/steamify-installer/
# SteamifyPage.qml) shows the app's explanations (Theme.qml, Texts.qml, from the
# checkout, else the newest release's app) and its rows (Items.qml: this
# fallback until calamares-online.sh writes the real list).
qml="$(dirname "$dest")/qml"
rm -rf "$qml"; mkdir -p "$qml"
if [[ -n "${1:-}" ]]; then
cp "$1"/ui/qml/{Theme,Texts}.qml "$qml/"
else
curl -fsSL "$rel/steamify-app.tar.gz" |
tar -xz -C "$qml" --strip-components=2 ui/qml/Theme.qml ui/qml/Texts.qml
fi
cp "$(cd "$(dirname "$0")" && pwd)/archiso/airootfs/usr/local/share/steamify-installer/Items.qml" "$qml/"
printf '%s\n' 'singleton Theme 1.0 Theme.qml' 'singleton Texts 1.0 Texts.qml' 'singleton Items 1.0 Items.qml' > "$qml/qmldir"
grep -q -- '--defaults' "$dest" || { echo "This Steamify has no --defaults (needs 2.6.0 or newer)." >&2; exit 1; }
chmod 755 "$dest"
# Temporary: cachyos-calamares-next 3.4.2-13 is linked against Boost 1.91
# while the repos ship 1.92, so the installer doesn't start. Boost's
# libraries carry their version in the name: 1.91's sit next to 1.92's.
# Drop this once CachyOS rebuilds the installer.
boost=boost-libs-1.91.0-2-x86_64.pkg.tar.zst
libs="$(cd "$(dirname "$0")" && pwd)/archiso/airootfs"
curl -fsSL "https://archive.archlinux.org/packages/b/boost-libs/$boost" -o "/tmp/$boost"
tar -xf "/tmp/$boost" -C "$libs" --wildcards 'usr/lib/libboost_*.so.1.91.0'
rm -f "/tmp/$boost"
echo "Steamify $(grep -m1 '^VERSION=' "$dest" | cut -d= -f2) on the ISO."
+31 -6
View File
@@ -104,7 +104,14 @@ generate_version_tag() {
local _profile="$1" local _profile="$1"
local _version="$2" local _version="$2"
if [ "$_profile" == "desktop" ]; then if [ "$_profile" == "desktop" ]; then
echo "${_version}" > ${src_dir}/archiso/airootfs/etc/version-tag # Steam Machine edition: CachyOS Hello won't install an ISO newer
# than CachyOS's newest release (it calls it a testing ISO), so the
# tag is that release, the one this build follows; the build's own
# date goes to /etc/steammachine-iso-build.
local _release
_release="$(curl -fsS --max-time 20 https://cachyos.org/versions.json | grep -o '"desktopISOVersion":"[0-9]*"' | grep -o '[0-9]\+')"
echo "${_release:-${_version}}" > ${src_dir}/archiso/airootfs/etc/version-tag
echo "${_version}" > ${src_dir}/archiso/airootfs/etc/steammachine-iso-build
fi fi
} }
@@ -122,6 +129,10 @@ modify_mkarchiso() {
else else
msg "mkarchiso is already patched!" msg "mkarchiso is already patched!"
fi fi
# Steamify: live-system changes after the packages (they overwrite airootfs).
if ! grep -q 'steamify-customize.sh' /usr/bin/mkarchiso; then
sudo sed "s#_run_once _make_customize_airootfs#_run_once _make_customize_airootfs\n\t${src_dir}/steamify-customize.sh \"\${pacstrap_dir}\"#" -i /usr/bin/mkarchiso
fi
} }
prepare_profile(){ prepare_profile(){
@@ -129,7 +140,10 @@ prepare_profile(){
info "Profile: [%s]" "${profile}" info "Profile: [%s]" "${profile}"
local _iso_version="$(date +%y%m%d)" # The release tag without its v when the release workflow builds (STEAMIFY_ISO_VERSION): the boot menu
# and /etc/steammachine-iso-build name the same release as the file, label and tag (no clock of its own).
# By hand, without a tag: local.
local _iso_version="${STEAMIFY_ISO_VERSION:-local}"
change_grub_version "${_iso_version}" change_grub_version "${_iso_version}"
# Fetch up-to-date version of CachyOS repo mirrorlist # Fetch up-to-date version of CachyOS repo mirrorlist
@@ -187,11 +201,22 @@ run_build() {
[ -d "$outFolder/$_profile" ] || mkdir -p "$outFolder/$_profile" [ -d "$outFolder/$_profile" ] || mkdir -p "$outFolder/$_profile"
cd ${work_dir}/archiso/ cd ${work_dir}/archiso/
sudo mkarchiso -v -w ${work_dir} -o "$outFolder/$_profile" ${work_dir}/archiso/ # sudo drops the environment: the release tag's file name and label (profiledef.sh) must get through.
sudo chown $USER $outFolder sudo --preserve-env=STEAMIFY_ISO_VERSION,STEAMIFY_BUILD_STAMP mkarchiso -v -w ${work_dir} -o "$outFolder/$_profile" ${work_dir}/archiso/
# $USER is empty when this runs as root (e.g. in a container): an empty
# chown failed and aborted the rest (the build was done, the exit code 1).
sudo chown "${SUDO_USER:-${USER:-$(id -un)}}" "$outFolder"
cp ${work_dir}/iso/arch/pkglist.x86_64.txt "$outFolder/$_profile/$(gen_iso_fn).pkgs.txt" # Steamify: profiledef.sh's iso_name (steamify-cachyos) names the ISO, so
mv "$outFolder/$_profile/cachyos-$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)-x86_64.iso" "$outFolder/$_profile/${iso_file}" # CachyOS's rename only applies when mkarchiso wrote cachyos-<date>.
local built="$outFolder/$_profile/cachyos-$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)-x86_64.iso"
if [[ -e "$built" ]]; then
mv "$built" "$outFolder/$_profile/${iso_file}"
else
built="$(ls -t "$outFolder/$_profile"/*.iso | head -1)"
iso_file="$(basename "$built")"
fi
cp ${work_dir}/iso/arch/pkglist.x86_64.txt "$outFolder/$_profile/${iso_file%.iso}.pkgs.txt"
msg "Done [Build ISO] ${iso_file}" msg "Done [Build ISO] ${iso_file}"
msg "Finished building [%s]" "${_profile}" msg "Finished building [%s]" "${_profile}"