diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7ac7c7d..c84cf16 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -30,17 +30,51 @@ jobs: rpm-package: runs-on: ubuntu-latest container: fedora:44 + env: + WORKSPACE: ${{ gitea.workspace }} steps: - - name: Build dependencies - run: dnf -y install git golang rpm-build systemd-rpm-macros curl python3 - - uses: actions/checkout@v4 - - name: Build backend and RPM + - name: Bootstrap minimal checkout dependency + shell: bash run: | set -euo pipefail + mkdir -p "$WORKSPACE/.dnf-cache" + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install git-core + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true + df -h / + - uses: actions/checkout@v4 + - name: Build backend with minimal Go toolchain + shell: bash + run: | + set -euo pipefail + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install golang-bin + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true bash ./build.sh + # The backend is fully static; release the compiler before bringing in rpmbuild. + dnf -y --setopt=system_cachedir="$WORKSPACE/.dnf-cache" remove golang-bin || true + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true + df -h / + - name: Build RPM in low-disk mode + shell: bash + run: | + set -euo pipefail + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install rpm-build + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true bash ./packaging/build-rpm.sh VERSION="$(tr -d '[:space:]' < VERSION)" rpm -qpi "dist/citizen-launcher-${VERSION}-1.linux.x86_64.rpm" + df -h / arch-package: runs-on: ubuntu-latest diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 2f9336a..9357a7c 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -73,21 +73,56 @@ jobs: container: fedora:44 env: CITIZEN_LAUNCHER_RELEASE_REPO: gitea:${{ gitea.api_url }}/repos/${{ gitea.repository }} + WORKSPACE: ${{ gitea.workspace }} steps: - - name: Build dependencies - run: dnf -y install git golang rpm-build systemd-rpm-macros curl python3 + - name: Bootstrap minimal checkout/release dependencies + shell: bash + run: | + set -euo pipefail + mkdir -p "$WORKSPACE/.dnf-cache" + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install git-core jq + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true + df -h / - uses: actions/checkout@v4 - - name: Verify tag and build RPM + - name: Verify tag and build backend shell: bash run: | set -euo pipefail VERSION="$(tr -d '[:space:]' < VERSION)" test "$TAG_NAME" = "v${VERSION}" + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install golang-bin + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true bash ./build.sh + # The backend is static, so free the compiler before installing rpmbuild. + dnf -y --setopt=system_cachedir="$WORKSPACE/.dnf-cache" remove golang-bin || true + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true + df -h / + - name: Build RPM in low-disk mode + shell: bash + run: | + set -euo pipefail + dnf -y \ + --setopt=system_cachedir="$WORKSPACE/.dnf-cache" \ + --setopt=install_weak_deps=False \ + --setopt=tsflags=nodocs \ + install rpm-build + find "$WORKSPACE/.dnf-cache" -type f -name '*.rpm' -delete 2>/dev/null || true bash ./packaging/build-rpm.sh + VERSION="$(tr -d '[:space:]' < VERSION)" rpm -qpi "dist/citizen-launcher-${VERSION}-1.linux.x86_64.rpm" + df -h / - name: Upload RPM to Gitea shell: bash + env: + CITIZEN_JSON_BACKEND: jq run: | set -euo pipefail VERSION="$(tr -d '[:space:]' < VERSION)" diff --git a/GITEA.md b/GITEA.md index 4d4c4cc..147abf3 100644 --- a/GITEA.md +++ b/GITEA.md @@ -1,6 +1,6 @@ # Gitea Actions setup -Citizen Launcher 1.1.2 uses Gitea Actions natively. Workflows are in `.gitea/workflows/`; the old `.github/workflows/` directory is intentionally absent. +Citizen Launcher 1.1.3 uses Gitea Actions natively. Workflows are in `.gitea/workflows/`; the old `.github/workflows/` directory is intentionally absent. ## Requirements @@ -26,8 +26,8 @@ If your runner uses a different label, replace `runs-on: ubuntu-latest` in both Push a version tag matching `VERSION`, for example: ```bash -git tag v1.1.2 -git push origin v1.1.2 +git tag v1.1.3 +git push origin v1.1.3 ``` `.gitea/workflows/release.yml` then: @@ -70,3 +70,24 @@ Existing `github:owner/repo` and legacy `owner/repo` sources remain supported. ## Reruns The release helper `scripts/gitea-release.sh` is intentionally rerun-safe. Existing same-named attachments are removed before a replacement is uploaded, so rerunning a failed release does not create duplicate package assets. + +## Small runner / Fedora RPM jobs + +The Fedora jobs are intentionally written for constrained `act_runner` Docker roots. Do not replace the staged dependency setup with one large command such as: + +```text +dnf install git golang rpm-build systemd-rpm-macros curl python3 +``` + +That pulls Fedora's full Go source package, weak RPM build dependencies and other tooling at the same time and can exceed a small container root filesystem before `rpmbuild` starts. + +The 1.1.3 workflow instead: + +1. installs only `git-core` for checkout; +2. stores DNF cache data on `${{ gitea.workspace }}` rather than `/var/cache/libdnf5`; +3. disables weak dependencies and docs in the disposable build container; +4. installs `golang-bin`, builds the static backend, then removes the compiler; +5. installs `rpm-build` only after Go has been removed; +6. uses `jq` instead of Python for the RPM release upload path. + +The workflow prints `df -h /` after the major phases. If a runner still cannot fit the native Fedora build after this reduction, increase the Docker/container root storage rather than silently dropping RPM verification. diff --git a/README.md b/README.md index 8a1a145..a1832fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Citizen Launcher 1.1.2 +# Citizen Launcher 1.1.3 Citizen Launcher is a cross-distribution Star Citizen setup, launch, repair and maintenance application for Linux. The same static Go core runs on Debian/Ubuntu, Fedora/RHEL derivatives, Arch derivatives, openSUSE and generic glibc-based desktop Linux. Omarchy support is an optional integration, not a runtime requirement. @@ -8,7 +8,7 @@ Citizen Launcher is a cross-distribution Star Citizen setup, launch, repair and The launcher owns the fragile user-space gaming stack so users do not have to pick Wine builds, copy DXVK DLLs or rebuild prefixes by hand. Kernel, GPU driver, firmware and base-distribution upgrades remain owned by the Linux distribution. -The 1.0.1 gaming path was confirmed end-to-end on real hardware: launcher, RSI installation and Star Citizen playability. 1.1.2 keeps that gaming core and adds native multi-distribution packaging/update integration. +The 1.0.1 gaming path was confirmed end-to-end on real hardware: launcher, RSI installation and Star Citizen playability. 1.1.3 keeps that gaming core, the multi-distribution packaging/update layer, and hardens Fedora RPM CI for small Gitea runners by staging build tools instead of installing the whole toolchain at once. ## Supported Linux families @@ -32,25 +32,25 @@ The installer detects the distro family, prefers a native package found in `dist ### Debian / Ubuntu / Mint ```bash -sudo apt install ./dist/citizen-launcher_1.1.2_amd64.deb +sudo apt install ./dist/citizen-launcher_1.1.3_amd64.deb ``` ### Fedora / Nobara / RHEL family ```bash -sudo dnf install ./dist/citizen-launcher-1.1.2-1.linux.x86_64.rpm +sudo dnf install ./dist/citizen-launcher-1.1.3-1.linux.x86_64.rpm ``` ### openSUSE Tumbleweed / Slowroll ```bash -sudo zypper install ./dist/citizen-launcher-1.1.2-1.linux.x86_64.rpm +sudo zypper install ./dist/citizen-launcher-1.1.3-1.linux.x86_64.rpm ``` ### Arch / Manjaro / EndeavourOS / CachyOS / Omarchy ```bash -sudo pacman -U ./dist/citizen-launcher-1.1.2-1-x86_64.pkg.tar.zst +sudo pacman -U ./dist/citizen-launcher-1.1.3-1-x86_64.pkg.tar.zst ``` ### Generic / immutable desktop Linux diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d5ab8d6..588bbb9 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,35 +1,31 @@ -# Citizen Launcher 1.1.2 +# Citizen Launcher 1.1.3 -## Gitea-native CI/CD release +## Gitea Fedora/RPM runner hardening -1.1.2 keeps the confirmed playable multi-distribution 1.1.x runtime and hardens the Gitea Actions pipeline introduced in 1.1.1. +1.1.3 keeps the confirmed playable multi-distribution 1.1.x runtime and fixes the next real Gitea `act_runner` failure found in the Fedora RPM job. -### Gitea workflows +### Root cause -- workflows now live exclusively under `.gitea/workflows/` -- CI keeps the full Go/race/package verification plus Fedora RPM and Arch package builds -- tag releases are created with Gitea's REST API and built-in `GITEA_TOKEN` -- release publishing no longer depends on the `gh` CLI -- release jobs upload `.deb`, `.rpm`, `.pkg.tar.zst`, generic tarball and one combined `SHA256SUMS.txt` -- upload is idempotent: a rerun replaces same-named release attachments instead of duplicating them +The previous workflow installed `git`, the full `golang` meta package, `rpm-build`, `systemd-rpm-macros`, `curl` and `python3` in one Fedora transaction. Fedora also enabled weak dependencies by default. On a small Docker root filesystem this expanded into hundreds of packages and exhausted `/` before the RPM build even started. -### Gitea-aware self-update +### Fix -Packages produced by Gitea Actions embed an exact `gitea:` update source. The launcher can now resolve Gitea's latest-release API and hydrate per-asset SHA-256 values from the workflow-generated `SHA256SUMS.txt`. GitHub release sources remain supported for existing installations. +- Fedora jobs now use `git-core` instead of the full Git package. +- Go builds use `golang-bin` rather than the `golang` meta package, avoiding the large source package. +- `systemd-rpm-macros`, Python and other unnecessary RPM-build dependencies are no longer installed in the Fedora build phase. +- DNF weak dependencies are disabled and documentation payloads are skipped in CI containers. +- DNF's root cache is redirected to the mounted Gitea workspace volume. +- Downloaded RPM cache files are deleted after each transaction. +- The static Go backend is built first, then `golang-bin` is removed before `rpm-build` is installed. The two heavy toolchains no longer occupy the container root simultaneously. +- The Gitea release helper now supports a `jq` JSON backend, so the Fedora release job does not need the full Python runtime just to upload an RPM. +- Disk usage is printed between phases to make future runner-capacity problems immediately visible. -For privileged updates, a configured Gitea source must use HTTPS. This preserves the fail-closed package-update model. +### Regression guards -### Runner portability - -The release flow does not depend on cross-job `upload-artifact` compatibility. Each native build uploads its package directly to the Gitea Release, and the final job downloads those release attachments to create the checksum manifest. This works across a wider range of act_runner versions. +- `tests/rpm-ci-footprint.sh` asserts the low-disk policy in both CI and release RPM jobs. +- Gitea release-helper tests now exercise both Python and `jq` JSON backends. +- The earlier `0644`/`Permission denied` workflow hardening remains in place. ### Runtime Wine, DXVK, RSI Launcher setup, hardware checks, single-instance protection, repair, support bundles and the already confirmed playable Star Citizen path are unchanged. -## Gitea CI hardening - -- Gitea Actions invokes repository shell scripts explicitly through `bash`. -- Nested test, build and release scripts use the same mode-independent convention. -- Added a regression guard against direct `.sh` execution in the critical CI graph. -- Fixes Gitea/act exit code 126 (`Permission denied`) when checkout files are mode `0644`. - diff --git a/TEST_REPORT.md b/TEST_REPORT.md index 246b3de..2640d42 100644 --- a/TEST_REPORT.md +++ b/TEST_REPORT.md @@ -1,14 +1,20 @@ -# Citizen Launcher 1.1.2 – Verification Report +# Citizen Launcher 1.1.3 – Verification Report Date: 2026-09-01 ## Release scope -Citizen Launcher 1.1.2 keeps the confirmed playable 1.1.x multi-distribution gaming core and hardens the Gitea-native CI/CD introduced in 1.1.1 against checkout permission differences. +Citizen Launcher 1.1.3 keeps the confirmed playable multi-distribution 1.1.x gaming core and hardens Gitea-native RPM CI against constrained Fedora job-container storage. + +## Failure reproduced from real Gitea runner log + +The real Fedora 44 job reached dependency installation but failed before checkout/build because the transaction could not fit on `/`. The original dependency set expanded to hundreds of packages. The log ultimately reported that packages needed roughly 407 MB more root filesystem space and the `Build dependencies` step exited with status 1. + +1.1.3 therefore treats CI container footprint as a release property, not a runner-specific workaround. ## Automated release gate -The 1.1.2 tree passes the local release gate with: +The 1.1.3 tree passes the local release gate with: - shell syntax checks for installers, packaging, Gitea release helper and tests - `gofmt` cleanliness @@ -21,14 +27,30 @@ The 1.1.2 tree passes the local release gate with: - generic Linux amd64 tarball build and execution check - Gitea workflow YAML parsing - assertion that legacy `.github/workflows/` does not shadow `.gitea/workflows/` -- assertion that the release workflow does not use `gh release` or cross-job artifact actions -- mocked Gitea REST API test covering release creation, release update on rerun, asset upload, same-name asset replacement and asset download +- assertion that release publishing does not use `gh release` or cross-job artifact actions +- mocked Gitea REST API test covering release creation, update on rerun, asset upload, replacement and download +- Gitea release helper exercised with both Python and `jq` JSON backends - Gitea release-source parsing and `SHA256SUMS.txt` parser regression tests - static validation of RPM spec and Arch PKGBUILD/package hooks - multi-distro platform-family and immutable-host regression tests - native package self-update parser/asset-selection tests for DEB, RPM and pacman formats -- full regression suite repeated successfully after forcing every repository `*.sh` file to mode `0644`; CI does not depend on executable bits -- regression guard rejects direct repository `.sh` execution in Gitea workflow entry points +- mode-independent shell invocation checks for Gitea/act checkouts without execute bits +- low-disk Fedora RPM workflow policy regression test + +## Fedora RPM low-disk policy + +Both `.gitea/workflows/ci.yml` and `.gitea/workflows/release.yml` now enforce this shape: + +- DNF cache moved to the mounted Gitea workspace via `system_cachedir` +- `install_weak_deps=False` +- `tsflags=nodocs` +- `git-core`, not the full `git` package +- `golang-bin`, not the `golang` meta package +- `golang-bin` removed before `rpm-build` is installed +- no explicit `systemd-rpm-macros` in the build container +- no Python requirement for Fedora release upload; `jq` is used by `gitea-release.sh` + +`tests/rpm-ci-footprint.sh` fails if the heavy one-shot dependency pattern is reintroduced. ## Gitea CI/CD design verified @@ -37,40 +59,22 @@ Workflows live in: - `.gitea/workflows/ci.yml` - `.gitea/workflows/release.yml` -The release workflow uses Gitea-native contexts (`gitea.api_url`, `gitea.repository`, `gitea.ref_name`, `gitea.sha`, `gitea.token`) and the built-in job token with `code: read` / `releases: write` permissions. - -Release packages are uploaded directly to the Gitea Release rather than moved between jobs with `actions/upload-artifact`. The final job downloads the native release packages and publishes one deterministic `SHA256SUMS.txt`. - -The release helper is rerun-safe: an existing release is refreshed and same-named attachments are replaced instead of duplicated. - -## Gitea-aware launcher self-update - -A Gitea workflow build injects this form as the launcher's trusted update source: - -```text -gitea:/repos// -``` - -The launcher resolves Gitea's latest release API and maps the release `SHA256SUMS.txt` back onto the package assets before a privileged update is allowed. Privileged Gitea sources require HTTPS. Existing GitHub release-source syntax remains supported for migration/backward compatibility. - -A separate build check confirmed that a `gitea:https://.../api/v1/repos/owner/repo` source is successfully embedded into the static binary and reported by `self-update status`. +Release packages are uploaded directly to the Gitea Release. The final job downloads the native packages and publishes a deterministic `SHA256SUMS.txt`. The helper is rerun-safe and replaces same-named attachments. ## Native package build coverage -The current local build environment contains Debian packaging tools, therefore these artifacts can be built and inspected locally: +The local verification environment builds and inspects: -- `dist/citizen-launcher_1.1.2_amd64.deb` -- `dist/citizen-launcher-1.1.2-linux-amd64.tar.gz` +- `dist/citizen-launcher_1.1.3_amd64.deb` +- `dist/citizen-launcher-1.1.3-linux-amd64.tar.gz` -The local environment does not provide native `rpmbuild` / Arch `makepkg`; the Gitea workflows build those in Fedora and Arch job containers: +The Gitea workflows build these in native job containers: -- `citizen-launcher-1.1.2-1.linux.x86_64.rpm` -- `citizen-launcher-1.1.2-1-x86_64.pkg.tar.zst` +- `citizen-launcher-1.1.3-1.linux.x86_64.rpm` +- `citizen-launcher-1.1.3-1-x86_64.pkg.tar.zst` + +The current sandbox does not provide Docker/rpmbuild, so the exact Fedora container transaction cannot be executed locally. Instead, the workflow structure is regression-tested and the full application/package suite is executed locally. The next Gitea Fedora run is the authoritative integration check for the runner's actual storage quota. ## Gaming-core acceptance -The Star Citizen install/play path is unchanged from the already accepted 1.0.1/1.1.0 line. The CI migration changes release and update plumbing, not Wine/DXVK/RSI launch behavior. - -## Checkout permission regression - -The complete `tests/full-verify.sh` suite was repeated after forcing every repository `*.sh` file to Unix mode `0644`. It still completed successfully. This reproduces the Gitea/act checkout condition that caused exit code 126 and verifies that CI/CD no longer depends on executable bits for repository shell scripts. Distributed archives still mark shell scripts executable for convenience. +The Star Citizen install/play path is unchanged from the already accepted 1.0.1/1.1.x line. This release changes CI/release plumbing, not Wine/DXVK/RSI launch behavior. diff --git a/VERSION b/VERSION index 45a1b3f..781dcb0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.2 +1.1.3 diff --git a/backend/bin/citizen-launcher b/backend/bin/citizen-launcher index 61c091e..0df8de5 100644 Binary files a/backend/bin/citizen-launcher and b/backend/bin/citizen-launcher differ diff --git a/backend/cmd/citizen-launcher/main.go b/backend/cmd/citizen-launcher/main.go index 1e29114..07e2535 100644 --- a/backend/cmd/citizen-launcher/main.go +++ b/backend/cmd/citizen-launcher/main.go @@ -16,7 +16,7 @@ import ( ) var ( - appVersion = "1.1.2" + appVersion = "1.1.3" releaseRepo = "github:sendnwv/omarchy-sc" ) diff --git a/dist/citizen-launcher-1.1.3-linux-amd64.tar.gz b/dist/citizen-launcher-1.1.3-linux-amd64.tar.gz new file mode 100644 index 0000000..7bee15b Binary files /dev/null and b/dist/citizen-launcher-1.1.3-linux-amd64.tar.gz differ diff --git a/dist/citizen-launcher_1.1.3_amd64.deb b/dist/citizen-launcher_1.1.3_amd64.deb new file mode 100644 index 0000000..559602a Binary files /dev/null and b/dist/citizen-launcher_1.1.3_amd64.deb differ diff --git a/integrations/omarchy/Panel.qml b/integrations/omarchy/Panel.qml index ca40c62..274315e 100644 --- a/integrations/omarchy/Panel.qml +++ b/integrations/omarchy/Panel.qml @@ -12,7 +12,7 @@ Panel { property var anchorItem: null property var hostWidget: null - property string pluginVersion: "1.1.2" + property string pluginVersion: "1.1.3" property string health: "checking" property string depsState: "checking" property string depsMissing: "" @@ -106,7 +106,7 @@ Panel { values[lines[i].slice(0, p)] = lines[i].slice(p + 1) } - pluginVersion = values.plugin_version || "1.1.2" + pluginVersion = values.plugin_version || "1.1.3" health = values.health || "setup" depsState = values.deps || "missing" depsMissing = values.deps_missing || "" diff --git a/integrations/omarchy/citizenctl b/integrations/omarchy/citizenctl index d8805ef..e8d3e7d 100644 --- a/integrations/omarchy/citizenctl +++ b/integrations/omarchy/citizenctl @@ -2,7 +2,7 @@ set -u PLUGIN_ID="local.omarchy-citizen" -PLUGIN_VERSION="1.1.2" +PLUGIN_VERSION="1.1.3" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" diff --git a/integrations/omarchy/manifest.json b/integrations/omarchy/manifest.json index f1a1c8d..b575eb5 100644 --- a/integrations/omarchy/manifest.json +++ b/integrations/omarchy/manifest.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "id": "local.omarchy-citizen", "name": "Citizen Launcher · Omarchy", - "version": "1.1.2", + "version": "1.1.3", "author": "Community prototype", "license": "MIT", "description": "Optional Omarchy bar integration for the distro-neutral Citizen Launcher.", diff --git a/packaging/citizen-launcher.spec b/packaging/citizen-launcher.spec index ded0f8b..dc27f77 100644 --- a/packaging/citizen-launcher.spec +++ b/packaging/citizen-launcher.spec @@ -104,7 +104,7 @@ fi %config(noreplace) %{_sysconfdir}/citizen-launcher/release-repo %changelog -* Tue Sep 01 2026 Citizen Launcher Project - 1.1.2-1 -- Gitea Actions checkout permission hardening +* Tue Sep 01 2026 Citizen Launcher Project - 1.1.3-1 +- Low-disk Gitea Fedora RPM build and release hardening * Tue Sep 01 2026 Citizen Launcher Project - 1.1.1-1 - Multi-distribution native package support diff --git a/packaging/metainfo/io.github.citizenlauncher.CitizenLauncher.metainfo.xml b/packaging/metainfo/io.github.citizenlauncher.CitizenLauncher.metainfo.xml index b9417ae..ed6420b 100644 --- a/packaging/metainfo/io.github.citizenlauncher.CitizenLauncher.metainfo.xml +++ b/packaging/metainfo/io.github.citizenlauncher.CitizenLauncher.metainfo.xml @@ -13,6 +13,6 @@ https://github.com/sendnwv/omarchy-sc Game citizen-launcher - + diff --git a/packaging/rpm/citizen-launcher.spec b/packaging/rpm/citizen-launcher.spec index ded0f8b..dc27f77 100644 --- a/packaging/rpm/citizen-launcher.spec +++ b/packaging/rpm/citizen-launcher.spec @@ -104,7 +104,7 @@ fi %config(noreplace) %{_sysconfdir}/citizen-launcher/release-repo %changelog -* Tue Sep 01 2026 Citizen Launcher Project - 1.1.2-1 -- Gitea Actions checkout permission hardening +* Tue Sep 01 2026 Citizen Launcher Project - 1.1.3-1 +- Low-disk Gitea Fedora RPM build and release hardening * Tue Sep 01 2026 Citizen Launcher Project - 1.1.1-1 - Multi-distribution native package support diff --git a/scripts/gitea-release.sh b/scripts/gitea-release.sh index df3eac4..65d01fd 100644 --- a/scripts/gitea-release.sh +++ b/scripts/gitea-release.sh @@ -8,8 +8,41 @@ set -euo pipefail api_base="${GITEA_API_URL%/}/repos/${GITEA_REPOSITORY}" auth=( -H "Authorization: token ${GITEA_TOKEN}" ) +select_json_backend() { + case "${CITIZEN_JSON_BACKEND:-auto}" in + python|python3) + command -v python3 >/dev/null 2>&1 || { echo "python3 is required by CITIZEN_JSON_BACKEND" >&2; exit 2; } + printf '%s\n' python + ;; + jq) + command -v jq >/dev/null 2>&1 || { echo "jq is required by CITIZEN_JSON_BACKEND" >&2; exit 2; } + printf '%s\n' jq + ;; + auto|"") + if command -v python3 >/dev/null 2>&1; then + printf '%s\n' python + elif command -v jq >/dev/null 2>&1; then + printf '%s\n' jq + else + echo "Gitea release helper needs python3 or jq" >&2 + exit 2 + fi + ;; + *) + echo "Unknown CITIZEN_JSON_BACKEND=${CITIZEN_JSON_BACKEND}" >&2 + exit 2 + ;; + esac +} + +JSON_BACKEND="$(select_json_backend)" + urlencode() { - python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1" + if [[ "$JSON_BACKEND" == python ]]; then + python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$1" + else + jq -rn --arg value "$1" '$value | @uri' + fi } release_json() { @@ -19,8 +52,10 @@ release_json() { } release_payload() { - local tag="$1" title="$2" body_file="$3" target="${4:-}" - python3 - "$tag" "$title" "$body_file" "$target" <<'PY' + local tag="$1" title="$2" body_file="$3" target="${4:-}" body="" + [[ -f "$body_file" ]] && body="$(cat "$body_file")" + if [[ "$JSON_BACKEND" == python ]]; then + python3 - "$tag" "$title" "$body_file" "$target" <<'PY' import json, pathlib, sys body_path = pathlib.Path(sys.argv[3]) body = body_path.read_text(encoding='utf-8') if body_path.exists() else '' @@ -33,6 +68,55 @@ print(json.dumps({ 'target_commitish': sys.argv[4], })) PY + else + jq -cn \ + --arg tag "$tag" \ + --arg title "$title" \ + --arg body "$body" \ + --arg target "$target" \ + '{tag_name:$tag,name:$title,body:$body,draft:false,prerelease:false,target_commitish:$target}' + fi +} + +json_release_id() { + if [[ "$JSON_BACKEND" == python ]]; then + python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' + else + jq -er '.id' + fi +} + +json_matching_asset_ids() { + local name="$1" + if [[ "$JSON_BACKEND" == python ]]; then + python3 -c 'import json,sys; name=sys.argv[1]; [print(a["id"]) for a in json.load(sys.stdin).get("assets",[]) if a.get("name")==name]' "$name" + else + jq -er --arg name "$name" '.assets[]? | select(.name == $name) | .id' 2>/dev/null || true + fi +} + +json_downloadable_assets_tsv() { + if [[ "$JSON_BACKEND" == python ]]; then + python3 -c ' +import json, sys +release = json.load(sys.stdin) +for asset in release.get("assets", []): + name = asset.get("name", "") + url = asset.get("browser_download_url", "") + if not name or not url or name == "SHA256SUMS.txt": + continue + if name.endswith(".deb") or name.endswith(".rpm") or name.endswith(".pkg.tar.zst") or name.endswith("-linux-amd64.tar.gz"): + print(f"{name}\t{url}") +' + else + jq -r ' + .assets[]? + | select(.name != null and .browser_download_url != null and .name != "SHA256SUMS.txt") + | select(.name | endswith(".deb") or endswith(".rpm") or endswith(".pkg.tar.zst") or endswith("-linux-amd64.tar.gz")) + | [.name, .browser_download_url] + | @tsv + ' + fi } ensure_release() { @@ -40,7 +124,7 @@ ensure_release() { local payload current id payload="$(release_payload "$tag" "$title" "$body_file" "$target")" if current="$(release_json "$tag" 2>/dev/null)"; then - id="$(printf '%s' "$current" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + id="$(printf '%s' "$current" | json_release_id)" curl -fsS "${auth[@]}" -H 'Content-Type: application/json' \ -X PATCH -d "$payload" "${api_base}/releases/${id}" >/dev/null echo "Updated Gitea release ${tag}" @@ -56,10 +140,10 @@ upload_asset() { [[ -s "$file" ]] || { echo "Asset missing/empty: $file" >&2; exit 1; } local rel id name enc existing_ids rel="$(release_json "$tag")" - id="$(printf '%s' "$rel" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')" + id="$(printf '%s' "$rel" | json_release_id)" name="$(basename "$file")" enc="$(urlencode "$name")" - existing_ids="$(printf '%s' "$rel" | python3 -c 'import json,sys; name=sys.argv[1]; [print(a["id"]) for a in json.load(sys.stdin).get("assets",[]) if a.get("name")==name]' "$name")" + existing_ids="$(printf '%s' "$rel" | json_matching_asset_ids "$name")" if [[ -n "$existing_ids" ]]; then while IFS= read -r aid; do [[ -n "$aid" ]] || continue @@ -75,30 +159,13 @@ upload_asset() { download_assets() { local tag="$1" dir="$2" mkdir -p "$dir" - local rel + local rel name url rel="$(release_json "$tag")" - printf '%s' "$rel" | python3 -c ' -import json, os, pathlib, sys, urllib.request -out = pathlib.Path(sys.argv[1]) -release = json.load(sys.stdin) -for asset in release.get("assets", []): - name = asset.get("name", "") - if not name or name == "SHA256SUMS.txt": - continue - if not (name.endswith(".deb") or name.endswith(".rpm") or name.endswith(".pkg.tar.zst") or name.endswith("-linux-amd64.tar.gz")): - continue - req = urllib.request.Request(asset["browser_download_url"]) - token = os.environ.get("GITEA_TOKEN", "") - if token: - req.add_header("Authorization", f"token {token}") - with urllib.request.urlopen(req, timeout=120) as r, open(out / name, "wb") as f: - while True: - block = r.read(1024 * 1024) - if not block: - break - f.write(block) - print(name) -' "$dir" + while IFS=$'\t' read -r name url; do + [[ -n "$name" && -n "$url" ]] || continue + curl -fsSL "${auth[@]}" "$url" -o "$dir/$name" + echo "$name" + done < <(printf '%s' "$rel" | json_downloadable_assets_tsv) } case "${1:-}" in diff --git a/tests/full-verify.sh b/tests/full-verify.sh index 3fb7406..d7a755e 100644 --- a/tests/full-verify.sh +++ b/tests/full-verify.sh @@ -3,6 +3,7 @@ set -euo pipefail ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" bash "$ROOT/tests/verify.sh" bash "$ROOT/tests/mode-independence.sh" +bash "$ROOT/tests/rpm-ci-footprint.sh" (cd "$ROOT/backend" && go test -race ./...) bash "$ROOT/backend/integration-test.sh" bash "$ROOT/tests/gitea-release-helper.sh" diff --git a/tests/gitea-release-helper.sh b/tests/gitea-release-helper.sh index 4edf9c2..36de743 100644 --- a/tests/gitea-release-helper.sh +++ b/tests/gitea-release-helper.sh @@ -25,7 +25,7 @@ class H(http.server.BaseHTTPRequestHandler): return r def do_GET(self): path=urllib.parse.urlparse(self.path).path - if path.endswith('/releases/tags/v1.1.2'): + if path.endswith('/releases/tags/v1.1.3'): if release is None: self.send_error(404); return self.sendj(200,self.rel()); return if path.startswith('/download/'): @@ -78,12 +78,23 @@ export GITEA_API_URL="http://127.0.0.1:${PORT}/api/v1" export GITEA_REPOSITORY="owner/repo" export GITEA_TOKEN="test-token" -echo first > "$TMP/citizen-launcher_1.1.2_amd64.deb" -bash "$ROOT/scripts/gitea-release.sh" ensure v1.1.2 'Citizen Launcher 1.1.2' "$ROOT/RELEASE_NOTES.md" deadbeef -bash "$ROOT/scripts/gitea-release.sh" ensure v1.1.2 'Citizen Launcher 1.1.2' "$ROOT/RELEASE_NOTES.md" deadbeef -bash "$ROOT/scripts/gitea-release.sh" upload v1.1.2 "$TMP/citizen-launcher_1.1.2_amd64.deb" -echo replacement > "$TMP/citizen-launcher_1.1.2_amd64.deb" -bash "$ROOT/scripts/gitea-release.sh" upload v1.1.2 "$TMP/citizen-launcher_1.1.2_amd64.deb" -bash "$ROOT/scripts/gitea-release.sh" download-assets v1.1.2 "$TMP/download" -cmp "$TMP/citizen-launcher_1.1.2_amd64.deb" "$TMP/download/citizen-launcher_1.1.2_amd64.deb" -echo 'Gitea release helper verification: OK' +echo first > "$TMP/citizen-launcher_1.1.3_amd64.deb" +bash "$ROOT/scripts/gitea-release.sh" ensure v1.1.3 'Citizen Launcher 1.1.3' "$ROOT/RELEASE_NOTES.md" deadbeef +bash "$ROOT/scripts/gitea-release.sh" ensure v1.1.3 'Citizen Launcher 1.1.3' "$ROOT/RELEASE_NOTES.md" deadbeef +bash "$ROOT/scripts/gitea-release.sh" upload v1.1.3 "$TMP/citizen-launcher_1.1.3_amd64.deb" +echo replacement > "$TMP/citizen-launcher_1.1.3_amd64.deb" +bash "$ROOT/scripts/gitea-release.sh" upload v1.1.3 "$TMP/citizen-launcher_1.1.3_amd64.deb" +bash "$ROOT/scripts/gitea-release.sh" download-assets v1.1.3 "$TMP/download" +cmp "$TMP/citizen-launcher_1.1.3_amd64.deb" "$TMP/download/citizen-launcher_1.1.3_amd64.deb" + +# Fedora RPM jobs intentionally avoid Python to keep the container footprint low. +# Exercise the jq backend against the same mock Gitea API. +command -v jq >/dev/null 2>&1 || { echo 'jq required for Gitea helper jq test' >&2; exit 1; } +export CITIZEN_JSON_BACKEND=jq +echo jq-backend > "$TMP/citizen-launcher-1.1.3-1.linux.x86_64.rpm" +bash "$ROOT/scripts/gitea-release.sh" ensure v1.1.3 'Citizen Launcher 1.1.3' "$ROOT/RELEASE_NOTES.md" deadbeef +bash "$ROOT/scripts/gitea-release.sh" upload v1.1.3 "$TMP/citizen-launcher-1.1.3-1.linux.x86_64.rpm" +bash "$ROOT/scripts/gitea-release.sh" download-assets v1.1.3 "$TMP/download-jq" +cmp "$TMP/citizen-launcher-1.1.3-1.linux.x86_64.rpm" "$TMP/download-jq/citizen-launcher-1.1.3-1.linux.x86_64.rpm" + +echo 'Gitea release helper verification: OK (python + jq)' diff --git a/tests/mode-independence.sh b/tests/mode-independence.sh index eb9d290..8848f32 100644 --- a/tests/mode-independence.sh +++ b/tests/mode-independence.sh @@ -9,7 +9,7 @@ root = Path(sys.argv[1]) check = [ root/'build.sh', root/'install-omarchy.sh', root/'INSTALLIEREN.sh', root/'tests/full-verify.sh', root/'tests/verify.sh', root/'tests/package-verify.sh', - root/'tests/gitea-release-helper.sh', root/'packaging/build-all.sh', + root/'tests/gitea-release-helper.sh', root/'tests/rpm-ci-footprint.sh', root/'packaging/build-all.sh', root/'.gitea/workflows/ci.yml', root/'.gitea/workflows/release.yml', ] for p in check: diff --git a/tests/rpm-ci-footprint.sh b/tests/rpm-ci-footprint.sh new file mode 100644 index 0000000..08a179b --- /dev/null +++ b/tests/rpm-ci-footprint.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +python3 - "$ROOT" <<'PY' +from pathlib import Path +import re, sys, yaml +root = Path(sys.argv[1]) +checks = [ + (root/'.gitea/workflows/ci.yml', 'rpm-package'), + (root/'.gitea/workflows/release.yml', 'rpm'), +] +for path, job_name in checks: + data = yaml.safe_load(path.read_text()) + job = data['jobs'][job_name] + runs = '\n'.join(str(step.get('run','')) for step in job.get('steps',[])) + flat = re.sub(r'\\\n\s*', ' ', runs) + flat = re.sub(r'\s+', ' ', flat) + required = [ + 'system_cachedir=', + 'install_weak_deps=False', + 'tsflags=nodocs', + 'install git-core', + 'install golang-bin', + 'remove golang-bin', + 'install rpm-build', + ] + for needle in required: + if needle not in flat: + raise SystemExit(f'{path.name}:{job_name} missing low-disk guard: {needle}') + forbidden = ['systemd-rpm-macros', 'install git golang', 'install golang rpm-build', 'install python3'] + for needle in forbidden: + if needle in flat: + raise SystemExit(f'{path.name}:{job_name} reintroduced heavy dependency set: {needle}') + if flat.index('install golang-bin') > flat.index('remove golang-bin') or flat.index('remove golang-bin') > flat.index('install rpm-build'): + raise SystemExit(f'{path.name}:{job_name} no longer stages Go before rpmbuild') +print('RPM CI low-disk dependency policy: OK') +PY diff --git a/tests/verify.sh b/tests/verify.sh index f7f76de..93a299d 100644 --- a/tests/verify.sh +++ b/tests/verify.sh @@ -5,7 +5,7 @@ for f in \ "$ROOT/install.sh" "$ROOT/uninstall.sh" "$ROOT/install-omarchy.sh" "$ROOT/INSTALLIEREN.sh" \ "$ROOT/build.sh" "$ROOT/packaging/build-deb.sh" "$ROOT/packaging/build-rpm.sh" \ "$ROOT/packaging/build-arch.sh" "$ROOT/packaging/build-tarball.sh" "$ROOT/packaging/build-all.sh" \ - "$ROOT/scripts/gitea-release.sh" "$ROOT/tests/gitea-release-helper.sh" "$ROOT/tests/mode-independence.sh"; do + "$ROOT/scripts/gitea-release.sh" "$ROOT/tests/gitea-release-helper.sh" "$ROOT/tests/mode-independence.sh" "$ROOT/tests/rpm-ci-footprint.sh"; do bash -n "$f" done (cd "$ROOT/backend" && gofmt -w cmd/citizen-launcher && go test ./... && go vet ./...)