mirror of
https://github.com/NixOS/nix
synced 2025-06-25 02:21:16 +02:00
This adds a setting 'lazy-trees' that causes flake inputs to be "mounted" as virtual filesystems on top of /nix/store as random "virtual" store paths. Only when the store path is actually used as a dependency of a store derivation do we materialize ("devirtualize") the input by copying it to its content-addressed location in the store. String contexts determine when devirtualization happens. One wrinkle is that there are cases where we had store paths without proper contexts, in particular when the user does `toString <path>` (where <path> is a source tree in the Nix store) and passes the result to a derivation. This usage was always broken, since it can result in derivations that lack correct references. But to ensure that we don't change evaluation results, we introduce a new type of context that results in devirtualization but not in store references. We also now print a warning about this.
250 lines
8.6 KiB
Nix
250 lines
8.6 KiB
Nix
{
|
|
lib,
|
|
config,
|
|
nixpkgs,
|
|
...
|
|
}:
|
|
let
|
|
pkgs = config.nodes.client.nixpkgs.pkgs;
|
|
|
|
# Generate a fake root CA and a fake api.github.com / github.com / channels.nixos.org certificate.
|
|
cert = pkgs.runCommand "cert" { nativeBuildInputs = [ pkgs.openssl ]; } ''
|
|
mkdir -p $out
|
|
|
|
openssl genrsa -out ca.key 2048
|
|
openssl req -new -x509 -days 36500 -key ca.key \
|
|
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
|
|
|
|
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
|
|
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr
|
|
openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:channels.nixos.org") \
|
|
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
|
|
'';
|
|
|
|
registry = pkgs.writeTextFile {
|
|
name = "registry";
|
|
text = ''
|
|
{
|
|
"flakes": [
|
|
{
|
|
"from": {
|
|
"type": "indirect",
|
|
"id": "nixpkgs"
|
|
},
|
|
"to": {
|
|
"type": "github",
|
|
"owner": "NixOS",
|
|
"repo": "nixpkgs"
|
|
}
|
|
},
|
|
{
|
|
"from": {
|
|
"type": "indirect",
|
|
"id": "private-flake"
|
|
},
|
|
"to": {
|
|
"type": "github",
|
|
"owner": "fancy-enterprise",
|
|
"repo": "private-flake"
|
|
}
|
|
}
|
|
],
|
|
"version": 2
|
|
}
|
|
'';
|
|
destination = "/flake-registry.json";
|
|
};
|
|
|
|
private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d";
|
|
|
|
private-flake-api = pkgs.runCommand "private-flake" { } ''
|
|
mkdir -p $out/{commits,tarball}
|
|
|
|
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
|
|
echo '{"sha": "${private-flake-rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
|
|
|
|
# Setup tarball download via API
|
|
dir=private-flake
|
|
mkdir $dir
|
|
echo '{ outputs = {...}: {}; }' > $dir/flake.nix
|
|
tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference
|
|
'';
|
|
|
|
nixpkgs-api = pkgs.runCommand "nixpkgs-flake" { } ''
|
|
mkdir -p $out/commits
|
|
|
|
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
|
|
echo '{"sha": "${nixpkgs.rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
|
|
'';
|
|
|
|
archive = pkgs.runCommand "nixpkgs-flake" { } ''
|
|
mkdir -p $out/archive
|
|
|
|
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
|
|
cp -prd ${nixpkgs} $dir
|
|
# Set the correct timestamp in the tarball.
|
|
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${
|
|
builtins.substring 12 2 nixpkgs.lastModifiedDate
|
|
} --
|
|
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
|
|
'';
|
|
in
|
|
|
|
{
|
|
name = "github-flakes";
|
|
|
|
nodes = {
|
|
github =
|
|
{ config, pkgs, ... }:
|
|
{
|
|
networking.firewall.allowedTCPPorts = [
|
|
80
|
|
443
|
|
];
|
|
|
|
services.httpd.enable = true;
|
|
services.httpd.adminAddr = "foo@example.org";
|
|
services.httpd.extraConfig = ''
|
|
ErrorLog syslog:local6
|
|
'';
|
|
services.httpd.virtualHosts."channels.nixos.org" = {
|
|
forceSSL = true;
|
|
sslServerKey = "${cert}/server.key";
|
|
sslServerCert = "${cert}/server.crt";
|
|
servedDirs = [
|
|
{
|
|
urlPath = "/";
|
|
dir = registry;
|
|
}
|
|
];
|
|
};
|
|
services.httpd.virtualHosts."api.github.com" = {
|
|
forceSSL = true;
|
|
sslServerKey = "${cert}/server.key";
|
|
sslServerCert = "${cert}/server.crt";
|
|
servedDirs = [
|
|
{
|
|
urlPath = "/repos/NixOS/nixpkgs";
|
|
dir = nixpkgs-api;
|
|
}
|
|
{
|
|
urlPath = "/repos/fancy-enterprise/private-flake";
|
|
dir = private-flake-api;
|
|
}
|
|
];
|
|
};
|
|
services.httpd.virtualHosts."github.com" = {
|
|
forceSSL = true;
|
|
sslServerKey = "${cert}/server.key";
|
|
sslServerCert = "${cert}/server.crt";
|
|
servedDirs = [
|
|
{
|
|
urlPath = "/NixOS/nixpkgs";
|
|
dir = archive;
|
|
}
|
|
];
|
|
};
|
|
};
|
|
|
|
client =
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
nodes,
|
|
...
|
|
}:
|
|
{
|
|
virtualisation.writableStore = true;
|
|
virtualisation.diskSize = 2048;
|
|
virtualisation.additionalPaths = [
|
|
pkgs.hello
|
|
pkgs.fuse
|
|
];
|
|
virtualisation.memorySize = 4096;
|
|
nix.settings.substituters = lib.mkForce [ ];
|
|
nix.extraOptions = "experimental-features = nix-command flakes";
|
|
networking.hosts.${(builtins.head nodes.github.networking.interfaces.eth1.ipv4.addresses).address} =
|
|
[
|
|
"channels.nixos.org"
|
|
"api.github.com"
|
|
"github.com"
|
|
];
|
|
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
|
|
};
|
|
};
|
|
|
|
testScript =
|
|
{ nodes }:
|
|
''
|
|
# fmt: off
|
|
import json
|
|
import time
|
|
|
|
start_all()
|
|
|
|
def cat_log():
|
|
github.succeed("cat /var/log/httpd/*.log >&2")
|
|
|
|
github.wait_for_unit("httpd.service")
|
|
github.wait_for_unit("network-addresses-eth1.service")
|
|
|
|
client.wait_for_unit("network-addresses-eth1.service")
|
|
client.succeed("curl -v https://github.com/ >&2")
|
|
out = client.succeed("nix registry list")
|
|
print(out)
|
|
assert "github:NixOS/nixpkgs" in out, "nixpkgs flake not found"
|
|
assert "github:fancy-enterprise/private-flake" in out, "private flake not found"
|
|
cat_log()
|
|
|
|
# If no github access token is provided, nix should use the public archive url...
|
|
out = client.succeed("nix flake metadata nixpkgs --json")
|
|
print(out)
|
|
info = json.loads(out)
|
|
assert info["revision"] == "${nixpkgs.rev}", f"revision mismatch: {info['revision']} != ${nixpkgs.rev}"
|
|
cat_log()
|
|
|
|
# ... otherwise it should use the API
|
|
out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0 --no-trust-tarballs-from-git-forges")
|
|
print(out)
|
|
info = json.loads(out)
|
|
assert info["revision"] == "${private-flake-rev}", f"revision mismatch: {info['revision']} != ${private-flake-rev}"
|
|
assert info["fingerprint"]
|
|
cat_log()
|
|
|
|
# Fetching with the resolved URL should produce the same result.
|
|
info2 = json.loads(client.succeed(f"nix flake metadata {info['url']} --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0"))
|
|
print(info["fingerprint"], info2["fingerprint"])
|
|
assert info["fingerprint"] == info2["fingerprint"], "fingerprint mismatch"
|
|
|
|
client.succeed("nix registry pin nixpkgs")
|
|
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
|
|
|
|
# Test fetchTree on a github URL.
|
|
hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'")
|
|
assert hash == info['locked']['narHash']
|
|
|
|
# Fetching with an incorrect NAR hash should fail.
|
|
out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree \"github:fancy-enterprise/private-flake/{info['revision']}?narHash=sha256-HsrRFZYg69qaVe/wDyWBYLeS6ca7ACEJg2Z%2BGpEFw4A%3D\").narHash' 2>&1")
|
|
assert "NAR hash mismatch in input" in out, "NAR hash check did not fail with the expected error"
|
|
|
|
# Fetching without a narHash should succeed if trust-github is set and fail otherwise.
|
|
client.succeed(f"nix eval --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}'")
|
|
out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}' 2>&1")
|
|
assert "will not fetch unlocked input" in out, "--no-trust-tarballs-from-git-forges did not fail with the expected error"
|
|
|
|
# Shut down the web server. The flake should be cached on the client.
|
|
github.succeed("systemctl stop httpd.service")
|
|
|
|
info = json.loads(client.succeed("nix flake metadata nixpkgs --json"))
|
|
date = time.strftime("%Y%m%d%H%M%S", time.gmtime(info['lastModified']))
|
|
assert date == "${nixpkgs.lastModifiedDate}", "time mismatch"
|
|
|
|
client.succeed("nix build nixpkgs#hello")
|
|
|
|
# The build shouldn't fail even with --tarball-ttl 0 (the server
|
|
# being down should not be a fatal error).
|
|
client.succeed("nix build nixpkgs#fuse --tarball-ttl 0")
|
|
'';
|
|
|
|
}
|