Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/hive-c0re/environment.nix
atlas 221c3f35a1 remove the hive-c0re forge-credential chain nothing reads any more
With no nix build on the hive, the boot publish of the forge token for nix has no reader. Removes the ForgeSweep node, the token file, the git credential helper and hive-c0re's forge gitconfig sections.
2026-10-29 22:55:31 +01:00

315 lines
17 KiB
Nix

# Environment of the hive-c0re daemon unit — a plain function file
# (not a module) returning the env attrset, imported by ./default.nix.
# Everything meta.rs forwards into agent containers or reads for the
# meta-flake render is assembled here.
{
lib,
config,
pkgs,
}:
let
controllerCfg = config.services.hyperhive.deploy.hive-controller;
baoDeploy = config.services.hyperhive.deploy.bao;
baoCfg = config.services.hyperhive.swarm.bao;
# A reader is defined by holding a certificate the store accepts, not by
# sharing a host with the store. Deriving this from
# `deploy.bao.enable` would be the co-location assumption itself.
haveBaoClientIdentity = baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null;
# What an agent reaches directly and never through squid. Squid refuses every
# destination that resolves to the bridge, and the hive's resolver answers the
# bridge address for the gateway's names, so each of these must skip it. A bare
# name matches itself and its subdomains in curl, Node and reqwest.
egressDirectHosts = lib.unique (
lib.filter (n: n != null && n != "") (
[
# Node's NO_PROXY has no CIDR support, so the bridge is listed as the address.
config.services.hyperhive.network.bridgeIp
# The hive's own zone, which dnsmasq answers with the bridge IP.
config.services.hyperhive.domain
# The mTLS store and the queue: not in `serviceDomains`.
config.services.hyperhive.swarm.bao.domain
config.services.hyperhive.swarm.nats.domain
]
# The swarm's apex (the swarm UI and the controller) and its forge, matrix,
# attic, logs, metrics and otel services, wherever they sit. The store and
# queue names are not in it, hence their lines above.
++ config.services.hyperhive.swarm.serviceDomains
# Every name this host's gateway answers with the bridge IP.
++ config.services.hyperhive.gateway.localNames
)
);
in
{
# git writes its config and caches under $HOME. As a system user
# hive-core has no home, so HOME defaults to the unwritable
# /var/empty. Point HOME at the writable StateDirectory.
HOME = "/var/lib/hyperhive";
HYPERHIVE_GIT = "${pkgs.git}/bin/git";
# Never let git block on an interactive credential prompt. hive-core is a
# TTY-less system user, so a prompt (e.g. git asking for credentials
# because the forge isn't reachable yet on cold boot)
# would hang forever and freeze the daemon during startup. With this set,
# git fails fast instead of prompting. Paired with the git http
# low-speed abort in `safeDirGitconfig` (bounds a stalled transfer) and
# the 120s migration shellout timeout in migrate.rs.
GIT_TERMINAL_PROMPT = "0";
# No HIVE_STATIC_DIR: the gateway static-serves the dashboard dist
# (see the hive-gateway module); this router is API-only.
# Path to the base agent frontend dist. hive-c0re's
# gateway_nginx.rs uses this to generate split location
# blocks in agents.conf — static HTML/CSS/JS served from the
# nix store directly; dynamic API paths still proxied to the
# agent daemon. nginx runs on the host, which is where this
# store path already is, so it is reachable as written.
HIVE_AGENT_FRONTEND_DIR = "${controllerCfg.servedFrontend}/agent";
}
// {
# Identity env vars threaded into c0re's own service env and
# forwarded by meta.rs into every sub-agent's harness env —
# full chain in docs/process/conventions.md::Hive identity. `domain` is
# required (asserted in hive-network.nix), so it's always set.
HYPERHIVE_HIVE_DOMAIN = config.services.hyperhive.domain;
}
// lib.optionalAttrs (config.services.hyperhive.deploy.hive-controller.dashboardDomain != null) {
# The host the dashboard and the agent UIs answer on, for the URLs c0re
# hands out (`hivectl open home`, each agent's UI link).
HYPERHIVE_DASHBOARD_DOMAIN = config.services.hyperhive.deploy.hive-controller.dashboardDomain;
}
// lib.optionalAttrs (config.services.hyperhive.deploy.hive-controller.hiveName != null) {
HYPERHIVE_HIVE_NAME = config.services.hyperhive.deploy.hive-controller.hiveName;
}
// lib.optionalAttrs (config.services.hyperhive.swarm.name != null) {
HYPERHIVE_SWARM_NAME = config.services.hyperhive.swarm.name;
}
// lib.optionalAttrs (!config.services.hyperhive.swarm.github.enable) {
# GitHub integration is on by default; only signal the OFF override to
# meta.rs, which then injects `services.hyperhive.agent.github.enable = false` into
# every agent. See services.hyperhive.swarm.github.enable.
HYPERHIVE_GITHUB_DISABLED = "1";
}
// {
# hive-c0re's OWN container-resource exporter (stats/otel_metrics.rs) reads
# the STANDARD OTLP variable — the same one hive-metric and every agent
# read — and lets the SDK resolve the URL, which appends the signal path
# (`/v1/metrics`). Handing the SDK an address programmatically instead
# takes it verbatim: it POSTs to the collector's root, gets a 404 on every
# export, and says nothing, because OTLP export failures go to an error
# handler no binary here installs.
#
# The bridge address, not `otel.collector.host`: on the host that name may
# resolve to loopback (`gateway.localHostsEntry`), where the receiver does
# not listen. Agents are not told an address at all; their module defaults
# to the collector's name.
OTEL_EXPORTER_OTLP_ENDPOINT = "http://${config.services.hyperhive.network.bridgeIp}:${toString config.services.hyperhive.swarm.hiveOtel.collectorPort}";
}
//
lib.optionalAttrs
(config.services.hyperhive.deploy.hive-controller.otel.extraResourceAttributes != "")
{
HYPERHIVE_OTEL_EXTRA_RESOURCE_ATTRIBUTES =
config.services.hyperhive.deploy.hive-controller.otel.extraResourceAttributes;
}
# No upstream credential is emitted here, and its absence is the security half
# of this design: it would put the upstream token where an agent could read
# it. Nothing delivers the token into a container — no nspawn credential, no
# agent-readable header file. The collector holding the credential achieves
# nothing while anything else hands out a copy, so there is exactly one
# holder and it is on the host.
// {
# In-cluster forge URL — the gateway vhost (`forge.<domain>`), which
# nginx proxies to forgejo. Used both for internal API calls in
# hive-c0re (forge/mod.rs `forge_http_base()`) and forwarded to
# agents via meta.rs for their forge-notify client. The forge is
# mandatory, so this is unconditional (the whole env block is already
# gated on hyperhive being enabled). See `docs/networking/gateway.md::HIVE_FORGE_URL`.
HIVE_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}";
# The one blacklist of names an agent may not take — see
# `nix/reserved-names.nix`, which is also read by the swarm controller, by
# the swarm collector's owner assertion, and by the test suite. Nix owns it
# so that keeping it current is a config change, not a rebuild of a binary.
#
# Whitespace-separated rather than JSON: every entry is an `Ident`
# (`[a-z0-9-]`), so a space can never occur inside a name and the encoding
# cannot be lossy. Spelling it as JSON would put a parser in the crate whose
# whole point is to have no dependencies.
#
# Unconditional on purpose. The consumer treats an ABSENT variable as "I was
# never told" and says so out loud, which is the correct reading — but it is
# a reading no correctly-built hive should ever have to make.
HIVE_RESERVED_NAMES = lib.concatStringsSep " " (import ../../reserved-names.nix);
}
// {
# Browser URL of the swarm's SSO provider. The dashboard's and the agent
# page's profile menu link authelia's settings and sign-out pages from it.
# Always set: `swarm.authelia.url` has a default.
HIVE_AUTHELIA_PUBLIC_URL = config.services.hyperhive.swarm.authelia.url;
}
// lib.optionalAttrs (config.services.hyperhive.swarm.matrix.gatewayHost != null) {
# Matrix homeserver URL for each agent's hive-matrix-daemon — the
# gateway vhost (`chat.<swarm-domain>`). Forwarded to agents by meta.rs
# alongside HIVE_FORGE_URL; shares the same env-forwarding ordering
# caveat (value baked at config-generation time).
#
# Deliberately NOT conditioned on this host running the homeserver. A
# swarm has one matrix, and every hive's agents talk to it — the hive
# that hosts it is not the only hive whose agents need its address.
# `gatewayHost` is swarm-scoped and names that one homeserver from any
# member hive, so its being set is the whole question.
#
# Null forwards nothing rather than falling back to loopback. That
# fallback reads as harmless because hive-c0re shares the host netns —
# but the value is handed to *agents*, which do not, so `127.0.0.1`
# there names the agent itself. An absent forward leaves
# `services.hyperhive.agent.matrix.url` null and the daemon no-ops; that is the honest
# answer when there is no matrix vhost to point at.
HIVE_MATRIX_URL = "http://${config.services.hyperhive.swarm.matrix.gatewayHost}";
}
// {
# The gateway always runs, so the dashboard always builds
# same-origin `/agent/<name>/` links (never the direct
# `<host>:<port>` TCP fallback). Kept as an env flag so the
# dashboard doesn't need to learn the gateway is unconditional.
HIVE_GATEWAY_ENABLED = "1";
}
// lib.optionalAttrs (config.services.hyperhive.swarm.forge.publicUrl != null) {
# Public URL of the forge, for the dashboard to build browser-facing
# forge links from instead of guessing `<hostname>:3000` (which
# breaks the moment the operator's browser hostname isn't the forge
# host, e.g. through the gateway or a reverse proxy). Sourced from
# `services.hyperhive.swarm.forge.publicUrl`, which itself defaults to the
# gateway vhost URL — see that option's doc for the "hide, don't guess"
# rationale.
# Absent here whenever `publicUrl` is `null`; the dashboard hides
# forge links rather than emitting one it can't justify.
HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.swarm.forge.publicUrl;
}
// lib.optionalAttrs (config.services.hyperhive.swarm.domain != null) {
# Browser URL of the swarm UI, which answers on the swarm apex domain
# (see ../swarm-ui.nix). The dashboard links to it from its pages menu.
# Absent when the domain is unset, so the link is hidden rather than
# guessed.
HIVE_SWARM_PUBLIC_URL = "https://${config.services.hyperhive.swarm.domain}/";
}
// lib.optionalAttrs (config.services.hyperhive.swarm.matrix.gatewayHost != null) {
# Browser-facing matrix GUI (fluffychat) URL — the gateway
# vhost (`gatewayHost`, `chat.<swarm-domain>` by default). Surfaced via the daemon's `Urls`
# request for `hivectl open matrix`. Absent when no gatewayHost is
# set (no browser-reachable matrix vhost).
HIVE_MATRIX_PUBLIC_URL = "https://${config.services.hyperhive.swarm.matrix.gatewayHost}/";
}
// lib.optionalAttrs (config.services.hyperhive.swarm.snapshotStore.address != null) {
# `host:port` of the swarm's single snapshot store, for pushing agent
# snapshots (hive-c0re::snapshot_push). One per swarm, not one per
# peer — the receiver keys destinations by agent so a migrating agent
# keeps one incremental chain. Absent when no store is configured, and
# a push then fails naming the option rather than guessing.
HYPERHIVE_SNAPSHOT_STORE =
let
s = config.services.hyperhive.swarm.snapshotStore;
in
"${s.address}:${toString s.port}";
}
//
# Swarm-queue coordinates for offering this hive's status upward
# (hive-c0re::swarm_status). All three together or none: a half-set
# environment is a deployment bug the daemon refuses to treat as
# "no queue configured", because the failure it would otherwise
# produce is a hive that comes up fine and silently never reports.
#
# The client secret is not among them: the daemon reads it from the
# swarm's secret store under this hive's own certificate
# (`swarm_secret_client::queue::hive_client_path`), so a hive with no
# store identity has no way to publish and is handed nothing.
lib.optionalAttrs
(
config.services.hyperhive.deploy.hive-controller.statusPublish.natsUrl != null
&& config.services.hyperhive.swarm.statusPublish.tokenEndpoint != null
&& haveBaoClientIdentity
)
{
HIVE_C0RE_NATS_URL = config.services.hyperhive.deploy.hive-controller.statusPublish.natsUrl;
HIVE_C0RE_OIDC_TOKEN_ENDPOINT = config.services.hyperhive.swarm.statusPublish.tokenEndpoint;
# The identity swarm-authelia.nix already declares for every entry in
# `swarm.hives` — the hive does not choose its own name here, it uses
# the one the roster gave it.
HIVE_C0RE_OIDC_CLIENT_ID = "hive-${config.services.hyperhive.deploy.hive-controller.hiveName}";
}
//
# Where the agents reach the queue: the address goes into every container in
# its hive facts (`hive_c0re::lifecycle::hive_facts`).
#
# ⚠️ By name, like `HIVE_C0RE_NATS_URL` above, and never a loopback
# address: inside a container `127.0.0.1` is the agent itself.
lib.optionalAttrs (config.services.hyperhive.deploy.hive-controller.queue.agentNatsUrl != null) {
HIVE_AGENT_NATS_URL = config.services.hyperhive.deploy.hive-controller.queue.agentNatsUrl;
}
//
# Where an agent reads the swarm's logs. Gated on the store identity below,
# because `swarm-logs` authenticates with the agent's own queue secret, read
# from the store: an address with nothing to authenticate as would put a
# binary on PATH that can only ever answer 401, which an agent reads as "no
# logs".
#
# The **machine** route (`^~ /select/logsql/`), not the browser one at `/`:
# that one ends in `error_page 401 =302` and hands a machine caller
# authelia's login page as a 200 with an HTML body. See
# `swarm-victorialogs.nix`'s location for the whole reasoning.
lib.optionalAttrs haveBaoClientIdentity {
HIVE_AGENT_LOGS_QUERY_URL = "https://${config.services.hyperhive.swarm.victorialogs.domain}/select/logsql/query";
# The metrics store's origin, for `swarm-metrics`; same credential and
# gate. Its read locations answer a bare 401, never a login redirect.
HIVE_AGENT_METRICS_URL = "https://${config.services.hyperhive.swarm.victoriametrics.domain}";
}
//
# Where the swarm's secret store is, and the identity this hive presents to
# it (hive-c0re::lifecycle::agent_identity). `swarm_secret_client` reads these
# spellings explicitly rather than vaultrs's `VAULT_*` defaults — falling
# through to those would build a client with no identity and fail at the TLS
# handshake, naming neither.
lib.optionalAttrs haveBaoClientIdentity {
BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
# The same address again under an agent-facing name, forwarded into every
# container's module block by `hive_meta_flake::render_flake`. Deliberately not the
# `BAO_ADDR` line itself: a container that inherited this hive's store
# environment wholesale would inherit the hive's certificate paths with it,
# and those name a credential no agent may present.
HIVE_AGENT_BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
# The mount whose root an agent fetches to trust the swarm's services; the
# same value ../hive-tls.nix reads the root from.
HIVE_AGENT_BAO_SERVICES_PKI_MOUNT = baoDeploy.servicesPkiMountPath;
# `%d`, not the paths themselves: the key is `0600` root-owned and this
# daemon runs as hive-core, so it never gets read access to the original.
# See the LoadCredential in ./default.nix.
BAO_CLIENT_CERT = "%d/bao-client.pem";
BAO_CLIENT_KEY = "%d/bao-client-key.pem";
# Where `workers::squid_users` renders the hashes of this hive's agents' proxy
# credentials for squid, and the list of the users whose agent holds the
# swarm's `unrestricted_internet` flag; it reads the credentials from the
# store under the identity above.
HIVE_SQUID_USERS_FILE = config.services.hyperhive.deploy.hive-controller.squid.usersFile;
HIVE_SQUID_UNRESTRICTED_FILE =
config.services.hyperhive.deploy.hive-controller.squid.unrestrictedFile;
# The swarm's egress allow-list, which `workers::squid_allow` renders into
# the two files; the seed is the list the files hold until the swarm has one,
# and the always-allowed names are those it never repeats.
HIVE_SQUID_ALLOWED_FILE = config.services.hyperhive.deploy.hive-controller.squid.allowedFile;
HIVE_SQUID_AGENT_ALLOW_FILE = config.services.hyperhive.deploy.hive-controller.squid.agentAllowFile;
HIVE_SQUID_ALLOWED_SEED_FILE =
config.services.hyperhive.deploy.hive-controller.squid.allowedSeedFile;
HIVE_SQUID_ALWAYS_ALLOWED = lib.concatStringsSep " " config.services.hyperhive.deploy.hive-controller.squid.alwaysAllowed;
# The egress proxy facts every container is handed (`HiveFacts` in
# `hive-priv-sock`). They sit with the users file above because that is what
# makes squid's credential check and per-agent policy real on this host: an
# agent sends nothing through the proxy unless it reads both. Absent on a
# hive that predates them, so its agents go direct.
HIVE_SQUID_PROXY = config.services.hyperhive.deploy.hive-controller.squid.listen;
HIVE_EGRESS_NO_PROXY = lib.concatStringsSep "," egressDirectHosts;
}
// lib.optionalAttrs (haveBaoClientIdentity && baoDeploy.serverCaFile != null) {
# Absent means the system trust store — right for a deployment with a real
# CA, wrong for the self-signed one ../glue-bao-tls.nix mints, which is why
# that file names this path rather than leaving it to a default.
BAO_CACERT = "%d/bao-ca.pem";
}