diff --git a/.forgejo/workflows/rpm.yml b/.forgejo/workflows/rpm.yml new file mode 100644 index 0000000..479665c --- /dev/null +++ b/.forgejo/workflows/rpm.yml @@ -0,0 +1,52 @@ +name: RPM + +# Builds dnf-package-store as an RPM in a Fedora container and uploads it to the +# forge's `quine` RPM group, +# https://forge.quinefoundation.com/api/packages/quine-global-labs/rpm/quine, +# which quine-linux's image installs it from. +# +# Secret: REGISTRY_TOKEN, a Forgejo token with package write access. +# Variable (optional): REGISTRY_USER, the token's owner (default: the actor). +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: rpm-${{ github.ref }} + cancel-in-progress: true + +jobs: + rpm: + runs-on: runner-x86-linux + steps: + - uses: https://forge.quinefoundation.com/quine-global-labs/mirror-actions-checkout@v4 + with: + fetch-depth: 0 + + - name: Build + # /var/cache/quine-rpm-build outlives the job (host-mode runner): the Flutter SDK and pub packages. + run: | + mkdir -p out /var/cache/quine-rpm-build + podman run --rm -v "$PWD":/src -v /var/cache/quine-rpm-build:/cache -v "$PWD/out":/out \ + registry.fedoraproject.org/fedora:44 bash /src/packaging/build-flutter-rpm.sh dnf-package-store + ls -l out + + - name: Upload to the quine RPM group + env: + REGISTRY_USER: ${{ vars.REGISTRY_USER || github.actor }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + [ -n "$REGISTRY_TOKEN" ] || { echo "::error::no REGISTRY_TOKEN secret (org or repo)"; exit 1; } + netrc=$(mktemp); trap 'rm -f "$netrc"' EXIT; chmod 600 "$netrc" + printf 'machine %s login %s password %s\n' "${GITHUB_SERVER_URL#https://}" "$REGISTRY_USER" "$REGISTRY_TOKEN" >"$netrc" + for f in out/*.rpm; do + code=$(curl -sS -o /dev/null -w '%{http_code}' --netrc-file "$netrc" --upload-file "$f" \ + "$GITHUB_SERVER_URL/api/packages/quine-global-labs/rpm/quine/upload") + echo "$code ${f#out/}" + [ "$code" = 201 ] || [ "$code" = 409 ] + done + + - name: Clean up + if: always() + run: rm -rf out build diff --git a/lib/main.dart b/lib/main.dart index 44e99f5..e92cac4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -85,9 +85,14 @@ class _SearchPageState extends State { /// Package history lives outside this app now (see `pkg-history` at the /// repo root) — it's a plain script reading rpm-ostree deployments, with - /// no Flutter/GTK build required. This just opens it in a terminal. + /// no Flutter/GTK build required. This just opens it in a terminal. It's + /// next to the executable when installed (the RPM puts both in + /// /usr/libexec/dnf-package-store/), or in the working directory when run + /// from a checkout (run.sh). Future _openHistory() async { - final script = File('pkg-history').absolute.path; + final installed = '${File(Platform.resolvedExecutable).parent.path}/pkg-history'; + final script = + await File(installed).exists() ? installed : File('pkg-history').absolute.path; if (!await File(script).exists()) { if (mounted) { ScaffoldMessenger.of(context) diff --git a/packaging/build-flutter-rpm.sh b/packaging/build-flutter-rpm.sh new file mode 100755 index 0000000..d22f5e9 --- /dev/null +++ b/packaging/build-flutter-rpm.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Builds the Flutter Linux release bundle and packages it as an RPM +# (packaging/$NAME.spec). Runs in a Fedora container (.forgejo/workflows/rpm.yml) +# with this repo at /src, a cache that outlives the job at /cache (the +# Flutter SDK and pub packages), and the RPMs going to /out. +# +# podman run --rm -v "$PWD":/src -v /var/cache/quine-rpm-build:/cache \ +# -v "$PWD/out":/out registry.fedoraproject.org/fedora:44 \ +# bash /src/packaging/build-flutter-rpm.sh dnf-package-store +set -euo pipefail +NAME=$1 + +# Flutter's stable release, pinned (storage.googleapis.com/flutter_infra_release/releases/releases_linux.json). +FLUTTER_VERSION=3.47.9 +FLUTTER_SHA256=25ed8c3c29d51a7353f2a944310b478af8214920639524b19c50a19e6c044c5a + +dnf install -y --setopt=install_weak_deps=False \ + clang cmake ninja-build pkgconf-pkg-config gtk3-devel xz-devel libstdc++-devel \ + git xz which unzip rpm-build desktop-file-utils + +sdk=/cache/flutter-$FLUTTER_VERSION +if [ ! -x "$sdk/bin/flutter" ]; then + curl -fL --retry 3 -o /cache/flutter.tar.xz \ + "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION-stable.tar.xz" + echo "$FLUTTER_SHA256 /cache/flutter.tar.xz" | sha256sum -c + rm -rf "$sdk.tmp" && mkdir -p "$sdk.tmp" + tar -xJf /cache/flutter.tar.xz -C "$sdk.tmp" --strip-components=1 + mv "$sdk.tmp" "$sdk" && rm /cache/flutter.tar.xz +fi +# The SDK and this checkout belong to other users than the container's root. +git config --global --add safe.directory '*' +export PUB_CACHE=/cache/pub PATH="$sdk/bin:$PATH" +flutter --disable-analytics >/dev/null 2>&1 || true + +cd /src +flutter build linux --release + +version=$(sed -n 's/^version: *\([0-9][0-9.]*\).*/\1/p' pubspec.yaml) +release=$(date -u +%Y%m%d%H%M).git$(git rev-parse --short=7 HEAD) +rpmbuild -bb "packaging/$NAME.spec" \ + --define "_topdir /tmp/rpmbuild" \ + --define "version_ $version" --define "release_ $release" \ + --define "bundle /src/build/linux/x64/release/bundle" --define "srcdir /src" +cp -v /tmp/rpmbuild/RPMS/*/*.rpm /out/ diff --git a/packaging/dnf-package-store.spec b/packaging/dnf-package-store.spec new file mode 100644 index 0000000..adb8b71 --- /dev/null +++ b/packaging/dnf-package-store.spec @@ -0,0 +1,54 @@ +# Built by packaging/build-flutter-rpm.sh from Flutter's release bundle; not +# a from-source spec (no %prep/%build). +%global debug_package %{nil} +# The bundle's own libraries (libflutter_linux_gtk.so, plugins) are private, +# and the executable links them from its own lib/, so no automatic +# dependencies from the bundle: the Requires below are what it needs from the +# system (the image build checks every library resolves). +%global __provides_exclude_from ^%{_libexecdir}/%{name}/.*$ +%global __requires_exclude_from ^%{_libexecdir}/%{name}/.*$ + +Name: dnf-package-store +Version: %{version_} +Release: %{release_}%{?dist} +Summary: Browse DNF packages and try them out with transient installs +License: LicenseRef-Not-Specified +URL: https://forge.quinefoundation.com/quine-global-labs/dnf-package-store +ExclusiveArch: x86_64 + +Requires: gtk3 +Requires: polkit +Requires: dnf5 +Requires: python3 +Requires: rpm-ostree + +%description +A desktop app for browsing the package catalog of DNF-based systems and +installing packages transiently (dnf install --transient), so test installs +disappear on reboot. pkg-history lists the system's rpm-ostree deployments. + +%install +mkdir -p %{buildroot}%{_libexecdir}/%{name} %{buildroot}%{_bindir} +cp -a %{bundle}/. %{buildroot}%{_libexecdir}/%{name}/ +# Next to the executable, where the app looks for pkg-history (lib/main.dart). +install -m 0755 %{srcdir}/pkg-history %{srcdir}/pkg-apply %{buildroot}%{_libexecdir}/%{name}/ +ln -s ../libexec/%{name}/dnf_package_store %{buildroot}%{_bindir}/%{name} +ln -s ../libexec/%{name}/pkg-history %{buildroot}%{_bindir}/pkg-history +mkdir -p %{buildroot}%{_datadir}/applications +cat > %{buildroot}%{_datadir}/applications/com.ironmagma.dnf_package_store.desktop <<'DESKTOP' +[Desktop Entry] +Type=Application +Name=DNF Package Store +Comment=Browse packages and try them out without keeping them +Exec=dnf-package-store +Icon=system-software-install +Categories=System;PackageManager; +StartupWMClass=com.ironmagma.dnf_package_store +DESKTOP +desktop-file-validate %{buildroot}%{_datadir}/applications/com.ironmagma.dnf_package_store.desktop + +%files +%{_libexecdir}/%{name} +%{_bindir}/%{name} +%{_bindir}/pkg-history +%{_datadir}/applications/com.ironmagma.dnf_package_store.desktop