diff --git a/.github/workflows/copr.yml b/.github/workflows/copr.yml new file mode 100644 index 0000000..997903e --- /dev/null +++ b/.github/workflows/copr.yml @@ -0,0 +1,66 @@ +name: COPR + +on: + # Called by release.yml once the GitHub release and its assets exist: COPR + # downloads the tag archive and the vendored crates from the release. + workflow_call: + inputs: + version: + required: true + type: string + secrets: + COPR_CONFIG: + required: true + workflow_dispatch: + inputs: + version: + description: 'Release to build, such as 3.5.0. It must already have its vendor tarball.' + required: true + type: string + +permissions: + contents: read + +concurrency: + group: copr + cancel-in-progress: false + +jobs: + build: + name: Build in COPR + runs-on: ubuntu-24.04 + container: fedora:latest + env: + VERSION: ${{ inputs.version }} + COPR_PROJECT: narrrl/proton-drive-linux + + steps: + - name: Install packaging tools + run: dnf -y install copr-cli rpm-build rpmdevtools + + - name: Checkout repository + uses: actions/checkout@v7 + + - name: Configure COPR access + env: + COPR_CONFIG: ${{ secrets.COPR_CONFIG }} + run: | + install -d -m700 ~/.config + printf '%s\n' "$COPR_CONFIG" > ~/.config/copr + chmod 600 ~/.config/copr + + # COPR rebuilds the source package without our --define, so the version + # has to be written into the spec itself. + - name: Build the source package + run: | + version="${VERSION#v}" + spec=packaging/proton-drive-linux.spec + sed -i -E "s/^(%\{!\?version: %global version )[^}]*\}/\1${version}}/" "$spec" + grep -qF "%global version ${version}}" "$spec" + mkdir -p rpmbuild/SOURCES + spectool -g -C rpmbuild/SOURCES "$spec" + rpmbuild -bs --define "_topdir $PWD/rpmbuild" "$spec" + + # Waits for the build, so a failed COPR build fails this job. + - name: Submit to COPR + run: copr-cli build "$COPR_PROJECT" rpmbuild/SRPMS/*.src.rpm diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b6d542..cb6e9b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,8 +52,10 @@ jobs: tag_version="${GITHUB_REF_NAME#v}" manifest_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n1)" pkgbuild_version="$(sed -n 's/^pkgver=//p' packaging/PKGBUILD | head -n1)" + spec_version="$(sed -n 's/^%{!?version: %global version \([^}]*\)}/\1/p' packaging/proton-drive-linux.spec)" test "$tag_version" = "$manifest_version" test "$tag_version" = "$pkgbuild_version" + test "$tag_version" = "$spec_version" - name: Release quality gates run: | @@ -129,6 +131,12 @@ jobs: # Create tarball tar -czf proton-drive-linux-${{ env.VERSION }}-x86_64.tar.gz -C dist/binaries . + # Every crate the build needs, so COPR can build the spec without network. + - name: Package vendored crates (.tar.xz) + run: | + cargo vendor --locked dist/vendor > /dev/null + tar -cJf proton-drive-linux-vendor-${{ env.VERSION }}.tar.xz -C dist vendor + - name: Package Debian package (.deb) run: | mkdir -p deb/DEBIAN @@ -230,6 +238,7 @@ jobs: path: | *.deb *.tar.gz + *.tar.xz *.rpm if-no-files-found: error @@ -297,6 +306,7 @@ jobs: files: | *.deb *.tar.gz + *.tar.xz *.rpm draft: false prerelease: false @@ -312,3 +322,13 @@ jobs: version: ${{ github.ref_name }} secrets: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + + copr: + name: Build COPR packages + needs: publish + if: startsWith(github.ref, 'refs/tags/') + uses: ./.github/workflows/copr.yml + with: + version: ${{ github.ref_name }} + secrets: + COPR_CONFIG: ${{ secrets.COPR_CONFIG }} diff --git a/README.md b/README.md index 04a592c..af63543 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,15 @@ scriptable CLI. | Distribution | Package | |---|---| | Debian, Ubuntu 24.04 or newer | `.deb` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) | -| Fedora 44 or newer | `.rpm` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) | +| Fedora 44 or newer | [`narrrl/proton-drive-linux`](https://copr.fedorainfracloud.org/coprs/narrrl/proton-drive-linux/) in COPR, or the `.rpm` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) | | Arch Linux | [`proton-drive-for-linux`](https://aur.archlinux.org/packages/proton-drive-for-linux) (also `-bin` and `-git`) in the AUR | | NixOS | The flake in this repository: see [Install → NixOS](docs/INSTALL.md#nixos-flake) | | Anything else | [Build from source](docs/INSTALL.md#build-from-source) | ```bash sudo apt install ./proton-drive-linux_*.deb # Debian, Ubuntu -sudo dnf install ./proton-drive-linux-*.rpm # Fedora +sudo dnf copr enable narrrl/proton-drive-linux # Fedora +sudo dnf install proton-drive-linux paru -S proton-drive-for-linux # Arch ``` diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5ddcb5c..b212283 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -11,6 +11,11 @@ scratch (user data in `staging/` and `recovery/` is never touched by this). ## [Unreleased] +### Added +- **A COPR repository for Fedora**, so `dnf upgrade` brings each new release: + `sudo dnf copr enable narrrl/proton-drive-linux`. Every release also ships the vendored crates + as `proton-drive-linux-vendor-.tar.xz`, which the COPR build uses offline (#42). + ## [3.5.0] — 2026-10-16 Schema: **44**. Queued changes get an index by kind and name, and each synced file remembers the diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 61a1244..9c4396f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -150,22 +150,25 @@ to the GitHub wiki, so keep their file names. Releases are cut from `main` by pushing a `v*` tag. `.github/workflows/release.yml` then: -1. checks that the tag, the workspace version in `Cargo.toml` and `pkgver` in - `packaging/PKGBUILD` agree; +1. checks that the tag, the workspace version in `Cargo.toml`, `pkgver` in + `packaging/PKGBUILD` and the default version in `packaging/proton-drive-linux.spec` agree; 2. runs the quality gates; -3. builds the release binaries and packages a `.tar.gz`, a `.deb` and a GPG-signed `.rpm`, and - builds the Nix flake at the tag, which is what a flake input pinned to `vX.Y.Z` gets, and - pushes it to the `proton-drive-linux` Cachix cache (secret `CACHIX_AUTH_TOKEN`); +3. builds the release binaries and packages a `.tar.gz`, a `.deb`, a GPG-signed `.rpm` and a + `.tar.xz` of the vendored crates, and builds the Nix flake at the tag, which is what a flake + input pinned to `vX.Y.Z` gets, and pushes it to the `proton-drive-linux` Cachix cache (secret + `CACHIX_AUTH_TOKEN`); 4. publishes them as a GitHub release, with the version's section of `docs/CHANGELOG.md` as the notes; 5. calls `.github/workflows/aur.yml`, which updates and pushes the AUR recipes and commits them - back to `main`. + back to `main`; +6. calls `.github/workflows/copr.yml`, which builds the release in the + [COPR repository](../packaging/copr/README.md) (secret `COPR_CONFIG`). To prepare a release: ```bash -# 1. Bump the version in Cargo.toml, packaging/PKGBUILD, po/pdfs.pot and the pinned flake -# URL in docs/INSTALL.md, then refresh the lock file. +# 1. Bump the version in Cargo.toml, packaging/PKGBUILD, packaging/proton-drive-linux.spec, +# po/pdfs.pot and the pinned flake URL in docs/INSTALL.md, then refresh the lock file. cargo update --workspace # 2. Move the [Unreleased] notes in docs/CHANGELOG.md under the new version. # 3. Commit, tag and push. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 8ec4eb7..d6da210 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -59,6 +59,17 @@ sudo apt install ./proton-drive-linux_*_amd64.deb ### Fedora (44 or newer) +The [COPR repository](https://copr.fedorainfracloud.org/coprs/narrrl/proton-drive-linux/) builds +every release, so `dnf upgrade` keeps the client up to date: + +```bash +sudo dnf copr enable narrrl/proton-drive-linux +sudo dnf install proton-drive-linux +``` + +The `.rpm` from the release page installs the same files, but you have to download each new +version yourself: + ```bash sudo dnf install ./proton-drive-linux-*.x86_64.rpm ``` diff --git a/docs/SECURITY_MODEL.md b/docs/SECURITY_MODEL.md index 89be444..af4896c 100644 --- a/docs/SECURITY_MODEL.md +++ b/docs/SECURITY_MODEL.md @@ -75,7 +75,9 @@ operations, and the client only writes them to its local log. ## Packages -- The `.rpm` is signed with GPG. +- The `.rpm` on the release page is signed with GPG. +- The COPR packages are built by Fedora's COPR service from the tagged source and the vendored + crates of the release, and are signed with the COPR project's own key. - The `.deb` and the `.tar.gz` are built by GitHub Actions from the tagged source, and are not signed. - The AUR packages build from the tagged source or from `main`, or install the release binaries diff --git a/packaging/copr/README.md b/packaging/copr/README.md new file mode 100644 index 0000000..fa01b60 --- /dev/null +++ b/packaging/copr/README.md @@ -0,0 +1,43 @@ +# COPR repository + +Fedora users install from the COPR project +[`narrrl/proton-drive-linux`](https://copr.fedorainfracloud.org/coprs/narrrl/proton-drive-linux/), +so `dnf upgrade` brings every release. COPR builds `packaging/proton-drive-linux.spec` from two +release assets: + +| Source | What it is | +| --- | --- | +| `Source0` | GitHub's archive of the `v` tag | +| `Source1` | `proton-drive-linux-vendor-.tar.xz`: every crate from `Cargo.lock`, made by `cargo vendor` in `release.yml` | + +With the vendored crates the build needs no network, so the COPR project keeps network access +off. + +## Releasing a new version + +`.github/workflows/copr.yml` does this automatically: + +- **On a release tag**, `release.yml` calls it after the GitHub release is published. It writes + the version into the spec, downloads both sources with `spectool`, builds a source package + and submits it with `copr-cli build`. The job waits for COPR, so a failed build fails the + release workflow. +- **By hand**, run the workflow from the Actions tab with a version. The release must already + have its vendor tarball. + +It needs the `COPR_CONFIG` repository secret: the whole content of `~/.config/copr` from +. **The token expires after 180 days.** Renew it on that +page and replace the secret, or the next release's COPR job fails. + +To do the same by hand: + +```bash +spectool -g -C ~/rpmbuild/SOURCES packaging/proton-drive-linux.spec +rpmbuild -bs packaging/proton-drive-linux.spec +copr-cli build narrrl/proton-drive-linux ~/rpmbuild/SRPMS/proton-drive-linux-*.src.rpm +``` + +## Project settings + +- Chroots: `fedora-44-x86_64`, `fedora-45-x86_64` and `fedora-rawhide-x86_64`. The spec is + `ExclusiveArch: x86_64` and needs `rust >= 1.96`. +- Network access in builds: off. diff --git a/packaging/proton-drive-linux.spec b/packaging/proton-drive-linux.spec index 84e52c5..3734f04 100644 --- a/packaging/proton-drive-linux.spec +++ b/packaging/proton-drive-linux.spec @@ -1,13 +1,19 @@ -# Local in-tree RPM (mirrors packaging/PKGBUILD). -# From the repository root: -# rpmbuild -bb packaging/proton-drive-linux.spec --define "git_dir $PWD" ... -# %build needs network so cargo can fetch crates.io (same trade-off as the Arch PKGBUILD). +# Two ways to build this spec: +# +# - From a checkout (local builds and the release workflow). From the repository root: +# rpmbuild -bb packaging/proton-drive-linux.spec --define "git_dir $PWD" ... +# %build needs network so cargo can fetch crates.io (same trade-off as the Arch PKGBUILD). +# - From the release tarballs (COPR, .github/workflows/copr.yml), when git_dir is not +# defined. Source1 holds every crate, so the build runs offline. %global debug_package %{nil} # Match Arch PKGBUILD `!lto` — LTO has broken some GTK/Rust links in practice. %global _lto_cflags %{nil} -%{!?version: %global version 0.4.0} +%{!?version: %global version 3.5.0} +%global forge https://github.com/narrrl/proton-drive-linux +# Files come from the checkout, or from the unpacked tag archive. +%global src %{?git_dir}%{!?git_dir:.} Name: proton-drive-linux Version: %{version} @@ -16,9 +22,13 @@ Summary: Unofficial Proton Drive client for Linux (FUSE, CLI, GTK4 app + License: MIT URL: https://proton-drive.narl.io ExclusiveArch: x86_64 +%if 0%{!?git_dir:1} +Source0: %{forge}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz +Source1: %{forge}/releases/download/v%{version}/%{name}-vendor-%{version}.tar.xz +%endif BuildRequires: cargo -BuildRequires: rust +BuildRequires: rust >= 1.96 BuildRequires: pkgconf-pkg-config BuildRequires: fuse3-devel BuildRequires: gtk4-devel @@ -57,18 +67,31 @@ block-level caching, a scriptable CLI, and a GTK4/Libadwaita desktop app with system tray and search launcher. Unofficial and not affiliated with Proton AG. %prep -# In-tree build: no Source tarball. Pass --define "git_dir /path/to/checkout". -test -n "%{?git_dir}" || (echo 'Pass --define "git_dir $PWD" from the repo root' >&2; exit 1) +%if 0%{?git_dir:1} +# In-tree build: no Source tarball. test -f %{git_dir}/Cargo.toml cp -a %{git_dir}/LICENSE . +%else +# -a 1 unpacks the vendored crates into vendor/ inside the source tree. +%autosetup -n %{name}-%{version} -a 1 +mkdir -p .cargo +cat > .cargo/config.toml <<'EOF' +[source.crates-io] +replace-with = "vendored-sources" + +[source.vendored-sources] +directory = "vendor" +EOF +%endif + %build # The release workflow has already built target/release and passes --define "prebuilt 1". # rpmbuild exports its own compiler flags, which would make cargo rebuild everything. %if 0%{?prebuilt} test -x %{git_dir}/target/release/pdfs %else -cd %{git_dir} -cargo build --release --locked \ +cd %{src} +cargo build --release %{?git_dir:--locked}%{!?git_dir:--frozen} \ --bin pdfs \ --bin pdfs-tray \ --bin pdfs-app \ @@ -76,21 +99,21 @@ cargo build --release --locked \ %endif %install -rel=%{git_dir}/target/release +rel=%{src}/target/release install -D -m0755 "$rel/pdfs" %{buildroot}%{_bindir}/pdfs install -D -m0755 "$rel/pdfs-tray" %{buildroot}%{_bindir}/pdfs-tray install -D -m0755 "$rel/pdfs-app" %{buildroot}%{_bindir}/pdfs-app install -D -m0755 "$rel/pdfs-prompt" %{buildroot}%{_bindir}/pdfs-prompt -install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux.desktop \ +install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux.desktop \ %{buildroot}%{_datadir}/applications/io.narl.proton-drive-linux.desktop -install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux-tray.desktop \ +install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux-tray.desktop \ %{buildroot}%{_sysconfdir}/xdg/autostart/io.narl.proton-drive-linux-tray.desktop -install -D -m0644 %{git_dir}/packaging/io.narl.proton-drive-linux.svg \ +install -D -m0644 %{src}/packaging/io.narl.proton-drive-linux.svg \ %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/io.narl.proton-drive-linux.svg -install -D -m0644 %{git_dir}/packaging/proton-drive.service \ +install -D -m0644 %{src}/packaging/proton-drive.service \ %{buildroot}/usr/lib/systemd/user/proton-drive.service -%{git_dir}/po/build.sh %{buildroot}%{_datadir}/locale +%{src}/po/build.sh %{buildroot}%{_datadir}/locale # Removing the package does not stop a running daemon: it keeps serving the # mount from the deleted binary, so files deleted under the mount afterwards,