Watch
0
0
Fork
You've already forked hyperhive
0

swarm-builder: scope the forge token to the forge, one build at a time, opt-in

argus's review on #5177: the git credential helper answered every `get`,
so a config PR's own flake inputs could have git send the builder's forge
token to any host. The helper is now configured under
`credential.<forge-url>.helper` and itself answers only an https request
whose host is the forge's; a test runs it as git does and checks that
other hosts, http and non-get operations get nothing.

Builds are bounded: every `build_agent` node needs the one `builder` jobq
resource, so one runs at a time and the rest wait pending, and a newer
head cancels its PR's pending build (a running one finishes and writes on
its own commit). The builder also holds a one-permit semaphore around each
build.

`builder` and `review-classifier`, the forge logins swarm-controller owns,
join nix/reserved-names.nix, with a test tying the list to both constants.

The builder is now opt-in like the CI runner: `allSwarmServices` no longer
turns it on, since it runs unreviewed config PRs through the host's nix
daemon. mara had not answered atlas's question on the PR.
This commit is contained in:
atlas 2026-10-12 22:47:30 +02:00
commit cafa120d77
13 changed files with 360 additions and 70 deletions

View file

@ -689,11 +689,13 @@ in
this host's nix daemon, and swarm-controller writes the result as a
`swarm-builder` commit status on the PR.
On by default where
{option}`services.hyperhive.deploy.allSwarmServices` is set and this
host has the builder's store identity (the pair
{option}`services.hyperhive.deploy.bao.builderClientCertFile` and
`builderClientKeyFile`, minted on the store's host).
Not derived from
{option}`services.hyperhive.deploy.allSwarmServices`: the builder
evaluates and builds unreviewed config PRs on this host's nix daemon,
so running one is a decision of its own, as for the CI runner. It needs
the builder's store identity
({option}`services.hyperhive.deploy.bao.builderClientCertFile` and
`builderClientKeyFile`), which the store's host mints.
'';
};

View file

@ -1,8 +1,9 @@
# The swarm builder, in a `swarm-builder` container on the host that sets
# `deploy.builder.enable`, which `deploy.allSwarmServices` defaults on where
# the host has the builder's store identity. It builds agent configs for
# swarm-controller: the controller sends a config PR's head over the queue,
# and the daemon builds that config on this host's agent base and replies.
# `deploy.builder.enable`, which nothing turns on for the operator: it runs
# unreviewed config PRs through the host's nix daemon. It builds agent configs
# for swarm-controller: the controller sends a config PR's head over the
# queue, and the daemon builds that config on this host's agent base and
# replies.
# docs/swarm/builder.md has the design.
#
# ⚠️ Its own network namespace on the bridge, as ./swarm-ci-runner.nix has: a

View file

@ -117,11 +117,4 @@ in
&& deployCfg.bao.reviewClassifierClientCertFile != null
&& deployCfg.bao.reviewClassifierClientKeyFile != null
);
# The swarm builder, defaulted on the same terms as the review classifier.
config.services.hyperhive.deploy.builder.enable = lib.mkDefault (
deployCfg.allSwarmServices
&& deployCfg.bao.builderClientCertFile != null
&& deployCfg.bao.builderClientKeyFile != null
);
}

View file

@ -33,11 +33,6 @@ let
servicesHere = hiveWithController { deploy.allSwarmServices = true; };
servicesBaoElsewhere = hive {
deploy.allSwarmServices = true;
deploy.bao.enable = false;
};
natsHere = hive {
swarm.nats.enable = true;
swarm.nats.authPackage = pkgs.emptyDirectory;
@ -59,16 +54,15 @@ let
ok = !(bare.containers ? swarm-builder) && withBuilder.containers ? swarm-builder;
}
{
# Its module asserts a store identity that only the store's host mints,
# so with the store elsewhere the default stays off rather than failing
# that host's eval. `nats` is the control that the switch is on at all.
name = "the shared services turn the builder on with the store's leaf and leave it off without";
# It runs unreviewed config PRs through the host's nix daemon, so hosting
# the shared services, even with the store's leaf here, does not turn it
# on. `nats` is the control that the switch is on at all.
name = "hosting the shared services leaves the builder off";
ok =
servicesHere.services.hyperhive.deploy.builder.enable
&& servicesHere.containers ? swarm-builder
&& !servicesBaoElsewhere.services.hyperhive.deploy.builder.enable
&& !(servicesBaoElsewhere.containers ? swarm-builder)
&& servicesBaoElsewhere.services.hyperhive.deploy.nats.enable;
!servicesHere.services.hyperhive.deploy.builder.enable
&& !(servicesHere.containers ? swarm-builder)
&& servicesHere.services.hyperhive.deploy.bao.enable
&& servicesHere.services.hyperhive.deploy.nats.enable;
}
{
# A config PR is unreviewed input to an evaluation: the container keeps

View file

@ -472,7 +472,7 @@ let
# that lost a container can't pass by comparing fewer of them.
name = "every service container restates its host-side privateNetwork";
ok =
lib.length (lib.attrNames serviceContainers) == 12
lib.length (lib.attrNames serviceContainers) == 11
&& lib.all (
c: c.privateNetwork == (c.config.services.hyperhive.swarmContainer.privateNetwork or null)
) (lib.attrValues serviceContainers);

View file

@ -22,9 +22,9 @@
# here forbids it for both. That is the point: two lists is how they drift.
#
# ⚠️ Every entry must be a value some component actually PRODUCES as a message
# `from`/`to`, a component name the collector builds pipelines from, or a
# username the forge refuses — not a word that merely looked risky. A name in
# here that nothing emits or refuses is a refusal with no failure behind it.
# `from`/`to`, a component name the collector builds pipelines from, or a forge
# username the forge refuses or a swarm service owns — not a word that merely
# looked risky. A name nothing emits or refuses is a refusal with no failure.
#
# ⚠️ Matched by EQUALITY. Words forbidden *inside* a hive name live in
# `./reserved-hive-fragments.nix` — read its header before merging the two.
@ -90,6 +90,13 @@
"repo-avatars"
"user"
"v2"
# ---- forge accounts swarm-controller owns ----------------------------
# Service accounts it creates under these logins (`forge::builder_account`,
# `forge::classifier_account`). An agent of the same name would be refused
# one node into provisioning, and would keep the service from its token.
"builder"
"review-classifier"
]
# Deliberately absent, and it is a load-bearing omission:
#