This repository has no description
4.4 kB
105 lines
1#!/usr/bin/env bash
2# Mounts a lazy /nix/store for building a machine (the machine jobs of
3# ci.yml): every store path the build would substitute from cache.nixos.org
4# or cache.lassul.us is registered as valid from its narinfo and served by
5# lazy-store (tools/lazy-store), which downloads a path's NAR only when a
6# file in it is opened first. What nix builds, and what neither cache has,
7# is built or substituted (also from hestia) as usual; writes go to the
8# real store dir below the mount.
9#
10# `closure FETCH` collects the closure of the paths in FETCH, those the
11# build would substitute (from the eval job's dry-run, .github/ci-shared.sh),
12# from the caches' narinfos; it needs no drvs and can run while they are
13# fetched. `mount` mounts it on /nix/store and registers the paths, after
14# `closure` and once the drvs are in the store.
15#
16# Needs sudo, /dev/fuse and LAZY_STORE, the lazy-store package's out path
17# (substitutable). If anything fails before the paths are registered, the
18# mount is undone and the build substitutes eagerly: this script always
19# exits 0. The FUSE log ends up in $RUNNER_TEMP/lazy-store/fuse.log.
20# Usage: lazy-store.sh closure FETCH
21# lazy-store.sh mount
22set -euo pipefail
23L=$RUNNER_TEMP/lazy-store
24mkdir -p "$L"
25
26fallback() {
27 set +e
28 echo "::error::lazy store: $1; the build substitutes eagerly instead"
29 rm -f "$L/ready"
30 if [ -s "$L/fuse.log" ]; then
31 tail -n 50 "$L/fuse.log"
32 fi
33 if mountpoint -q /nix/store; then
34 sudo umount /nix/store || sudo umount -l /nix/store
35 fi
36 if mountpoint -q /lazy/nix; then
37 sudo umount -l /lazy/nix
38 fi
39 exit 0
40}
41trap 'fallback "line $LINENO failed"' ERR
42
43lazy=$LAZY_STORE/bin/lazy-store
44
45if [ "$1" = closure ]; then
46 if [ ! -s "$2" ]; then
47 echo "lazy store: nothing to substitute"
48 exit 0
49 fi
50 nix-store --realise "$LAZY_STORE" >/dev/null || fallback "cannot get $LAZY_STORE"
51 "$lazy" closure "$L" <"$2" >/dev/null || fallback "closure failed"
52 jq -r '"lazy store: \(.paths) of the \(.roots) paths to substitute and their closure (\(.file_size / 1048576 | floor) MiB to download eagerly); \(.missing | length) not in the caches, \(.incomplete) referring to those"' \
53 "$L/closure.json"
54 if [ "$(jq length "$L/paths.json")" != 0 ]; then
55 touch "$L/ready"
56 fi
57 exit 0
58fi
59
60if [ ! -e "$L/ready" ]; then
61 exit 0
62fi
63nixbin=$(dirname "$(readlink -f "$(command -v nix-store)")")
64
65sudo modprobe fuse || true
66# The upper layer is the real store dir, reached through a private bind
67# mount of /nix, so that the mount on /nix/store doesn't show up there.
68sudo mkdir -p /lazy/nix
69sudo mount --bind /nix /lazy/nix || fallback "bind mount of /nix failed"
70sudo mount --make-private /lazy/nix || fallback "making /lazy/nix private failed"
71# shellcheck disable=SC2024 # the log is ours, only the mount needs root
72sudo "$lazy" mount "$L" /lazy/nix/store /nix/store >"$L/fuse.log" 2>&1 &
73fuse=$!
74# sudo runs as root (no kill -0) and stays a zombie until waited for
75fuse_alive() {
76 local s
77 s=$(ps -o stat= -p "$fuse") && [[ $s != Z* ]]
78}
79# it fetches all .ls listings before mounting; paths without one, and those
80# referring to them, it leaves out of paths.json and db.dump, so that the
81# build substitutes them
82until mountpoint -q /nix/store; do
83 fuse_alive || fallback "lazy-store exited before mounting /nix/store"
84 sleep 1
85done
86grep -m1 '^init: ' "$L/fuse.log" || true
87
88n=$(jq length "$L/paths.json")
89some=$(jq -r 'keys[0]' "$L/paths.json")
90[ -e "/nix/store/$some" ] || fallback "/nix/store/$some is not served"
91# shellcheck disable=SC2024 # db.dump is ours, only nix-store needs root
92sudo "$nixbin/nix-store" --load-db <"$L/db.dump" || fallback "registering the paths failed"
93# registered: the mount has to stay
94trap - ERR
95set +e
96grep '^no listing for ' "$L/fuse.log"
97jq -r --arg n "$n" '"lazy store: mounted on /nix/store, \($n) paths registered; \(.failed | length) without a listing, \(.incomplete) referring to those"' \
98 "$L/no-listing.json"
99# niks3 and hestia leave out what cache.nixos.org signed by the signatures
100# in nix's database; without them they would upload (and so download)
101# those paths
102jq -r 'keys[] | "/nix/store/" + .' "$L/paths.json" |
103 sudo "$nixbin/nix" --extra-experimental-features nix-command \
104 store copy-sigs -s "file://$L/narinfo" --stdin ||
105 echo "::warning::lazy store: copying the signatures failed, uploads may download paths of cache.nixos.org"