Last active
May 15, 2026 13:04
-
-
Save Soupstraw/f0ce4dd2a0e86a3f294e0efa555f81db to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { combinators, helpers, pkgs, ... }: | |
| let | |
| inherit (combinators) add-pkg-deps add-runtime compose noescape rw-bind set-env; | |
| inherit (pkgs) lib; | |
| bootstrap = with pkgs; [ | |
| nix | |
| bashInteractive | |
| coreutils | |
| gitMinimal | |
| cacert | |
| ]; | |
| # state.entry is a string per jail.nix, either lib.getExe (raw store path) | |
| # or lib.escapeShellArg (single-quote-wrapped). Strip surrounding single | |
| # quotes if present, then take the store-path prefix. | |
| entryStorePath = | |
| entry: | |
| let | |
| unquoted = | |
| if lib.hasPrefix "'" entry && lib.hasSuffix "'" entry then | |
| lib.removeSuffix "'" (lib.removePrefix "'" entry) | |
| else | |
| entry; | |
| m = builtins.match "(/nix/store/[^/]+).*" unquoted; | |
| in | |
| if m == null then null else builtins.head m; | |
| in | |
| { | |
| sig = "String -> [Package] -> Permission"; | |
| doc = '' | |
| Persists `/nix` across all jails with the given name, so `nix develop` | |
| and other store-mutating operations retain state between launches. | |
| The persistent store lives on the host at | |
| `~/.local/share/jail.nix/nix-root/<name>/nix`, and is bind-mounted to | |
| `/nix` inside the jail. Reset by deleting that directory. | |
| On first launch, the following are copied into the persistent store | |
| via `nix copy` against a chroot-store URL: | |
| * a small bootstrap closure (`nix`, `bash`, `coreutils`, `git`) | |
| * the jail's entry binary (extracted from `state.entry`) | |
| * everything in `extraPkgs` | |
| `nix copy` walks transitive references, so each top-level path pulls | |
| in its full closure. The bootstrap and `extraPkgs` are also added to | |
| `$PATH`, and `$SHELL` is set to bash so interactive use works out of | |
| the box. | |
| Use with `reset` to drop `bind-nix-store-runtime-closure` from base | |
| permissions — otherwise the host's store paths are read-only mounted | |
| over the persistent store and confuse the jail's nix database. | |
| Example: | |
| ```nix | |
| jail "claude" pkgs.claude-code (c: with c; [ | |
| reset | |
| base | |
| fake-passwd | |
| network | |
| (persist-store "claude" [ ]) | |
| mount-cwd | |
| ]) | |
| ``` | |
| ''; | |
| impl = | |
| name: extraPkgs: | |
| let | |
| rootPath = helpers.dataDirSubPath "nix-root/${name}"; | |
| nixPath = helpers.dataDirSubPath "nix-root/${name}/nix"; | |
| explicitPkgs = bootstrap ++ extraPkgs; | |
| explicitPathsList = map (p: p.outPath) explicitPkgs; | |
| in | |
| compose [ | |
| (state: | |
| if lib.elem "bind-nix-store-runtime-closure" state.includedOnce then | |
| throw '' | |
| persist-store "${name}" conflicts with bind-nix-store-runtime-closure, | |
| which read-only binds host /nix/store paths over the persistent store | |
| and desyncs the jail's nix database. | |
| Fix one of: | |
| * Add `reset` (and re-add `base` / `fake-passwd` as needed) before | |
| persist-store in this jail's combinator list. | |
| * Override basePermissions when extending jail.nix to omit | |
| bind-nix-store-runtime-closure: | |
| jail-nix.lib.extend { | |
| inherit pkgs; | |
| basePermissions = combinators: with combinators; [ base fake-passwd ]; | |
| }; | |
| '' | |
| else state) | |
| (rw-bind (noescape nixPath) "/nix") | |
| (add-pkg-deps explicitPkgs) | |
| (set-env "SHELL" "${pkgs.bashInteractive}/bin/bash") | |
| (set-env "NIX_SSL_CERT_FILE" "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt") | |
| (set-env "SSL_CERT_FILE" "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt") | |
| (state: | |
| let | |
| entry = entryStorePath state.entry; | |
| allPathsList = explicitPathsList ++ lib.optional (entry != null) entry; | |
| allPathsStr = lib.escapeShellArgs allPathsList; | |
| stampHash = builtins.substring 0 16 (builtins.hashString "sha256" allPathsStr); | |
| in | |
| add-runtime '' | |
| ROOT=${rootPath} | |
| STAMP="$ROOT/.jail-nix-bootstrapped-${stampHash}" | |
| mkdir -p "$ROOT" | |
| ( | |
| ${pkgs.util-linux}/bin/flock 9 | |
| if [ ! -e "$STAMP" ]; then | |
| ${pkgs.nix}/bin/nix copy --no-check-sigs \ | |
| --to "local?root=$ROOT" \ | |
| ${allPathsStr} | |
| rm -f "$ROOT"/.jail-nix-bootstrapped-* | |
| touch "$STAMP" | |
| fi | |
| ) 9>"$ROOT/.jail-nix-bootstrap.lock" | |
| '' state) | |
| ]; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment