Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/arch-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Arch package

on:
pull_request:
paths:
- scripts/package-arch.py
- .github/workflows/arch-package.yml
- .github/workflows/release.yml
workflow_dispatch:

permissions:
contents: read

jobs:
package:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
- name: Package checksum-verified desktop fixtures for both architectures
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y libarchive-tools
mkdir fixtures tools packages
gh release download v0.3.18 --repo open-cli-collective/Retune \
--pattern 'retune_0.3.18_*.deb' --pattern checksums.txt --dir fixtures
(cd fixtures; sha256sum -c checksums.txt --ignore-missing)
gh release download v2.47.0 --repo goreleaser/nfpm \
--pattern nfpm_2.47.0_Linux_x86_64.tar.gz --pattern checksums.txt --dir tools
(cd tools; sha256sum -c checksums.txt --ignore-missing)
tar -xzf tools/nfpm_2.47.0_Linux_x86_64.tar.gz -C tools nfpm
for arch in amd64 arm64; do
python3 scripts/package-arch.py "fixtures/retune_0.3.18_${arch}.deb" \
0.3.18 "$arch" packages --nfpm "$PWD/tools/nfpm"
done
if python3 scripts/package-arch.py fixtures/retune_0.3.18_amd64.deb \
0.3.19 amd64 packages --nfpm "$PWD/tools/nfpm"; then
echo '::error::mismatched payload version accepted'
exit 1
fi
if python3 scripts/package-arch.py fixtures/retune_0.3.18_amd64.deb \
0.3.18 arm64 packages --nfpm "$PWD/tools/nfpm"; then
echo '::error::mismatched payload architecture accepted'
exit 1
fi
bsdtar -xOf packages/retune_0.3.18_linux_arm64.pkg.tar.zst .PKGINFO | grep -qx 'arch = aarch64'
- name: Verify native desktop payload and dependencies in Arch
run: |
set -euo pipefail
docker run --rm -v "$PWD/packages:/packages:ro" archlinux:base bash -c '
set -euo pipefail
pacman -Syu --noconfirm
pacman -U --noconfirm /packages/retune_0.3.18_linux_amd64.pkg.tar.zst
pacman -Qkk retune
pacman -Qo /usr/bin/retune-desktop
test -x /usr/bin/retune-desktop
test -f /usr/share/applications/Retune.desktop
test -f /usr/share/icons/hicolor/128x128/apps/retune-desktop.png
ldd /usr/bin/retune-desktop > /tmp/retune-libraries
if grep -q "not found" /tmp/retune-libraries; then
cat /tmp/retune-libraries
exit 1
fi
pacman -R --noconfirm retune
test ! -e /usr/bin/retune-desktop
pacman -U --noconfirm /packages/retune_0.3.18_linux_amd64.pkg.tar.zst
pacman -Qkk retune
'
34 changes: 33 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ jobs:
curl \
file \
libasound2-dev \
libarchive-tools \
libayatana-appindicator3-dev \
libdbus-1-dev \
libssl-dev \
Expand Down Expand Up @@ -314,6 +315,33 @@ jobs:
[ "$package_version" = "$VERSION" ] || { echo "::error::Debian package version $package_version does not match $VERSION"; exit 1; }
mv "${files[0]}" "$bundle/retune_${VERSION}_${ARCH}.deb"

- name: Package Arch Linux payload
if: startsWith(matrix.os, 'ubuntu-')
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.prepare.outputs.version }}
ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
tool_dir="$(mktemp -d)"
tool_arch=x86_64
if [ "$ARCH" = arm64 ]; then tool_arch=arm64; fi
gh release download v2.47.0 --repo goreleaser/nfpm \
--pattern "nfpm_2.47.0_Linux_${tool_arch}.tar.gz" --pattern checksums.txt --dir "$tool_dir"
(cd "$tool_dir"; sha256sum -c checksums.txt --ignore-missing)
tar -xzf "$tool_dir/nfpm_2.47.0_Linux_${tool_arch}.tar.gz" -C "$tool_dir" nfpm
python3 scripts/package-arch.py \
"target/release/bundle/deb/retune_${VERSION}_${ARCH}.deb" \
"$VERSION" "$ARCH" target/release/bundle/arch --nfpm "$tool_dir/nfpm"

- name: Upload Arch Linux artifact
if: startsWith(matrix.os, 'ubuntu-')
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: retune-arch-${{ matrix.arch }}
path: target/release/bundle/arch/retune_${{ needs.prepare.outputs.version }}_linux_${{ matrix.arch }}.pkg.tar.zst
if-no-files-found: error

- name: Upload macOS artifact
if: matrix.os == 'macos-15'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
Expand Down Expand Up @@ -428,6 +456,8 @@ jobs:
"Retune-${VERSION}-windows-arm64-setup.exe"
"retune_${VERSION}_amd64.deb"
"retune_${VERSION}_arm64.deb"
"retune_${VERSION}_linux_amd64.pkg.tar.zst"
"retune_${VERSION}_linux_arm64.pkg.tar.zst"
)
for asset in "${expected[@]}"; do
test -f "dist/assets/$asset" || { echo "::error::missing release asset $asset"; exit 1; }
Expand Down Expand Up @@ -457,7 +487,7 @@ jobs:
run: |
set -euo pipefail
cat > dist/release-notes.md <<EOF
Retune $VERSION for macOS Apple Silicon, Windows x64/ARM64, and Ubuntu amd64/arm64.
Retune $VERSION for macOS Apple Silicon, Windows x64/ARM64, and Ubuntu/Arch Linux amd64/arm64.

macOS is stable-signed with the Open CLI Collective self-signed certificate but is not Apple-notarized.
Windows application payloads and NSIS installers are unsigned.
Expand All @@ -468,6 +498,8 @@ jobs:
"dist/assets/Retune-${VERSION}-windows-arm64-setup.exe"
"dist/assets/retune_${VERSION}_amd64.deb"
"dist/assets/retune_${VERSION}_arm64.deb"
"dist/assets/retune_${VERSION}_linux_amd64.pkg.tar.zst"
"dist/assets/retune_${VERSION}_linux_arm64.pkg.tar.zst"
dist/assets/checksums.txt
)
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl SyncRun<'_> {
fn note_deadline(&self, deadline: u64) {
let _ =
self.earliest_cooldown
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
Some(if current == 0 {
deadline
} else {
Expand Down Expand Up @@ -583,7 +583,7 @@ impl<'c, T: Transport> GenreSource<'c, T> {
}
let lookup =
run.artist_lookups
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |lookups| {
.try_update(Ordering::Relaxed, Ordering::Relaxed, |lookups| {
(lookups < ARTIST_LOOKUPS_PER_SYNC).then_some(lookups + 1)
});
let Ok(lookup) = lookup else {
Expand Down
14 changes: 13 additions & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ must match the configured release line and point to a commit reachable from
`main`. It builds and publishes exactly these assets: `Retune-<version>-aarch64.zip`,
`Retune-<version>-windows-x64-setup.exe`,
`Retune-<version>-windows-arm64-setup.exe`,
`retune_<version>_amd64.deb`, `retune_<version>_arm64.deb`, and
`retune_<version>_amd64.deb`, `retune_<version>_arm64.deb`,
`retune_<version>_linux_amd64.pkg.tar.zst`,
`retune_<version>_linux_arm64.pkg.tar.zst`, and
`checksums.txt`. The tag version is passed to Tauri through its `--config`
override so package metadata matches the release tag. The automatic
workflow's `workflow_dispatch` only evaluates the release gate and reports the
Expand Down Expand Up @@ -191,3 +193,13 @@ Do not make CI depend on live Spotify credentials or an audio device.
architectural boundary, invariant, persistence format, or external contract,
update that document in the same commit. Delete completed plans after durable
decisions have been incorporated.

### Arch Linux packaging

`scripts/package-arch.py` packages the validated Tauri Debian payload with
nFPM 2.47.0, preserving its executable, desktop launcher, icons, and file modes.
It checks payload version and architecture before assigning Arch runtime
dependencies. Release checksums cover both Arch packages; the Linux package
repository signs them and updates its pacman index. No second application build
or credentials are introduced. Test the resulting package in a clean Arch
container before shipping changes to this packaging path.
18 changes: 17 additions & 1 deletion docs/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ current floor.
| Windows 10/11 | x64, ARM64 | WebView2 105 (the installer updates older runtimes) |
| Ubuntu 22.04 | amd64, arm64 | Distribution WebKitGTK 4.1 |
| Compatible Debian/Ubuntu systems | amd64, arm64 | WebKitGTK 4.1 |
| Arch Linux / Omarchy | x86_64 | Distribution WebKitGTK 4.1 |
| Compatible Arch Linux ARM systems | aarch64 | Distribution WebKitGTK 4.1 |

Homebrew, Winget, and APT are recommended because they manage updates and verify
Homebrew, Winget, APT, and pacman are recommended because they manage updates and verify
published package metadata.

## Direct downloads
Expand All @@ -28,6 +30,8 @@ in each artifact name:
| Windows | ARM64 | `Retune-<version>-windows-arm64-setup.exe` |
| Debian/Ubuntu | amd64 | `retune_<version>_amd64.deb` |
| Debian/Ubuntu | arm64 | `retune_<version>_arm64.deb` |
| Arch Linux | x86_64 | `retune_<version>_linux_amd64.pkg.tar.zst` |
| Arch Linux ARM | aarch64 | `retune_<version>_linux_arm64.pkg.tar.zst` |

Download `checksums.txt` from that same latest release and verify the matching
asset before installing. The macOS artifact uses the Open CLI Collective's
Expand Down Expand Up @@ -173,3 +177,15 @@ documentation](https://developer.spotify.com/documentation/web-api/concepts/quot
run `sha256sum -c checksums.txt --ignore-missing` on Linux, `shasum -a 256`
on macOS, or `Get-FileHash -Algorithm SHA256` in PowerShell and compare the
result.

## Arch Linux / Omarchy

Add the signed [Open CLI Collective pacman repository](https://github.com/open-cli-collective/linux-packages#arch-linux-pacman) once, then install:

```sh
sudo pacman -Syu retune
```

Launch Retune from the desktop application menu. Pacman installs the WebKitGTK,
GTK, audio, and D-Bus runtime dependencies and upgrades Retune with the system.
Packages become available after a release finishes repository publication.
1 change: 1 addition & 0 deletions docs/tauri.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ identical. Platform overlays define the supported bundle contract:
| macOS arm64 | `.app` inside a `ditto`-created ZIP | macOS 15+ (Apple-supported releases) |
| Windows x64/ARM64 | NSIS | WebView2 105; downgrade disabled |
| Ubuntu amd64/arm64 | Debian package | Ubuntu 22.04 / WebKitGTK 4.1 |
| Arch x86_64 / Arch ARM aarch64 | `.pkg.tar.zst` from the same Linux payload | Distribution WebKitGTK 4.1 |

The Windows application manifest is an explicit build input and `build.rs`
emits `cargo:rerun-if-changed` for it. Tauri's build helper owns capability,
Expand Down
7 changes: 7 additions & 0 deletions scripts/check-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ for (const asset of [
'Retune-${VERSION}-windows-arm64-setup.exe',
'retune_${VERSION}_amd64.deb',
'retune_${VERSION}_arm64.deb',
'retune_${VERSION}_linux_amd64.pkg.tar.zst',
'retune_${VERSION}_linux_arm64.pkg.tar.zst',
'checksums.txt',
]) required(workflow, asset, `asset contract ${asset}`)
assert.doesNotMatch(workflow, /Retune-\$\{VERSION\}-aarch64\.tar\.gz|tar -czf/, 'macOS release must use Apple-compatible ZIP packaging')
Expand Down Expand Up @@ -356,3 +358,8 @@ required(winget, 'Retune-0.0.0-windows-x64-setup.exe')
required(winget, 'Retune-0.0.0-windows-arm64-setup.exe')

console.log(`release contract OK (${tauri.version})`)

required(workflow, "python3 scripts/package-arch.py", "Arch payload packaging step");
required(workflow, "nfpm_2.47.0_Linux_${tool_arch}.tar.gz", "pinned Arch packager");
required(workflow, "sha256sum -c checksums.txt --ignore-missing", "packager checksum verification");
required(workflow, "name: retune-arch-${{ matrix.arch }}", "Arch artifact upload");
66 changes: 66 additions & 0 deletions scripts/package-arch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""Package the validated Tauri Linux payload for the signed pacman repository."""
import argparse
import datetime
import io
import json
import pathlib
import re
import subprocess
import tarfile
import tempfile


def package(deb, version, arch, output, nfpm):
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version):
raise ValueError("Expected the release's numeric version")
if arch not in ("amd64", "arm64"):
raise ValueError("Unsupported architecture")
members = subprocess.check_output(["bsdtar", "-tf", str(deb)], text=True).splitlines()
def member(prefix):
matches = [name for name in members if name.startswith(prefix + ".tar.")]
if len(matches) != 1:
raise ValueError("Expected one " + prefix + " archive")
return subprocess.check_output(["bsdtar", "-xOf", str(deb), matches[0]])
with tarfile.open(fileobj=io.BytesIO(member("control")), mode="r:*") as control:
entry = next(item for item in control.getmembers() if item.name.lstrip("./") == "control")
fields = dict(line.split(": ", 1) for line in control.extractfile(entry).read().decode().splitlines() if ": " in line and not line.startswith(" "))
if fields.get("Version") != version or fields.get("Architecture") != arch:
raise ValueError("Debian payload version/architecture does not match the release")
output.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as directory:
staging = pathlib.Path(directory)
with tarfile.open(fileobj=io.BytesIO(member("data")), mode="r:*") as payload:
payload.extractall(staging, filter="data")
for required in ("usr/bin/retune-desktop", "usr/share/applications/Retune.desktop"):
if not (staging / required).is_file():
raise ValueError("Missing desktop payload: " + required)
contents = [{"src": str(path), "dst": "/" + str(path.relative_to(staging))}
for path in sorted((staging / "usr").rglob("*")) if path.is_file()]
contents.append({"src": str(pathlib.Path(__file__).resolve().parents[1] / "LICENSE"),
"dst": "/usr/share/licenses/retune/LICENSE"})
config = {"name": "retune", "arch": arch, "platform": "linux", "version": version,
"release": "1", "section": "sound",
# Whole seconds avoid tar/MTREE timestamp rounding differences.
"mtime": datetime.datetime.fromtimestamp(entry.mtime, datetime.timezone.utc).isoformat(), "maintainer": "Open CLI Collective",
"description": "Desktop music player for local files and Spotify",
"homepage": "https://github.com/open-cli-collective/Retune", "license": "MIT",
"depends": ["webkit2gtk-4.1", "gtk3", "alsa-lib", "dbus", "gcc-libs", "glibc"],
"contents": contents}
config_path = staging / "nfpm.json"
config_path.write_text(json.dumps(config))
destination = output / f"retune_{version}_linux_{arch}.pkg.tar.zst"
subprocess.run([nfpm, "package", "--packager", "archlinux", "--config", str(config_path),
"--target", str(destination)], check=True)
return destination


if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("deb", type=pathlib.Path)
parser.add_argument("version")
parser.add_argument("arch", choices=("amd64", "arm64"))
parser.add_argument("output", type=pathlib.Path)
parser.add_argument("--nfpm", default="nfpm")
args = parser.parse_args()
print(package(args.deb, args.version, args.arch, args.output, args.nfpm))
Loading