replace the aur package with a nix flake

This commit is contained in:
pj committed 2026-09-03 13:24:20 +05:30
1 parent 958931f5ef
commit a88055cbbd
11 files changed
+231 -107

No files matched your search

+11
View File
@@ -68,3 +68,14 @@ jobs:
- name: Test
working-directory: src-tauri
run: cargo test
# The flake at whatever release nix/release.json pins. A broken flake or lock, or a deb that no
# longer patches against current nixpkgs, shows up here rather than at the next release.
nix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: cachix/install-nix-action@v31
- run: nix build .#margin-calendar --print-build-logs
+34 -49
View File
@@ -180,72 +180,57 @@ jobs:
done
gh release edit "$TAG" --repo "$REPO" --draft=false --latest
# Arch gets its own package rather than the AppImage. The AppImage carries Ubuntu's GTK stack,
# which cannot talk to a modern Wayland compositor and silently falls back to Xwayland; a package
# linked against the system webkit2gtk runs as a native Wayland client. Runs after publish so it
# Nix is the Linux package. The AppImage carries Ubuntu's GTK stack, which cannot talk to a modern
# Wayland compositor and silently falls back to Xwayland; the Nix package relinks the published deb
# against nixpkgs' webkit2gtk and runs as a native Wayland client. Runs after publish so the flake
# can only ever point at a release that survived the manifest check.
aur:
nix:
needs: [prepare, publish]
runs-on: ubuntu-latest
steps:
# The tag, not main, so the template and the licence are the ones this release shipped.
# main rather than the tag: the pin lands on main, and the tag was cut before the artifact
# it needs the hash of existed.
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare.outputs.tag }}
ref: main
- name: Render the PKGBUILD for this release
- name: Pin the flake to this release
env:
VERSION: ${{ needs.prepare.outputs.version }}
REPO: ${{ github.repository }}
run: |
URL="https://github.com/$REPO/releases/download/v$VERSION/Margin.Calendar_${VERSION}_amd64.deb"
# From the published asset, so the checksum is of the artifact users will actually fetch.
# From the published asset, so the hash is of the artifact users will actually fetch.
curl -fsSL --retry 3 -o package.deb "$URL"
SHA=$(sha256sum package.deb | cut -d' ' -f1)
# The PKGBUILD fetches the licence from the tag, so checksum the copy at that same tag.
LICENSE_SHA=$(sha256sum LICENSE | cut -d' ' -f1)
sed -e "s/@VERSION@/$VERSION/g" \
-e "s/@SHA256@/$SHA/g" \
-e "s/@LICENSE_SHA256@/$LICENSE_SHA/g" \
packaging/aur/PKGBUILD.in > PKGBUILD
echo "pkgver $VERSION, deb $SHA, licence $LICENSE_SHA"
HASH="sha256-$(openssl dgst -sha256 -binary package.deb | base64)"
jq -n --arg v "$VERSION" --arg h "$HASH" '{version: $v, hash: $h}' > nix/release.json
cat nix/release.json
- name: Generate .SRCINFO
# makepkg is Arch-only and refuses to run as root, hence the container. It runs as the
# runner's own uid so nothing in the bind-mounted checkout changes owner; chowning it to a
# container user left the workspace unwritable for the push step. --printsrcinfo parses the
# PKGBUILD, it does not build anything.
run: |
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -v "$PWD:/w" -w /w \
archlinux:base-devel makepkg --printsrcinfo > .SRCINFO
cat .SRCINFO
- uses: cachix/install-nix-action@v31
- name: Push to the AUR
# Building it is the check: a wrong hash, a library autoPatchelf cannot find or a broken flake
# stops here rather than on someone's machine.
- name: Build the package
run: nix build .#margin-calendar --print-build-logs
- name: Commit the pin
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then
echo "::warning::AUR_SSH_PRIVATE_KEY is not set, so the AUR package was built but not published. The release itself is unaffected."
exit 0
fi
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan -t ed25519,rsa aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes"
git clone ssh://[email protected]/margin-calendar-bin.git aur
cp PKGBUILD .SRCINFO aur/
cd aur
# A package that does not exist yet clones as an empty repo, where the local branch name
# is whatever git defaults to. The AUR only accepts master.
git checkout -B master
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add PKGBUILD .SRCINFO
if git diff --cached --quiet; then
echo "AUR is already at this version, nothing to push."
exit 0
fi
git commit -m "margin-calendar-bin $VERSION"
git push origin master
git add nix/release.json
git commit -m "point the nix package at v$VERSION"
for attempt in 1 2 3 4 5; do
git fetch origin main
git rebase origin/main
if git push origin HEAD:main; then
break
fi
if [ "$attempt" = "5" ]; then
echo "::error::main kept advancing; could not push the nix pin after 5 attempts. The release is published; rerun this job."
exit 1
fi
echo "main advanced; rebasing and retrying ($attempt)…"
sleep 3
done
+4
View File
@@ -42,3 +42,7 @@ xcuserdata/
# Screenshots & Playwright MCP artifacts
.playwright-mcp/
/*.png
# Nix build output
/result
/result-*
+7 -6
View File
@@ -6,9 +6,10 @@ fits without scrolling, and on a desktop the whole thing is drivable from the ke
It is a sibling to [margin](https://github.com/priyanshujain/margin) and shares its stack and its
visual language.
`just install` builds it and installs it on the machine you are on. Setup and the Google OAuth
client it needs are in [docs/setup.md](docs/setup.md), and the extra clients a phone build needs
are in [docs/mobile.md](docs/mobile.md). How releases are cut is in
[docs/release.md](docs/release.md). The product decisions are in [docs/design.md](docs/design.md),
how it is built is in [docs/architecture.md](docs/architecture.md), and the conventions it follows
are in [docs/conventions.md](docs/conventions.md).
`just install` builds it and installs it on the machine you are on. On Linux,
`nix profile install github:priyanshujain/margin-calendar` installs the released build instead.
Setup and the Google OAuth client it needs are in [docs/setup.md](docs/setup.md), and the extra
clients a phone build needs are in [docs/mobile.md](docs/mobile.md). How releases are cut and how
the Nix package is made are in [docs/release.md](docs/release.md). The product decisions are in
[docs/design.md](docs/design.md), how it is built is in [docs/architecture.md](docs/architecture.md),
and the conventions it follows are in [docs/conventions.md](docs/conventions.md).
+1 -1
View File
@@ -133,7 +133,7 @@ Closing the window on macOS hides it rather than quitting, the way WhatsApp and
process stays in the Dock with sync running, a Dock click brings the window back, and Cmd+Q
quits. Both halves live in `lib.rs`. Linux has no traffic lights, so that padding is
conditional, and closing the window there quits. Linux builds need `libwebkit2gtk-4.1-dev` and
ship as AppImage and deb.
ship as AppImage and deb, and the deb is what the Nix package in `nix/package.nix` relinks.
## Order of work
+34 -14
View File
@@ -36,27 +36,47 @@ targets, at which point the publish gate wants `windows-x86_64` too.
Phones do not come from this pipeline at all. The store is their update channel, and what building
for one takes is in [mobile.md](mobile.md).
## Arch
## Linux
Arch gets its own package, `margin-calendar-bin` on the AUR, pushed by the release workflow after
the manifest check passes. It is worth the extra moving part: the AppImage bundles Ubuntu's GTK
stack, and a bundled `libwayland-client` cannot talk to a current compositor, so on Hyprland the
AppImage silently falls back to Xwayland. The packaged build links against the system
Linux installs come from Nix rather than from the AppImage. The AppImage bundles Ubuntu's GTK
stack, and a bundled `libwayland-client` cannot talk to a current compositor, so on Hyprland it
silently falls back to Xwayland. The Nix package relinks the published `.deb` against nixpkgs' own
`webkit2gtk-4.1` and runs as a native Wayland client.
Installing, and later updating:
```
nix profile install github:priyanshujain/margin-calendar
nix profile upgrade margin-calendar
```
On NixOS or under home-manager, add `github:priyanshujain/margin-calendar` as a flake input and
either put `margin-calendar.packages.x86_64-linux.default` in the package list or apply
`margin-calendar.overlays.default` and use `pkgs.margin-calendar`. Updating is then
`nix flake update margin-calendar` and a rebuild.
"Check for updates" inside a Nix install still checks. It reports the newer version and the
upgrade command rather than installing anything, because the package's wrapper sets
`MARGIN_CALENDAR_PACKAGED_BY=nix` and the app asks for that before it would touch its own binary,
which in the store it could not replace anyway.
It is a binary package by necessity rather than laziness. The Google OAuth client is embedded at
compile time from a file that is deliberately not in the repo, so anything built from source on
someone else's machine would run and then tell them Google Calendar is not set up. The PKGBUILD
therefore repackages the published `.deb`, whose payload is already a normal `/usr` tree.
someone else's machine would run and then tell them Google Calendar is not set up.
`nix/package.nix` therefore unpacks the published `.deb`, whose payload is already a normal `/usr`
tree, and `autoPatchelfHook` points its libraries at nixpkgs.
`packaging/aur/PKGBUILD.in` is the template. The workflow fills in the version and the sha256 of
the artifact that was actually published, generates `.SRCINFO` with `makepkg` in an Arch container,
and pushes to `ssh://[email protected]/margin-calendar-bin.git` using `AUR_SSH_PRIVATE_KEY`.
Without that secret the job renders the package, says so, and does not fail the release.
`nix/release.json` is the pin: the version and the hash of the `.deb` that was actually published.
The `nix` job in the release workflow writes it after the manifest check passes, builds the package
once to prove the pin is good, and commits it to main. That commit is what Nix users consume:
`github:priyanshujain/margin-calendar` means main, and main means the latest release that built. A
tag's own flake still points at the release before it, because the tag is cut before the artifact
exists. If the job fails, the release is out and Nix stays a version behind until the job is rerun.
The `license=('custom')` line is a placeholder for the fact that this repo has no licence file at
all. Nothing stops the package publishing, but a package on the AUR that nobody has licensed is
worth fixing before anyone else builds on it.
The flake is x86_64-linux only, which is the one Linux target the release builds. `flake.lock`
pins nixpkgs for anyone installing through the flake directly; the overlay uses whatever nixpkgs
the host already has. CI builds the package on every push, so a nixpkgs bump that breaks the
patching shows up before a release does.
## What the build needs
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1788316716,
"narHash": "sha256-bc7rSpXIdn9QWGNqfWcPZWOhEVF8NoeAZkWq0XWnf/k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3ed67ec0a4d3c7ab4ae1f04f8ee8df07bfa506a2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+22
View File
@@ -0,0 +1,22 @@
{
description = "Margin Calendar, a calendar for Google Calendar";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs =
{ self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
overlays.default = final: prev: {
margin-calendar = final.callPackage ./nix/package.nix { };
};
packages.${system} = {
margin-calendar = pkgs.callPackage ./nix/package.nix { };
default = self.packages.${system}.margin-calendar;
};
};
}
+87
View File
@@ -0,0 +1,87 @@
# The Linux package: the deb the release workflow published, relinked against nixpkgs' own GTK and
# WebKit so it runs as a native Wayland client. It is a binary package by necessity: the Google
# OAuth client is embedded at compile time from a file that is deliberately not in the repo, so
# anything built from source here would run and then say Google Calendar is not set up.
{
lib,
stdenv,
fetchurl,
dpkg,
autoPatchelfHook,
wrapGAppsHook3,
cairo,
dbus,
gdk-pixbuf,
glib,
glib-networking,
gsettings-desktop-schemas,
gtk3,
libsoup_3,
webkitgtk_4_1,
xdg-utils,
}:
let
release = lib.importJSON ./release.json;
in
stdenv.mkDerivation {
pname = "margin-calendar";
inherit (release) version;
src = fetchurl {
url = "https://github.com/priyanshujain/margin-calendar/releases/download/v${release.version}/Margin.Calendar_${release.version}_amd64.deb";
inherit (release) hash;
};
unpackPhase = ''
runHook preUnpack
dpkg-deb -x $src .
runHook postUnpack
'';
nativeBuildInputs = [
dpkg
autoPatchelfHook
wrapGAppsHook3
];
buildInputs = [
cairo
dbus
gdk-pixbuf
glib
glib-networking
gsettings-desktop-schemas
gtk3
libsoup_3
webkitgtk_4_1
];
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r usr/bin usr/share $out/
mv "$out/share/applications/Margin Calendar.desktop" $out/share/applications/margin-calendar.desktop
runHook postInstall
'';
# The app opens the Google consent page and "Report an issue" through xdg-open. The variable is
# how it knows the store owns the binary, so "Check for updates" points at Nix instead of trying
# to replace itself.
preFixup = ''
gappsWrapperArgs+=(
--prefix PATH : ${lib.makeBinPath [ xdg-utils ]}
--set MARGIN_CALENDAR_PACKAGED_BY nix
)
'';
meta = {
description = "A calendar for Google Calendar";
longDescription = "A calendar where the grid owns the window and the day always fits without scrolling.";
homepage = "https://github.com/priyanshujain/margin-calendar";
license = lib.licenses.mit;
sourceProvenance = [ lib.sourceTypes.binaryNativeCode ];
platforms = [ "x86_64-linux" ];
mainProgram = "margin-calendar";
};
}
+4
View File
@@ -0,0 +1,4 @@
{
"version": "0.0.4",
"hash": "sha256-ZYrpxD0/7AxIeBMmYHufZU0I6/m2JWPMMSBk4CkJWd8="
}
-37
View File
@@ -1,37 +0,0 @@
# Maintainer: PJ <[email protected]>
# The template the release workflow renders. @VERSION@ and @SHA256@ are filled in from the release
# that has just published, so the PKGBUILD on the AUR always points at an artifact that exists.
#
# A binary package rather than one built from source, and that is forced rather than lazy: the
# Google OAuth client is embedded at compile time from a file that is not in the repo, so anything
# built from source on a stranger's machine runs and then says Google Calendar is not set up.
pkgname=margin-calendar-bin
pkgver=@VERSION@
pkgrel=1
pkgdesc="A calendar for Google Calendar, where the grid owns the window and the day fits without scrolling"
arch=('x86_64')
url="https://github.com/priyanshujain/margin-calendar"
license=('MIT')
depends=('webkit2gtk-4.1' 'gtk3')
provides=('margin-calendar')
conflicts=('margin-calendar')
# Prebuilt and already linked: stripping it again buys nothing and risks the binary.
options=('!strip' '!debug')
source=("${pkgname}-${pkgver}.deb::${url}/releases/download/v${pkgver}/Margin.Calendar_${pkgver}_amd64.deb"
"LICENSE-${pkgver}::https://raw.githubusercontent.com/priyanshujain/margin-calendar/v${pkgver}/LICENSE")
noextract=("${pkgname}-${pkgver}.deb")
sha256sums=('@SHA256@'
'@LICENSE_SHA256@')
package() {
bsdtar -xOf "${pkgname}-${pkgver}.deb" data.tar.gz | bsdtar -xf - -C "${pkgdir}"
# The bundler names it after the product, spaces and all. Everything that reads this directory
# copes with that, and nothing that reads it enjoys it.
mv "${pkgdir}/usr/share/applications/Margin Calendar.desktop" \
"${pkgdir}/usr/share/applications/margin-calendar.desktop"
# MIT is not one of the licences Arch keeps in /usr/share/licenses/common, so every package
# under it has to carry its own copy.
install -Dm644 "LICENSE-${pkgver}" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}