Watch
0
0
Fork
You've already forked hyperhive
0

build agents from internal/meta when it pins their config

A rebuild's MetaSync resolves the head of the swarm's internal/meta once and
records it; the prebuild and the swap build that flake instead of the hive's
own, and a first deploy does the same in Create. hive-priv takes the commit as
a validated meta_rev on UpdateContainer and CreateContainer and builds from its
own forge URL (HIVE_PRIV_FORGE_URL).

The head is used only when its flake lists the agent at the config commit the
forge's main is at; otherwise, and when the repo has no commit or this hive has
no core token file for nix's git, the hive renders locally as before.

hive-c0re's and hive-priv's gitconfig now share the forge credential helper and
rewrite the forge's https URLs to http, which hive-priv lacked.
This commit is contained in:
atlas 2026-10-24 14:59:43 +02:00
commit 7c0a622d41
19 changed files with 831 additions and 64 deletions

View file

@ -35,6 +35,11 @@ let
gatewayCfg = config.services.hyperhive.gateway;
};
forgeGit = import ../lib/hive-forge-git.nix {
inherit pkgs;
forgeDomain = config.services.hyperhive.swarm.forge.domain;
};
# Privsep splits ownership across users, so git/libgit2's dubious-
# ownership guard trips on legitimate cross-user reads: hive-priv (root)
# fetches the hive-core-owned meta/applied repos via nix, and hive-c0re
@ -44,24 +49,13 @@ let
# (mid-path globs aren't supported, so per-agent repos can't be listed);
# in practice these processes only ever touch hyperhive's own repos.
#
# The agent config inputs are `git+http` forge URLs pinned by sha (meta.rs
# render). The `[credential]` stanza points git at the `hive-forge` helper
# below, scoped to the forge host, for any `git+http` fetch from it as the
# forge `core` user. hive-c0re's own forge reads use `forge::reader_token()` instead.
#
# git runs every matching `credential.helper`, system config first, so a
# host helper (e.g. `oauth`, which waits on a browser) would run ahead of
# ours. An empty `helper =` resets the list; it comes first at both the
# global and the forge level so no /etc/gitconfig helper survives.
# The forge sections (`forgeGit.config`) answer for the `git+http` forge
# fetches nix makes: the agent config inputs and `internal/meta`. hive-c0re's
# own forge reads use `forge::reader_token()` instead.
safeDirGitconfig = pkgs.writeText "hyperhive-safe-gitconfig" ''
[safe]
directory = *
[credential]
helper =
[credential "http://${config.services.hyperhive.swarm.forge.domain}"]
helper =
helper = hive-forge
username = core
${forgeGit.config}
[http]
# Abort a stalled fetch instead of blocking startup indefinitely. If a
# git http transfer from the forge (e.g. filling an applied mirror)
@ -73,21 +67,6 @@ let
lowSpeedTime = 30
'';
# git credential helper answering for the forge host as `core`. Reads the
# live forge-core admin token
# (`/var/lib/hyperhive/forge-core-token` — paths.rs `FORGE_CORE_TOKEN`) on
# every invocation, so it never holds a stale copy and survives token
# rotation; prints nothing where that file is absent. Scoped to the forge
# host by the `[credential]` stanza above; implements the git
# credential-helper protocol (only `get` answers).
forgeCredHelper = pkgs.writeShellScriptBin "git-credential-hive-forge" ''
[ "''${1:-}" = "get" ] || exit 0
if [ -r /var/lib/hyperhive/forge-core-token ]; then
printf 'username=core\n'
printf 'password=%s\n' "$(cat /var/lib/hyperhive/forge-core-token)"
fi
'';
# The `hive-c0re serve` config JSON. Keys are snake_case to match the
# `ServeConfig` serde shape the daemon deserialises (the
# container-injected HiveEnv fields, flattened, plus the hive-c0re-local
@ -276,7 +255,7 @@ in
pkgs.git
# `git-credential-hive-forge` on PATH so git finds it for git+http
# fetches from the forge (helper = hive-forge).
forgeCredHelper
forgeGit.credentialHelper
"/run/current-system/sw"
];
environment = import ./environment.nix { inherit lib config pkgs; };

View file

@ -10,12 +10,20 @@
let
cfg = config.services.hyperhive.c0re;
# Same safe.directory gitconfig as the c0re unit (see ./hive-c0re)
# — hive-priv (root) runs nix, which fetches the hive-core-owned
# meta/applied repos; libgit2 refuses cross-user reads without it.
forgeGit = import ./lib/hive-forge-git.nix {
inherit pkgs;
forgeDomain = config.services.hyperhive.swarm.forge.domain;
};
# Same gitconfig as the c0re unit (see ./hive-c0re) apart from its stalled-
# transfer limit: hive-priv (root) runs nix, which fetches the hive-core-owned
# meta/applied repos (libgit2 refuses cross-user reads without
# safe.directory) and, from a fresh HOME, the forge inputs of `internal/meta`
# (the forge sections).
safeDirGitconfig = pkgs.writeText "hyperhive-safe-gitconfig" ''
[safe]
directory = *
${forgeGit.config}
'';
in
{
@ -82,6 +90,7 @@ in
pkgs.nixos-container
pkgs.nix # nix, nix-env, nix-instantiate — create + update
pkgs.gitMinimal # git — nix fetches/re-resolves git-source flake inputs
forgeGit.credentialHelper # git-credential-hive-forge — the forge's `git+http` inputs
pkgs.openssh # ssh — nix dispatches builds to remote builders
pkgs.util-linux # umount (nsenter is hardcoded in the script)
pkgs.e2fsprogs # chattr
@ -100,6 +109,9 @@ in
# routing so nixos-container update and the nix prebuild see the
# same store and substituters as every other build context.
NIX_REMOTE = "daemon";
# The forge origin `internal/meta` is built from when a request names
# one of its commits. Fixed here; no request chooses it.
HIVE_PRIV_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}";
};
serviceConfig = {
ExecStart = "${cfg.package}/bin/hive-priv";

View file

@ -0,0 +1,44 @@
# Git settings for the host processes that hand nix a forge URL: hive-c0re
# (prebuilds as `hive-core`) and hive-priv (builds as root, with a HOME of its
# own), each through its own `$HOME/.gitconfig`. nix runs `git` for a `git+…`
# input, so git's config is the only credential and URL plumbing it has.
#
# - `credential`: the forge host answers through `git-credential-hive-forge`,
# which reads the live `core` token file. An empty `helper =` first resets the
# list, so no /etc/gitconfig helper (e.g. `oauth`, which waits on a browser)
# runs ahead of ours.
# - `url`: the swarm's `internal/meta` lock names its inputs by the forge's
# `https` URL, which only the builder's credential helper answers for. They
# are fetched over the `http` URL the helper above is scoped to.
#
# Pure function — NOT a NixOS module (don't add it to the host-modules
# aggregator). Call it from a module's `let`:
#
# forgeGit = import ./lib/hive-forge-git.nix { inherit pkgs; forgeDomain = …; };
# # then put `forgeGit.credentialHelper` on the unit's `path` and
# # `forgeGit.config` into its gitconfig.
{ pkgs, forgeDomain }:
{
# Answers only `get`, for the forge `core` user, from the live token file
# (`/var/lib/hyperhive/forge-core-token`, hive-c0re's `paths::FORGE_CORE_TOKEN`)
# on every invocation, so it never holds a stale copy and survives token
# rotation; prints nothing where the file is absent.
credentialHelper = pkgs.writeShellScriptBin "git-credential-hive-forge" ''
[ "''${1:-}" = "get" ] || exit 0
if [ -r /var/lib/hyperhive/forge-core-token ]; then
printf 'username=core\n'
printf 'password=%s\n' "$(cat /var/lib/hyperhive/forge-core-token)"
fi
'';
config = ''
[credential]
helper =
[credential "http://${forgeDomain}"]
helper =
helper = hive-forge
username = core
[url "http://${forgeDomain}/"]
insteadOf = https://${forgeDomain}/
'';
}

View file

@ -215,6 +215,49 @@ let
}
&& env shared.controllerAlone == { };
}
{
# nix fetches the forge inputs with `git`, so the credential helper and
# the https-to-http rewrite are git config in both processes that build:
# hive-c0re (prebuilds) and hive-priv (builds as root, from its own
# HOME). The controller's unit is the control that the lookup tells a
# unit with the helper from one without; the URL hive-priv builds
# `internal/meta` from is the one hive-c0re reads the forge at.
name = "hive-c0re and hive-priv answer the forge's git fetches and rewrite its https inputs to http";
ok =
let
forge = allLocal.services.hyperhive.swarm.forge.domain;
git = import ../host-modules/lib/hive-forge-git.nix {
inherit pkgs;
forgeDomain = forge;
};
helperOn =
unit:
lib.any (
p: builtins.isAttrs p && (p.name or "") == "git-credential-hive-forge"
) allLocal.systemd.services.${unit}.path;
env = unit: allLocal.systemd.services.${unit}.environment;
in
helperOn "hive-c0re"
&& helperOn "hive-priv"
&& !(helperOn "swarm-controller")
&& lib.hasInfix "[credential \"http://${forge}\"]\nhelper =\nhelper = hive-forge" git.config
&& lib.hasInfix "[url \"http://${forge}/\"]\ninsteadOf = https://${forge}/" git.config
&& (env "hive-priv").HIVE_PRIV_FORGE_URL == "http://${forge}"
&& (env "hive-priv").HIVE_PRIV_FORGE_URL == (env "hive-c0re").HIVE_FORGE_URL;
}
{
# `hyperhive_rev` defaults to none on the controller, so a settings JSON
# without the key is accepted and the pin would never move: the key has
# to be there, carrying the option it is wired from.
name = "the controller hands its running rev to the meta flake settings";
ok =
let
settings = builtins.fromJSON allLocal.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_META_FLAKE;
in
settings ? hyperhive_rev
&& settings.hyperhive_rev == allLocal.services.hyperhive.c0re.hyperhiveRev
&& settings ? git_base;
}
{
# An agent connects to the queue with its own secret, so the hive hands
# its agents no shared credential and no token endpoint to mint one at.