diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8af8397..bdf08b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: closure: name: production closure budget runs-on: ubuntu-latest + env: + # For the push step's condition: `if:` cannot read secrets directly. + CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} steps: - uses: actions/checkout@v4 @@ -44,12 +47,34 @@ jobs: - uses: DeterminateSystems/nix-installer-action@main + # Pull only. The cache is public, and the action's own push sends + # whole closures - the toplevel's has Spotify in it, which nixpkgs marks + # unfree and not redistributable. What is worth caching is pushed by + # name after the build. + - uses: cachix/cachix-action@v17 + with: + name: physshell + skipPush: true + # Builds system.build.toplevel - the closure the VHDX is made of - and # compares it against tests/closure-budget.nix. No KVM needed: the disk # image is not built here, only the system that goes into it. - name: Closure budget run: tools/closure.sh check + # The appliance kernel is the one expensive thing in that build - about + # 26 minutes on a runner - and 25 MiB to download. The toplevel and the + # image take its out and modules outputs, and neither refers to anything + # else; dev would drag 2.6 GiB of toolchain along, and nothing needs it. + # Skipped wherever the token is not there, forks included. + - name: Push the appliance kernel to the binary cache + if: env.CACHIX_AUTH_TOKEN != '' + run: | + kernel='.#nixosConfigurations.spotibox.config.boot.kernelPackages.kernel' + cachix push physshell \ + "$(nix eval --raw "$kernel.outPath")" \ + "$(nix eval --raw "$kernel.modules.outPath")" + powershell: name: controller lint + unit checks (${{ matrix.shell }}) runs-on: windows-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6ce848..6473f7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,9 @@ jobs: build: name: build and publish images runs-on: ubuntu-latest + env: + # For the push step's condition: `if:` cannot read secrets directly. + CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} steps: - uses: actions/checkout@v4 with: @@ -44,9 +47,27 @@ jobs: extra-conf: | system-features = nixos-test benchmark big-parallel kvm + # Pull only, for the reason given in ci.yml: a closure push from here + # would publish Spotify. The kernel is usually already there from the + # pull request that changed it. + - uses: cachix/cachix-action@v17 + with: + name: physshell + skipPush: true + - name: Build release bundle run: nix build .#spotibox-release -L --out-link release + # The same two outputs ci.yml pushes, for a tag whose kernel no pull + # request built. + - name: Push the appliance kernel to the binary cache + if: env.CACHIX_AUTH_TOKEN != '' + run: | + kernel='.#nixosConfigurations.spotibox.config.boot.kernelPackages.kernel' + cachix push physshell \ + "$(nix eval --raw "$kernel.outPath")" \ + "$(nix eval --raw "$kernel.modules.outPath")" + - name: Inspect assets run: | ls -la release/ diff --git a/README.md b/README.md index 7e19eb7..e2db777 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,17 @@ unchecked until a PR exists - open one early if you want the signal. | `controller lint + unit checks (powershell)` | windows | unit checks under Windows PowerShell 5.1, the shell `qubix-up.cmd` actually uses | | `controller lint + unit checks (pwsh)` | windows | the same checks under pwsh 7, plus PSScriptAnalyzer | +**The binary cache.** The appliance kernel is compiled from source - no +upstream cache has this configuration - which takes about 26 minutes on a +runner. The closure budget job and the release job pull from the public +Cachix cache `physshell`, and after their builds push exactly two paths: the +kernel's `out` and `modules` outputs, 25 MiB that refer to nothing else. So a +kernel is compiled once per configuration rather than once per push. Nothing +else goes in, deliberately: the cache is public, `cachix-action`'s own push +sends whole closures, and the toplevel's closure holds Spotify, which nixpkgs +marks unfree and not redistributable. Pushing needs a `CACHIX_AUTH_TOKEN` +repository secret; without one - on forks, for instance - the jobs only read. + Driving it from the terminal with the GitHub CLI: ```bash @@ -431,6 +442,26 @@ $Qubix = "\\wsl.localhost\NixOS\home\nixos\Documents\repos\qubix" With `-ImageSource wsl` the controller derives the distro and Linux path from the UNC path (override with `-WslDistro` / `-RepoLinuxPath`), regenerates the manifest from Nix, builds both images and copies them out of the store. +It leaves `result-spotibox-vhdx` and `result-spotibox-home-vhdx` in the +checkout as GC roots, so `nix-collect-garbage` keeps the last build - kernel +included - instead of sending the next `recreate` back to compiling it. + +The binary cache CI fills (see *Continuous Integration*) serves local builds +too: a kernel CI has already compiled downloads in seconds instead of taking +most of an hour. On NixOS-WSL, in the system configuration, then +`sudo nixos-rebuild switch`: + +```nix +nix.settings = { + extra-substituters = [ "https://physshell.cachix.org" ]; + extra-trusted-public-keys = [ + "physshell.cachix.org-1:JX0coz2i80gA+E0MVCbsvnT25VezCA5uw67JUaLiKyI=" + ]; +}; +``` + +It is not in `flake.nix` as `nixConfig`: Nix asks before applying a flake's +settings, and the controller's calls into WSL cannot answer. The `.cmd` wrappers run PowerShell with a process-scoped `-ExecutionPolicy Bypass`, which also sidesteps Windows treating scripts under diff --git a/tools/qubixctl.ps1 b/tools/qubixctl.ps1 index be813d2..3f44376 100644 --- a/tools/qubixctl.ps1 +++ b/tools/qubixctl.ps1 @@ -365,8 +365,12 @@ function Build-QubixImagesInWsl { $package = [string](Get-Prop $Config 'package') $homePackage = [string](Get-Prop $Config 'homePackage' '') + # Each image gets a GC root of its own. Both builds used to share + # `result`, so after a recreate only the home seed was rooted, and a + # nix-collect-garbage in WSL took the system image with it - the kernel + # inside it included, which is most of an hour to compile again. Write-Host "=== Building .#$package in WSL distro '$Distro' ===" - Invoke-WslInteractive -Distro $Distro -RepoPath $RepoPath -Script "nix build -L .#$package" + Invoke-WslInteractive -Distro $Distro -RepoPath $RepoPath -Script "nix build -L --out-link result-$package .#$package" # --print-out-paths gives the absolute store path; wslpath -w and Copy-Item # need absolute paths, and the 'result' symlink would resolve relative to /. @@ -379,7 +383,7 @@ function Build-QubixImagesInWsl { $homeWindows = '' if ($homePackage) { Write-Host "=== Building .#$homePackage in WSL distro '$Distro' ===" - Invoke-WslInteractive -Distro $Distro -RepoPath $RepoPath -Script "nix build -L .#$homePackage" + Invoke-WslInteractive -Distro $Distro -RepoPath $RepoPath -Script "nix build -L --out-link result-$homePackage .#$homePackage" $homeLinux = Invoke-WslCapture -Distro $Distro -RepoPath $RepoPath ` -Script "nix build --no-link --print-out-paths .#$homePackage | tr -d '\r'" $homeWindows = Convert-LinuxPathToWindows -Distro $Distro -LinuxPath $homeLinux