feat(lib): add stable service uid registry

This commit was merged in pull request #45.
This commit is contained in:
2026-08-16 14:42:16 -04:00
parent 14da6e5c2e
commit acdc497e4c
5 changed files with 88 additions and 12 deletions

View File

@@ -40,8 +40,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
It is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion <contact@alexion.dev>` with no override.
Verify it that way rather than from this checkout, whose `.git/config` carries the same identity and would mask a broken module.
`~/.gitconfig` (a second global file that outranks the flake-managed `~/.config/git/config`) currently holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, but it is undeclared and will not survive a reimage.
- The primary build/verify seam for any Host is `nix flake check`, which builds `checks.x86_64-linux.<host>` (the system toplevel).
Cheap targeted checks use `nix eval .#nixosConfigurations.<host>.config...`.
- The primary build/verify seam is `nix flake check`, which builds project-level checks plus `checks.x86_64-linux.<host>` system toplevels.
Cheap targeted host checks use `nix eval .#nixosConfigurations.<host>.config...`.
- chaotic-nyx must **not** follow our `nixpkgs`, and its packages are built against chaotic's own pinned nixpkgs (its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path).
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source.
The tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.

View File

@@ -98,13 +98,25 @@
];
};
# `nix flake check` builds each host's toplevel.
checks.x86_64-linux = lib.mapAttrs (
name: host:
if host.config.warnings == [] then
host.config.system.build.toplevel
else
throw "Host ${name} has evaluation warnings:\n${lib.concatStringsSep "\n" host.config.warnings}"
) self.nixosConfigurations;
# `nix flake check` builds host toplevels and project-level checks.
checks.x86_64-linux =
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
serviceIdentities = import ./lib/service-identities.nix { inherit lib; };
serviceIdentityTestsPass = lib.all (result: result) (lib.attrValues serviceIdentities.tests);
in
(lib.mapAttrs (
name: host:
if host.config.warnings == [] then
host.config.system.build.toplevel
else
throw "Host ${name} has evaluation warnings:\n${lib.concatStringsSep "\n" host.config.warnings}"
) self.nixosConfigurations)
// {
service-identities = pkgs.runCommand "service-identities-check" { } ''
${lib.optionalString (!serviceIdentityTestsPass) "exit 1"}
touch $out
'';
};
};
}

View File

@@ -13,6 +13,8 @@ let
mapAttrsToList
;
serviceIdentityRegistry = import ./service-identities.nix { inherit lib; };
# Recursively collect every `.nix` file under `dir` as a flat list, for a
# module's `imports`.
collectNixFiles =
@@ -418,4 +420,6 @@ in
guest
bridgeName
;
inherit (serviceIdentityRegistry) serviceUid;
serviceIdentities = serviceIdentityRegistry.identities;
}

View File

@@ -0,0 +1,59 @@
{ lib }:
let
defaultIdentities = {
actualbudget = 20001;
copyparty = 20002;
};
duplicateUidMessage = duplicates: ''
Duplicate stable service UID allocation(s): ${lib.concatStringsSep ", " (
map (entry: "${toString entry.uid} is assigned to ${lib.concatStringsSep ", " entry.names}") duplicates
)}
'';
mk = identities:
let
names = lib.attrNames identities;
namesByUid = lib.foldl' (
acc: name:
let
uid = toString identities.${name};
in
acc // {
${uid} = (acc.${uid} or [ ]) ++ [ name ];
}
) { } names;
duplicates = lib.mapAttrsToList (uid: namesForUid: {
uid = lib.toInt uid;
names = namesForUid;
}) (lib.filterAttrs (_uid: namesForUid: builtins.length namesForUid > 1) namesByUid);
checkedIdentities =
assert lib.assertMsg (duplicates == [ ]) (duplicateUidMessage duplicates);
identities;
in
rec {
inherit checkedIdentities;
serviceUid = name:
if builtins.hasAttr name checkedIdentities then
checkedIdentities.${name}
else
throw "Unknown stable service identity `${name}`. Known identities: ${lib.concatStringsSep ", " (lib.attrNames checkedIdentities)}";
};
default = mk defaultIdentities;
tests = {
actualbudgetUid = default.serviceUid "actualbudget" == 20001;
copypartyUid = default.serviceUid "copyparty" == 20002;
missingLookupFails = !(builtins.tryEval (default.serviceUid "missing-service")).success;
duplicateUidFails = !(builtins.tryEval (mk {
first = 20003;
second = 20003;
}).checkedIdentities).success;
};
in
default // {
identities = default.checkedIdentities;
inherit tests;
}

View File

@@ -1,6 +1,7 @@
{
config,
lib,
my,
...
}:
let
@@ -30,8 +31,8 @@ in
uid = lib.mkOption {
type = lib.types.ints.positive;
default = 20001;
description = "Fixed uid for the Actual Budget service user.";
default = my.serviceUid "actualbudget";
description = "Stable uid for the Actual Budget service user.";
};
group = lib.mkOption {