# jasp-desktop-bin Automated binary builds of [JASP](https://jasp-stats.org/) 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--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 ` | Written into the PKGBUILD header. The guidelines suggest obfuscating the address against spam harvesting. | | `TARBALL_HOST` | `files.example.org` | SSH host the tarballs are uploaded to | | `TARBALL_PATH` | `/path/to/tarballs` | Directory on that host that the web server publishes | | `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 the `jasp-build` volume under `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 `TARBALL_HOST` 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/`. They are kept outside the website's own web root: the site deploy replaces its web root atomically, so anything stored inside it would vanish on the next deploy. The web server exposes the tarball directory through a separate `/tarballz/` location 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 the runner's `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. - The Docker volume `jasp-cache`, 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. `docker volume rm jasp-cache` forces a clean build. A second volume, `jasp-build`, holds the work tree and output; the workflow empties it at the start of every run. - **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](https://wiki.archlinux.org/title/AUR_submission_guidelines#Rules_of_submission): > 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.