diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 46e392b..059be29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,9 @@ concurrency: jobs: build: + # CachyOS's plain ISO and its quicktest matrix: only in CachyOS's own repo, not in this fork + # (the Steamify ISO is iso-2-gitea-build.yml). + if: github.repository == 'CachyOS/CachyOS-Live-ISO' runs-on: ubuntu-latest container: image: archlinux:base-devel diff --git a/.github/workflows/iso-1-github-tag.yml b/.github/workflows/iso-1-github-tag.yml new file mode 100644 index 0000000..616dd73 --- /dev/null +++ b/.github/workflows/iso-1-github-tag.yml @@ -0,0 +1,69 @@ +# 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): +# master releases v--, feat/steamify a pre-release v-dev.<...>. +name: ISO 1/2 · Tag and release (GitHub) + +# Started by a new Steamify release (its bundle.yml, secret ISO_DISPATCH_TOKEN) or by hand (Run workflow, +# on master for a release, on feat/steamify for a test build); not by every push. +on: + workflow_dispatch: + +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 + + - name: Tag and release + env: + GH_TOKEN: ${{ github.token }} + run: | + steamify="$(gh release view -R theupriser/steamify-cachyos --json tagName -q .tagName)" + steamify="${steamify#v}" + stamp="$(date -u +%Y.%m.%d-%H%M)" + if [ "${{ github.ref }}" = refs/heads/master ]; then + tag="v$steamify-$stamp" pre="" name="CachyOS with Steamify Live ISO $steamify ($stamp UTC)" + else + tag="v$steamify-dev.$stamp" pre="--prerelease" name="CachyOS with Steamify Live ISO $steamify (test build $stamp 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 Steamify section of CHANGELOG.md ("## CachyOS with Steamify Live ISO"), without its heading. + section="$(awk '/^## CachyOS with Steamify Live ISO/ {found = 1; next} found && /^#/ {exit} found' CHANGELOG.md)" + notes="The ISO is built from \`$(git rev-parse --short HEAD)\` (${{ github.ref_name }}) with Steamify $steamify and published on the mirror (GitHub releases take at most 2 GB per file), about half an hour after this release (the mirror's sync, then the build): + + **Download: [$file]($dl/$file)** ([SHA-256]($dl/$file.sha256), [release page]($GITEA_URL/$GITEA_REPO/releases/tag/$tag)) + + Sources: [Steamify](https://github.com/theupriser/steamify-cachyos/tree/v$steamify), this repository at this tag, and the packages' sources at [CachyOS](https://github.com/CachyOS) and [Arch Linux](https://archlinux.org/packages/). + + $section" + # 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" + + # Optional: sync the mirror at once instead of at its interval (a Gitea token with repository write + # access as the secret GIT_UPRISER_TOKEN). + - name: Sync the Gitea mirror + env: + GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }} + run: | + if [ -z "$GIT_UPRISER_TOKEN" ]; then echo "No GIT_UPRISER_TOKEN: the mirror picks the tag up at its next sync."; exit 0; fi + curl -fsS --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" && + echo "Mirror sync started" diff --git a/.github/workflows/iso-2-gitea-build.yml b/.github/workflows/iso-2-gitea-build.yml new file mode 100644 index 0000000..edae835 --- /dev/null +++ b/.github/workflows/iso-2-gitea-build.yml @@ -0,0 +1,112 @@ +# 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-- (master) or v-dev.<...> (feat/steamify, a pre-release); +# the ISO gets exactly that Steamify release, and its label the same moment. GitHub skips it. +# 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) + +on: + push: + tags: ['v*'] + workflow_dispatch: + +concurrency: + group: steamify-iso-${{ github.ref }} + cancel-in-progress: true + +jobs: + 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 + steps: + - name: Tools + run: | + pacman-key --init && pacman-key --populate + # nodejs: the actions below run in this container + pacman -Syu --noconfirm --needed archiso mkinitcpio-archiso git squashfs-tools grub sudo 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]{4})\.([0-9]{2})\.([0-9]{2})-([0-9]{4})$ ]] || + { echo "::error::$tag is not a Steamify ISO release tag (v-[dev.]-)"; exit 1; } + m=("${BASH_REMATCH[@]}") + { + echo "STEAMIFY_VERSION=${m[1]}" + # One moment for the ISO's label (profiledef.sh) and the release's name: the tag's (UTC). + echo "STEAMIFY_BUILD_STAMP=${m[3]}${m[4]}${m[5]}_${m[6]}" + 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" + + - name: Steamify on the ISO (the version the tag names) + run: ./steamify-prepare.sh + + - name: Build + run: | + ./build-live-modules.sh + ./build-calamares-modules.sh + ./buildiso.sh -p desktop -w || ./buildiso.sh -p desktop -c -w + + - 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="${b:0:4}.${b:4:2}.${b:6:2}-${b:9:4}" + if [ "$ISO_PRERELEASE" = true ]; then + name="CachyOS with Steamify Live ISO $STEAMIFY_VERSION (test build $stamp UTC)" + else + name="CachyOS with Steamify Live ISO $STEAMIFY_VERSION ($stamp UTC)" + fi + # Never overwritten: a release for this tag that already has the ISO is left alone. + api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + if curl -fsS -H "Authorization: token $TOKEN" "$api/releases/tags/$tag" -o /tmp/rel.json && + [ "$(jq '[.assets[] | select(.name | endswith(".iso"))] | length' /tmp/rel.json)" -gt 0 ]; then + echo "::warning::$tag already has its ISO and won't be overwritten." + echo "publish=false" >> "$GITHUB_OUTPUT"; exit 0 + fi + { echo "publish=true"; echo "name=$name"; } >> "$GITHUB_OUTPUT" + section="$(awk '/^## CachyOS with Steamify Live ISO/ {found = 1; next} found && /^#/ {exit} found' CHANGELOG.md)" + { + echo "Built from \`$(git rev-parse --short HEAD)\` ($tag) with Steamify $STEAMIFY_VERSION." + echo + echo "SHA-256: \`$(cut -d' ' -f1 "$iso.sha256")\`, label \`STEAMIFY_${STEAMIFY_VERSION//./_}_$b\`" + echo + echo "Sources: [Steamify](https://github.com/theupriser/steamify-cachyos/tree/v$STEAMIFY_VERSION), this repository at this tag, and the packages' sources at [CachyOS](https://github.com/CachyOS) and [Arch Linux](https://archlinux.org/packages/)." + echo + echo "$section" + } > 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 diff --git a/.gitignore b/.gitignore index 6713072..fae11cb 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,9 @@ archiso/airootfs/etc/environment archiso/airootfs/etc/pacman.d/cachyos-mirrorlist archiso/airootfs/etc/edition-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/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3724971 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,132 @@ +# 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: ` 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=` 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: the Steamify version plus + GitHub's UTC time, `vX.Y.Z-[dev.]YYYY.MM.DD-HHMM`, an **annotated** tag and a release with notes and the direct + download link. `feat/steamify` makes a `dev.` pre-release, `master` a real one. +- `.github/workflows/iso-2-gitea-build.yml` (Gitea, `on: push: tags: v*`, skipped on GitHub) builds the ISO for that + tag and attaches it to the mirror's release. 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`), 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. +- CachyOS's own `Desktop ISO` workflow (`build.yml`) only runs in `CachyOS/CachyOS-Live-ISO`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ab16833..378af9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,46 @@ * Correctly handle VM environments * 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--`, 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_`); 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 **Features:** diff --git a/README.md b/README.md index 0adb4f3..55e3f2e 100644 --- a/README.md +++ b/README.md @@ -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-.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. ### buildiso diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..d142d57 --- /dev/null +++ b/TODO.md @@ -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-.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 ` / `vmisobuild.sh --steamify ` 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--x86_64.iso` (`iso_name` in + `archiso/profiledef.sh`); the volume label stays `COS_`. +- **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. diff --git a/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf b/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf new file mode 100644 index 0000000..b699cb9 --- /dev/null +++ b/archiso/airootfs/etc/calamares/modules/packagechooserq_steamifypage.conf @@ -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 diff --git a/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf b/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf new file mode 100644 index 0000000..24dfd01 --- /dev/null +++ b/archiso/airootfs/etc/calamares/modules/shellprocess_steamify.conf @@ -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)" diff --git a/archiso/airootfs/etc/xdg/autostart/steamify-live-theme.desktop b/archiso/airootfs/etc/xdg/autostart/steamify-live-theme.desktop new file mode 100644 index 0000000..0dec1d2 --- /dev/null +++ b/archiso/airootfs/etc/xdg/autostart/steamify-live-theme.desktop @@ -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 diff --git a/archiso/airootfs/usr/local/bin/calamares-online.sh b/archiso/airootfs/usr/local/bin/calamares-online.sh index b587323..6709e37 100755 --- a/archiso/airootfs/usr/local/bin/calamares-online.sh +++ b/archiso/airootfs/usr/local/bin/calamares-online.sh @@ -33,6 +33,24 @@ main() { echo "USING ISO VERSION: ${ISO_VERSION}" 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 inxi -F > "$log" @@ -44,7 +62,64 @@ main() { ########## System: $SYSTEM 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 + # 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 } diff --git a/archiso/airootfs/usr/local/bin/steamify-install b/archiso/airootfs/usr/local/bin/steamify-install new file mode 100755 index 0000000..b0bc080 --- /dev/null +++ b/archiso/airootfs/usr/local/bin/steamify-install @@ -0,0 +1,62 @@ +#!/bin/bash +# steamify-install : 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: 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 diff --git a/archiso/airootfs/usr/local/bin/steamify-live-theme b/archiso/airootfs/usr/local/bin/steamify-live-theme new file mode 100755 index 0000000..64b3095 --- /dev/null +++ b/archiso/airootfs/usr/local/bin/steamify-live-theme @@ -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 diff --git a/archiso/airootfs/usr/local/share/steamify-installer/Items.qml b/archiso/airootfs/usr/local/share/steamify-installer/Items.qml new file mode 100644 index 0000000..582574e --- /dev/null +++ b/archiso/airootfs/usr/local/share/steamify-installer/Items.qml @@ -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 } + ] +} diff --git a/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml b/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml new file mode 100644 index 0000000..34874db --- /dev/null +++ b/archiso/airootfs/usr/local/share/steamify-installer/SteamifyPage.qml @@ -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: "); 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 } + } + } + } + } + } + } +} diff --git a/archiso/packages_desktop.x86_64 b/archiso/packages_desktop.x86_64 index 6dc5c53..5fd8d02 100644 --- a/archiso/packages_desktop.x86_64 +++ b/archiso/packages_desktop.x86_64 @@ -90,6 +90,7 @@ linux-cachyos linux-cachyos-nvidia-open linux-cachyos-zfs linux-firmware +cachyos-vapor linux-firmware-marvell lsb-release lsof diff --git a/archiso/profiledef.sh b/archiso/profiledef.sh index ce4a089..d862a52 100644 --- a/archiso/profiledef.sh +++ b/archiso/profiledef.sh @@ -1,11 +1,20 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 -iso_name="cachyos" -iso_label="COS_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)" +iso_name="steamify-cachyos" +# 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 date and time, e.g. STEAMIFY_2_9_3_20260929_2030 (ISO 9660: +# at most 32 characters, A-Z 0-9 _). STEAMIFY_BUILD_STAMP (YYYYMMDD_HHMM, 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 " -iso_application="CachyOS Live/Rescue DVD" -iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)" +iso_application="Steamify CachyOS Live (based on CachyOS)" +# The file name (--x86_64.iso) is the release tag without its v (STEAMIFY_ISO_VERSION +# from the tag: steamify-cachyos-2.9.3-dev.2026.09.29-2030-x86_64.iso). Built by hand, without a tag: local. +iso_version="${STEAMIFY_ISO_VERSION:-local}" install_dir="arch" buildmodes=('iso') ## GRUB @@ -27,6 +36,8 @@ file_permissions=( ["/root/.gnupg"]="0:0:700" ["/usr/local/bin/choose-mirror"]="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/remove-nvidia"]="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/nvidia-module-loader"]="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" ) diff --git a/build-calamares-modules.sh b/build-calamares-modules.sh new file mode 100755 index 0000000..95fdf66 --- /dev/null +++ b/build-calamares-modules.sh @@ -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")." diff --git a/build-live-modules.sh b/build-live-modules.sh new file mode 100755 index 0000000..9b81ee7 --- /dev/null +++ b/build-live-modules.sh @@ -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' diff --git a/buildiso.sh b/buildiso.sh index 7effbf4..055d50f 100755 --- a/buildiso.sh +++ b/buildiso.sh @@ -76,6 +76,10 @@ for sig in TERM HUP QUIT; do done 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 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}" diff --git a/patches/packagechooserq-steamify-summary.patch b/patches/packagechooserq-steamify-summary.patch new file mode 100644 index 0000000..4f0e39e --- /dev/null +++ b/patches/packagechooserq-steamify-summary.patch @@ -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 ++#include ++#include ++#include ++#include ++ + 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: nothing" ); ++ } ++ QString status = tr( "Steamify:" ) + QStringLiteral( "
    " ); ++ for ( const auto& item : on ) ++ { ++ status += QStringLiteral( "
  • " ) + item + QStringLiteral( "
  • " ); ++ } ++ status += QStringLiteral( "
" ); ++ return status; + } + + bool diff --git a/steamify-customize.sh b/steamify-customize.sh new file mode 100755 index 0000000..309222d --- /dev/null +++ b/steamify-customize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# steamify-customize.sh : 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 diff --git a/steamify-prepare.sh b/steamify-prepare.sh new file mode 100755 index 0000000..f45a60f --- /dev/null +++ b/steamify-prepare.sh @@ -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." diff --git a/util-iso.sh b/util-iso.sh index 30010e1..2be2f76 100755 --- a/util-iso.sh +++ b/util-iso.sh @@ -104,7 +104,14 @@ generate_version_tag() { local _profile="$1" local _version="$2" 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 } @@ -122,6 +129,10 @@ modify_mkarchiso() { else msg "mkarchiso is already patched!" 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(){ @@ -129,7 +140,10 @@ prepare_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}" # Fetch up-to-date version of CachyOS repo mirrorlist @@ -187,11 +201,22 @@ run_build() { [ -d "$outFolder/$_profile" ] || mkdir -p "$outFolder/$_profile" cd ${work_dir}/archiso/ - sudo mkarchiso -v -w ${work_dir} -o "$outFolder/$_profile" ${work_dir}/archiso/ - sudo chown $USER $outFolder + # sudo drops the environment: the release tag's file name and label (profiledef.sh) must get through. + 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" - mv "$outFolder/$_profile/cachyos-$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)-x86_64.iso" "$outFolder/$_profile/${iso_file}" + # Steamify: profiledef.sh's iso_name (steamify-cachyos) names the ISO, so + # CachyOS's rename only applies when mkarchiso wrote cachyos-. + 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 "Finished building [%s]" "${_profile}"