agw e8c3d775aa Run the scheduled build in the evening
The AUR push is made under the maintainer's name and should land in the
evening rather than during working hours. At 16:00 UTC a two-hour build
publishes around 19:00-20:30 local time.

Claude-Session: https://claude.ai/code/session_01GGe7kUhooMFuzY6GaDb2w9
2026-09-30 22:15:00 +02:00

jasp-desktop-bin

Automated binary builds of JASP for Arch Linux, published to the AUR as jasp-desktop-bin.

Why this exists

Upstream JASP ships installers for Windows and macOS only — there is no official Linux binary. The official Linux channel is the Flathub build. The existing AUR package jasp-desktop therefore builds everything from source, including buildAllDefaultJaspModules.R, which compiles several hundred R packages. That is the multi-hour build this repo exists to avoid: the compile happens once, here, and users download the result.

The constraint that shapes everything

JASP pins its R package set with renv lockfiles against a frozen CRAN snapshot — for 0.98.1, Modules/Rcpp_RInside.lock points at the Posit snapshot of 2026-03-31. That set targets R 4.5.

Building against Arch's current R (4.6.1) fails immediately: Rcpp 1.1.1 does not compile there, because R_NamespaceRegistry is gone in R 4.6. The Rcpp release that fixes this (1.1.2, 2026-07-05) is newer than the pinned snapshot, so the lockfile cannot resolve it either. This is precisely the "old libraries with issues" problem that makes the source AUR package painful.

So this build compiles and bundles its own R 4.5.3 into /opt/jasp-desktop/R and points R_HOME at it — which is what upstream's own CI does (.github/workflows/ubuntu.yml sets JASP_R_VERSION: 4.5.3 and an explicit R_HOME). Tools/CMake/R.cmake reads R_HOME from the environment on Linux, so this is a supported configuration rather than a hack.

What is bundled, and why nothing is version-pinned

After staging, build/bundle-libs.sh walks every ELF object in the tree, copies each non-system shared library it depends on into /opt/jasp-desktop/lib, and rewrites the rpath to look there. It works by denylist: glibc, libstdc++, Qt6, the GL/X11/Wayland/dbus stack, fontconfig and freetype stay system-provided; everything else — ICU, jsoncpp, glpk, JAGS, readstat, librdata, BLAS/LAPACK, pcre2, readline — is bundled.

The point is that depends contains no version constraints at all. That is a deliberate reversal of the obvious design. Pinning (icu>=78 <79) looks safer but is actively harmful on a rolling distro: when Arch ships the next ICU, pacman would refuse to upgrade it while JASP is installed, blocking the user's entire system upgrade until a rebuild lands. Bundling means JASP never constrains the system, and a pacman -Syu cannot break it.

Qt6 is the deliberate exception and stays a system dependency. Qt guarantees binary compatibility across Qt 6 minor releases, and bundling QtWebEngine would add ~500 MB for very little gain. A Qt 7 transition will need a rebuild — which is why scripts/check-upstream.py watches the Qt major and nothing else besides the JASP version. Rebuilding for anything else would cost every user a ~1.7 GB re-download for no benefit.

jags and librdata-git are AUR-only, and JASP needs freexl >= 2.0.99 while Arch ships 2.0.0 — so the build compiles those itself, matching what upstream CI does.

Layout

Path Purpose
build/build-jasp.sh The actual build. Runs in an archlinux:base-devel container, emits the tarball plus build-info.json.
build/bundle-libs.sh Copies non-system shared libraries into /opt/jasp-desktop/lib and rewrites rpaths, so the package needs no version-pinned deps.
scripts/check-upstream.py Compares upstream JASP and the Arch Qt major against state.json; decides whether to build.
scripts/gen-pkgbuild.py Fills packaging/PKGBUILD.in from build-info.json; derives pkgrel from what is on the AUR.
scripts/publish-tarball.sh Uploads the tarball to asger.weirsoe.dk, verifies the checksum on arrival and prunes old releases.
scripts/push-aur.sh Generates .SRCINFO and pushes to aur.archlinux.org.
scripts/record-state.sh Commits state.json so the next nightly run knows what was built.
packaging/PKGBUILD.in Template for the AUR package.
state.json Last successfully built combination.

Flow

daily (16:00 UTC) ─► check-upstream.py
                     │ new JASP tag, or a Qt major transition?
                     ▼
                   build-jasp.sh  (Arch container, hours)
                     │  builds R 4.5.3, JASP, all R modules, then bundles libs
                     │  jasp-desktop-<ver>-x86_64.tar.zst + build-info.json
                     ▼
                   upload to asger.weirsoe.dk ──► verify public URL
                     │                                  ──► push to AUR
                     │
                     ▼
                   state.json committed back

Required configuration

Set these in the Gitea repo before the first run.

Variables (Settings → Actions → Variables):

Name Example Meaning
AUR_MAINTAINER Asger Geel Weirsoe <asger at weircon dot dk> Written into the PKGBUILD header. The guidelines suggest obfuscating the address against spam harvesting.
TARBALL_HOST 192.168.10.148 CT148, where tarballs are uploaded
TARBALL_PATH /srv/tarballz Directory on that host
TARBALL_BASE_URL https://asger.weirsoe.dk/tarballz Public URL the PKGBUILD downloads from
PUBLISH true Master switch. Unless it is true, a run only builds and verifies, leaving the tarball in /srv/jasp-build/out: no upload, no state.json commit, no AUR push.

Secrets (Settings → Actions → Secrets):

Name Meaning
GH_TOKEN Optional. A GitHub PAT, passed to the build as GITHUB_PAT. Not used for publishing — several JASP R modules install from GitHub and anonymous API rate limits can fail the build.
TARBALL_SSH_KEY Private key authorised on CT148 for uploading tarballs
AUR_SSH_PRIVATE_KEY Private SSH key registered with your AUR account. Copy the value already set on agw/pi3-smart-workspace.
STATE_PUSH_TOKEN Gitea token with write access to this repo, for committing state.json. (Gitea reserves the GITEA_ secret prefix.)

Where the tarballs live

Tarballs are served from https://asger.weirsoe.dk/tarballz/, backed by /srv/tarballz on CT148.

They are not stored under /var/www/asger. The asger-web deploy replaces that entire directory atomically — it unpacks into .new, swaps it in and deletes the old one — so anything kept inside it would vanish on the next site deploy. nginx exposes /srv/tarballz through a location /tarballz/ alias instead, with gzip off so makepkg gets a plain byte stream.

Note that asger.weirsoe.dk 301-redirects to the IDN form asger.xn--weirse-fya.dk. makepkg follows redirects, so the readable hostname is used in the PKGBUILD, but that redirect is load-bearing — if it is ever removed, the source= URL must be repointed.

The publish step verifies the uploaded copy's sha256 on the host, and a separate step then fetches the public URL and checks it against the sha256 baked into the PKGBUILD. Both run before the AUR push, so the package can never be published pointing at a missing or truncated file.

Runner requirements

The build runs for hours and needs disk. On the Gitea runner host:

  • runner.timeout in /var/lib/gitea-worker/config.yaml must exceed the build time (set to 16h).
  • Gitea's own job timeout ([actions] ENDLESS_TASK_TIMEOUT in app.ini, default 3h) must match, otherwise Gitea kills the job at 3h regardless of the runner.
  • Roughly 40–60 GB of free disk for the build tree, the bundled R and the staged install.
  • /srv/jasp-cache on the runner host, mounted into the build as /cache. It holds ccache and the compiled R package cache (renv), and is what keeps a rebuild from recompiling 1000+ R packages from scratch. It is never wiped by the workflow; entries whose system libraries have gone missing after an Arch update are purged at the start of each build. Delete it to force a clean build.
  • Swap. rstanarm (required by jaspMixedModels) and OpenMx generate translation units where a single cc1plus wants 4–8 GB. Without swap an over-commit is an instant OOM kill, which surfaces only as a module that never builds. 16 GB RAM plus 8 GB swap, with R packages compiled at R_JOBS=1, is what this build was verified against.

Running it by hand

# Build a specific tag without waiting for the schedule
Actions → Build JASP binary and publish to AUR → Run workflow
         jasp_tag = v0.98.1   force = true

Where the binary comes from, and how that is disclosed

The AUR has no formal "unofficial binary" flag. What it has is the naming rule in AUR submission guidelines:

Packages that use prebuilt deliverables, when the sources are available, must use the -bin suffix.

That rule is about the deliverable being prebuilt — it says nothing about who built it, so -bin is the correct and sufficient signal here even though the binary is ours rather than upstream's. The two rules that could bite do not: JASP is not in any official repository, and the AUR repo carries only PKGBUILD and .SRCINFO, never the makepkg tarball or a filelist.

Disclosure is therefore by convention rather than by mechanism, in the two places a user actually looks:

  • the comment header at the top of the PKGBUILD (the same header the guidelines require for maintainer info) states that upstream ships no Linux binary and links to the build pipeline that produced this one;
  • the pkgdesc ends in (prebuilt binary).

Anyone who wants to verify can read build/build-jasp.sh in this repo and reproduce the tarball.

S
Description
Automated Arch Linux binary builds of JASP, published to the AUR as jasp-desktop-bin
Readme
432 KiB
Languages
Shell 84.3%
Python 12.9%
R 2.8%