diff --git a/.github/workflows/bundle.yml b/.github/workflows/bundle.yml new file mode 100644 index 0000000..ee7516d --- /dev/null +++ b/.github/workflows/bundle.yml @@ -0,0 +1,54 @@ +# Builds the single-file wizard (tools/bundle.sh) and publishes it as the +# asset of the rolling "latest" release, for: +# bash <(curl -fsSL https://github.com/theupriser/cachyos-gamescope-boot/releases/download/latest/setup-gamescope-boot.sh) +# Pull requests only build and check it. +name: Single-file build + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: write + +jobs: + bundle: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # for the version in the bundle's header + + - name: Build + run: tools/bundle.sh dist/setup-gamescope-boot.sh + + - name: Check + run: | + bash -n dist/setup-gamescope-boot.sh + # SC2034/SC2154: variables used across the inlined modules. + shellcheck -S warning -e SC2034,SC2154 dist/setup-gamescope-boot.sh + + - uses: actions/upload-artifact@v4 + with: + name: setup-gamescope-boot + path: dist/setup-gamescope-boot.sh + + - name: Publish to the "latest" release + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + env: + GH_TOKEN: ${{ github.token }} + run: | + notes="Single-file build of $(git describe --always) ($(git log -1 --format=%cs)). + Run it with: + + bash <(curl -fsSL https://github.com/${{ github.repository }}/releases/download/latest/setup-gamescope-boot.sh)" + if gh release view latest >/dev/null 2>&1; then + gh release edit latest --notes "$notes" + else + gh release create latest --title "Latest single-file build" --notes "$notes" --latest + fi + # Move the tag to this commit and replace the asset. + git tag -f latest && git push -f origin latest + gh release upload latest dist/setup-gamescope-boot.sh --clobber diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..849ddff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/AGENTS.md b/AGENTS.md index ab67500..1ae2326 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,12 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine `krevert `. Never call `kwriteconfig6` directly for settings a component owns. System files are backed up with `backup_file` and restored on disable. +- **Single-file build.** `tools/bundle.sh` inlines `lib/*.sh` in the order of + the entry point's `for lib in ...; do` source loop and wraps everything + after that loop in `main()`. Keep that loop on one line, keep all logic in + functions, and don't rely on `SCRIPT_DIR` for anything but sourcing. + CI (`.github/workflows/bundle.yml`) publishes the bundle to the `latest` + release on pushes to `main`. ## Conventions diff --git a/README.md b/README.md index 1fd4daf..aec4748 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,16 @@ Everything you turn off is put back the way it was. Open **Konsole** on your Plasma desktop and run: ```bash -git clone -cd +bash <(curl -fsSL https://github.com/theupriser/cachyos-gamescope-boot/releases/download/latest/setup-gamescope-boot.sh) +``` + +That downloads and runs the latest single-file version. `curl -fsSL ... | bash` +works too. Prefer to keep a copy, or to look at the script first? Clone the +repository instead: + +```bash +git clone https://github.com/theupriser/cachyos-gamescope-boot.git +cd cachyos-gamescope-boot ./setup-gamescope-boot.sh ``` @@ -63,7 +71,8 @@ Run it again whenever you like - to change your choices, to turn things off again, or after a CachyOS update (press `a` in the menu to re-apply everything that's on). -Keep the whole folder: the script needs the files in `lib/` next to it. +If you cloned the repository, keep the whole folder: the script needs the +files in `lib/` next to it. ## Using it @@ -312,6 +321,15 @@ gamescope-session, ...) stay installed. | `lib/vapor-theme.sh` | SteamOS theme: Vapor, Valve's defaults, panel, dark mode | | `lib/single-user.sh` | Single user mode: no lock screen, user switching or log out | | `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager | +| `tools/bundle.sh` | Builds the single-file version (`dist/setup-gamescope-boot.sh`) | +| `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` | + +The single-file version is generated: on every push to `main`, GitHub +Actions runs `tools/bundle.sh`, checks the result with `bash -n` and +shellcheck, and uploads it to the rolling `latest` release. It inlines +`lib/*.sh` and wraps the entry point in `main()`, so bash has read the whole +file before anything runs; when stdin is a pipe (`curl | bash`) it reattaches +the terminal for the menu (set `WIZARD_KEEP_STDIN=1` to keep piped input). Notes for contributors and AI coding agents are in [`AGENTS.md`](AGENTS.md). diff --git a/tools/bundle.sh b/tools/bundle.sh new file mode 100755 index 0000000..d789454 --- /dev/null +++ b/tools/bundle.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Build a single-file version of the wizard for `curl | bash` use: +# tools/bundle.sh [output] (default: dist/setup-gamescope-boot.sh) +# +# Inlines lib/*.sh in the order setup-gamescope-boot.sh sources them and +# wraps the entry point's body in main(), so bash has read the whole file +# before anything runs. That lets the bundle reattach stdin to the terminal +# when it was piped in by curl, so the interactive menu still works. +set -euo pipefail +cd "$(dirname "$0")/.." + +out="${1:-dist/setup-gamescope-boot.sh}" +entry=setup-gamescope-boot.sh +mkdir -p "$(dirname "$out")" + +# Library order, taken from the entry point's source loop. +libs=$(grep -oP '^for lib in \K.*(?=; do$)' "$entry") +[[ -n "$libs" ]] || { echo "bundle: can't find the lib source loop in $entry" >&2; exit 1; } + +version="$(git describe --always --dirty 2>/dev/null || echo unknown)" + +{ + # Header comment and `set` line of the entry point, up to the source loop. + sed -n '1,/^SCRIPT_DIR=/p' "$entry" | sed '$d' + echo "# Single-file build ($version), generated by tools/bundle.sh - edit the" + echo "# sources in the repository instead: https://github.com/theupriser/cachyos-gamescope-boot" + echo + for lib in $libs; do + echo "# ----- lib/$lib.sh -----" + # Drop each module's shebang and its "Sourced by ..." boilerplate. + sed -e '1{/^#!/d}' -e '/^# Sourced by setup-gamescope-boot.sh/d' "lib/$lib.sh" + echo + done + echo "# ----- setup-gamescope-boot.sh -----" + echo "main() {" + # Everything after the source loop is the entry point's body. + sed -n '/^for lib in /,$p' "$entry" | sed '1,/^done$/d' | sed 's/^/ /; s/^ $//' + echo "}" + echo + echo "# Piped in via curl, bash reads this very file from stdin. Give the menu" + echo "# the terminal instead - on the same line as main and exit: bash has read" + echo "# this line in full, so it never reads further script from the terminal." + echo 'reattach_tty() { if [[ ! -t 0 && -z "${WIZARD_KEEP_STDIN:-}" ]]; then { exec /dev/null || true; fi; }' + echo 'reattach_tty; main "$@"; exit $?' +} > "$out" + +chmod +x "$out" +bash -n "$out" +echo "Wrote $out ($version)"