| A second host that checks, and deliberately does not publish 65c27be nandi yesterday | 1 | # The build, on rickub. The same `nix flake check` and the same three objects |
| 2 | # GitLab builds next door, for the case where a push reaches this host and not |
| 3 | # that one. |
| 4 | # |
| 5 | # It does not compile here, and could not: a rickub runner is a Linux x86-64 |
| 6 | # container, and this graph is ~800 vendored crates behind libcosmic, iced, |
| 7 | # wgpu and aws-lc-sys. What the runner does is evaluate, dispatch and fetch — |
| 8 | # eu.nixbuild.net holds every vendor path from previous runs and does the |
| 9 | # compiling, exactly as it does for GitLab. So the two hosts share a cache |
| 10 | # rather than duplicating an hour of work, and a warm pipeline here is a |
| 11 | # pipeline that mostly substitutes. |
| 12 | # |
| 13 | # What it deliberately does NOT do is publish. The tarballs go to GitLab's |
| 14 | # generic package registry, and the token for that is CI_JOB_TOKEN — a GitLab |
| 15 | # job's own credential, issued to the job, not something to mint and carry |
| 16 | # here as a long-lived secret. One publisher and one place a consumer pins |
| 17 | # from is also just simpler to reason about than two. This host builds, checks |
| 18 | # and hands the tarballs back as run artifacts; `main` on GitLab is what moves |
| 19 | # `latest`. |
| 20 | # |
| 21 | # Lives in .rickub/workflows/ rather than .github/workflows/ because rickub |
| 22 | # reads one or the other and never both — see |
| 23 | # https://rickub.com/docs/actions and https://rickub.com/docs/migrating-from-github |
| 24 | name: build |
| 25 | |
| 26 | on: |
| 27 | push: |
| 28 | pull_request: |
| 29 | workflow_dispatch: |
| 30 | |
| 31 | jobs: |
| 32 | check: |
| 33 | runs-on: ubuntu-latest |
| 34 | # A cold run vendors the world and compiles the workspace. The builder does |
| 35 | # the compiling, but this side waits on it, and waits on the fetch of what |
| 36 | # comes back. GitLab gives the same job 3h. |
| 37 | timeout-minutes: 180 |
| 38 | steps: |
| 39 | # The flake is fetched from the checkout, and nix wants the git tree |
| 40 | # rather than a shallow single commit to resolve `self` from. |
| 41 | - uses: actions/checkout@v4 |
| 42 | with: |
| 43 | fetch-depth: 0 |
| 44 | |
| 45 | - uses: cachix/install-nix-action@v27 |
| 46 | with: |
| 47 | extra_nix_config: | |
| 48 | experimental-features = nix-command flakes |
| 49 | |
| 50 | # Everything the GitLab job's before_script sets up, and for the same |
| 51 | # reasons — the comments there are the long version. The short one: |
| 52 | # |
| 53 | # max-jobs must NOT be zero. At zero the runner refuses to build |
| 54 | # anything at all, which sends the fixed-output fetches (rust |
| 55 | # toolchain tarballs, every crate source) to nixbuild, where the build |
| 56 | # sandbox has no network and they can only fail. nproc keeps those and |
| 57 | # crane's hundreds of trivial cargo-src derivations local and parallel; |
| 58 | # the expensive builds still go remote. |
| 59 | # |
| 60 | # The explicit priority on nixbuild matters. nix picks a substituter by |
| 61 | # priority rather than by the order listed, and an ssh-ng store |
| 62 | # defaults to 0 against cache.nixos.org's 40 — so without it nixbuild |
| 63 | # wins even for stock nixpkgs paths and they crawl down one SSH |
| 64 | # connection instead of coming off the CDN. |
| 65 | # |
| 66 | # Keep the builder's job count modest: nixbuild's sshd caps concurrent |
| 67 | # sessions and refuses the excess, which nix reports as the thoroughly |
| 68 | # misleading "Nix daemon disconnected unexpectedly (maybe it crashed?)". |
| 69 | - name: Point nix at eu.nixbuild.net |
| 70 | env: |
| 71 | NIXBUILD_SSH_KEY: ${{ secrets.NIXBUILD_SSH_KEY }} |
| 72 | run: | |
| 73 | set -eu |
| No daemon to restart, and no systemctl to try it with 459c9cd nandi yesterday | 74 | # ROOT's ~/.ssh. The GitLab job runs as root in a nixos/nix image and |
| 75 | # needs no such care; here the step below decides which install this |
| 76 | # is, and root is the answer either way — see the note there. |
| A second host that checks, and deliberately does not publish 65c27be nandi yesterday | 77 | sudo mkdir -p /root/.ssh |
| 78 | # base64 because a CI variable can only carry a single line. |
| 79 | printf '%s' "$NIXBUILD_SSH_KEY" | base64 -d | sudo tee /root/.ssh/nixbuild >/dev/null |
| 80 | sudo chmod 600 /root/.ssh/nixbuild |
| 81 | printf 'Host eu.nixbuild.net\n PubkeyAcceptedKeyTypes ssh-ed25519\n IdentityFile /root/.ssh/nixbuild\n IdentitiesOnly yes\n' | sudo tee /root/.ssh/config >/dev/null |
| 82 | printf 'eu.nixbuild.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIQCZc54poJ8vqawd8TraNryQeJnvH1eLpIDgbiqymM\n' | sudo tee /root/.ssh/known_hosts >/dev/null |
| 83 | # nixbuild's paths are signed, but the key is per-account and only |
| 84 | # readable from its web UI, so rather than carry a second secret we |
| 85 | # lean on the transport — the store is reached over SSH with our own |
| 86 | # key. cache.nixos.org is verified by its own key either way; |
| 87 | # require-sigs only relaxes nixbuild. |
| 88 | sudo tee -a /etc/nix/nix.conf >/dev/null <<EOF |
| 89 | builders = ssh-ng://eu.nixbuild.net x86_64-linux - 16 1 big-parallel,benchmark |
| 90 | builders-use-substitutes = true |
| 91 | substituters = https://cache.nixos.org ssh-ng://eu.nixbuild.net?priority=50 |
| 92 | require-sigs = false |
| 93 | max-jobs = $(nproc) |
| 94 | EOF |
| No daemon to restart, and no systemctl to try it with 459c9cd nandi yesterday | 95 | # Only if there IS a daemon. The runner's installer says |
| 96 | # "installing Nix as root is not supported by this script" and falls |
| 97 | # back to a SINGLE-USER install, so there is no nix-daemon to |
| 98 | # restart and no systemctl in the image to try — an unconditional |
| 99 | # restart here exited 127 and took the job with it. |
| 100 | # |
| 101 | # Which install it is decides two things, and they happen to agree. |
| 102 | # A daemon reads nix.conf once at start, so appended settings need |
| 103 | # the restart; a single-user client reads it per invocation, so they |
| 104 | # are live immediately. And the process opening the SSH connection |
| 105 | # is root either way — the daemon in one case, this job in the |
| 106 | # other — so the key belongs in /root/.ssh regardless. |
| 107 | if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet nix-daemon; then |
| 108 | sudo systemctl restart nix-daemon |
| 109 | else |
| 110 | echo "single-user nix: nix.conf is read per invocation, nothing to restart" |
| 111 | fi |
| 112 | # Printed rather than assumed. If the builder is not set here, every |
| 113 | # derivation below is built on this runner, and the first symptom of |
| 114 | # that is not an error — it is three hours of compiling libcosmic. |
| A second host that checks, and deliberately does not publish 65c27be nandi yesterday | 115 | nix config show builders |
| 116 | nix config show max-jobs |
| 117 | |
| 118 | # fmt, clippy and the test suite, plus all four desktop objects — the |
| 119 | # checks build them. This is the step that has historically failed, and |
| 120 | # it fails fast: fmt costs seconds and runs before anything is compiled. |
| 121 | - run: nix flake check -L |
| 122 | |
| 123 | # Store paths are read-only symlink farms; the runner uploads plain |
| 124 | # files, so dereference them into trees it can zip. |
| 125 | - name: Stage the objects |
| 126 | run: | |
| 127 | set -eu |
| 128 | nix build -L --no-link --print-out-paths .#libs .#android > /tmp/outs |
| 129 | mkdir -p artifacts && xargs -a /tmp/outs -I{} cp -rL {}/. artifacts/ |
| 130 | # Staged beside artifacts/ and not into it: libsPortable holds the |
| 131 | # same four sonames with a different RUNPATH, so merging the two |
| 132 | # trees would leave whichever was copied last under both names. |
| 133 | nix build -L --no-link --print-out-paths .#libsPortable > /tmp/portable |
| 134 | mkdir -p portable && xargs -a /tmp/portable -I{} cp -rL {}/. portable/ |
| 135 | |
| 136 | # The same three tarballs GitLab publishes, rooted at lib/ and include/ |
| 137 | # so a consumer's `flake = false` input resolves to ${input}/lib/… with |
| 138 | # nothing in between. Here they are only artifacts of the run — see the |
| 139 | # note at the top about why this host does not upload them anywhere. |
| 140 | - name: Tar them the way a consumer takes them |
| 141 | run: | |
| 142 | set -eu |
| 143 | tar czf x86_64-linux.tar.gz -C artifacts include \ |
| 144 | lib/libvidya.so lib/libjolttui.so lib/libjoltmoq.so lib/libjoltcosmic.so |
| 145 | # `-C portable lib include` and not a file list: the point of this one |
| 146 | # is the libraries that came along, and naming the four we know about |
| 147 | # would drop them. |
| 148 | tar czf x86_64-linux-portable.tar.gz -C portable lib include |
| 149 | tar czf android-arm64-v8a.tar.gz -C artifacts include lib/arm64-v8a |
| 150 | |
| 151 | - uses: actions/upload-artifact@v4 |
| 152 | with: |
| 153 | name: jolt-native-${{ github.sha }} |
| 154 | path: | |
| 155 | x86_64-linux.tar.gz |
| 156 | x86_64-linux-portable.tar.gz |
| 157 | android-arm64-v8a.tar.gz |
| 158 | if-no-files-found: error |