move links, adminSync.ignoreUsers and scrapeTargets under their service's deploy namespace
This commit is contained in:
parent
ee591b0546
commit
90272ec3c0
19 changed files with 156 additions and 108 deletions
|
|
@ -217,7 +217,7 @@ lands on.
|
||||||
- **Admins the sync didn't make keep admin.** An admin missing from that
|
- **Admins the sync didn't make keep admin.** An admin missing from that
|
||||||
file, `@swarm`, the server user `@conduit:<server_name>`, every agent's
|
file, `@swarm`, the server user `@conduit:<server_name>`, every agent's
|
||||||
account, and every user id in
|
account, and every user id in
|
||||||
`services.hyperhive.swarm.matrix.adminSync.ignoreUsers` are never
|
`services.hyperhive.deploy.swarm-controller.matrixAdminSync.ignoreUsers` are never
|
||||||
revoked.
|
revoked.
|
||||||
- **The last admin stays.** A revoke that would leave no admin in
|
- **The last admin stays.** A revoke that would leave no admin in
|
||||||
`#admins` besides `@swarm` and the server user isn't sent; the
|
`#admins` besides `@swarm` and the server user isn't sent; the
|
||||||
|
|
|
||||||
|
|
@ -945,7 +945,7 @@ the subrequest denies it.
|
||||||
The swarm UI's header carries a single 🔗 button, visible on every route,
|
The swarm UI's header carries a single 🔗 button, visible on every route,
|
||||||
opening a popover of links to other swarm-wide services. Backed by
|
opening a popover of links to other swarm-wide services. Backed by
|
||||||
`GET /api/links` (swarm-controller), which serves
|
`GET /api/links` (swarm-controller), which serves
|
||||||
`services.hyperhive.swarm.controller.links` (a `listOf { label, icon, url }`,
|
`services.hyperhive.deploy.swarm-controller.links` (a `listOf { label, icon, url }`,
|
||||||
same shape as the per-agent `services.hyperhive.agent.dashboardLinks`).
|
same shape as the per-agent `services.hyperhive.agent.dashboardLinks`).
|
||||||
|
|
||||||
Links to swarm services come from swarm-level options, so the list is the
|
Links to swarm services come from swarm-level options, so the list is the
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,18 @@ in
|
||||||
[ "services" "hyperhive" "c0re" "nixpkgsFlake" ]
|
[ "services" "hyperhive" "c0re" "nixpkgsFlake" ]
|
||||||
[ "services" "hyperhive" "deploy" "builder" "nixpkgsFlake" ]
|
[ "services" "hyperhive" "deploy" "builder" "nixpkgsFlake" ]
|
||||||
)
|
)
|
||||||
|
(lib.mkRenamedOptionModule
|
||||||
|
[ "services" "hyperhive" "swarm" "controller" "links" ]
|
||||||
|
[ "services" "hyperhive" "deploy" "swarm-controller" "links" ]
|
||||||
|
)
|
||||||
|
(lib.mkRenamedOptionModule
|
||||||
|
[ "services" "hyperhive" "swarm" "matrix" "adminSync" "ignoreUsers" ]
|
||||||
|
[ "services" "hyperhive" "deploy" "swarm-controller" "matrixAdminSync" "ignoreUsers" ]
|
||||||
|
)
|
||||||
|
(lib.mkRenamedOptionModule
|
||||||
|
[ "services" "hyperhive" "swarm" "otel" "scrapeTargets" ]
|
||||||
|
[ "services" "hyperhive" "deploy" "swarm-otel" "scrapeTargets" ]
|
||||||
|
)
|
||||||
# The homeserver's port and its firewall switch, removed rather than
|
# The homeserver's port and its firewall switch, removed rather than
|
||||||
# renamed: tuwunel listens on a unix socket only the gateway reaches, so
|
# renamed: tuwunel listens on a unix socket only the gateway reaches, so
|
||||||
# there is no host port to choose or open.
|
# there is no host port to choose or open.
|
||||||
|
|
|
||||||
|
|
@ -90,18 +90,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
adminSync.ignoreUsers = lib.mkOption {
|
|
||||||
type = lib.types.listOf (lib.types.strMatching "@[^:[:space:]]+:[^[:space:]]+");
|
|
||||||
default = [ ];
|
|
||||||
example = [ "@mara:example.org" ];
|
|
||||||
description = ''
|
|
||||||
Full matrix user ids `swarm-controller`'s operator admin sync never
|
|
||||||
revokes homeserver admin from. The sync revokes admin only from
|
|
||||||
accounts it made admin itself, once their owner leaves authelia's
|
|
||||||
`admins` group; an account listed here keeps admin regardless.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# This homeserver always delegates login to the swarm's authelia, as
|
# This homeserver always delegates login to the swarm's authelia, as
|
||||||
# an OIDC relying party — matrix SSO (`m.login.sso`), offered
|
# an OIDC relying party — matrix SSO (`m.login.sso`), offered
|
||||||
# alongside password login. No toggle: a homeserver in a swarm is a
|
# alongside password login. No toggle: a homeserver in a swarm is a
|
||||||
|
|
|
||||||
|
|
@ -3033,7 +3033,7 @@ in
|
||||||
# granter step, on the host where that step runs.
|
# granter step, on the host where that step runs.
|
||||||
environment.etc."hyperhive/bao-bootstrap-policy.hcl".source = ./bao-bootstrap-policy.hcl;
|
environment.etc."hyperhive/bao-bootstrap-policy.hcl".source = ./bao-bootstrap-policy.hcl;
|
||||||
|
|
||||||
# 🚫 No `swarm.otel.scrapeTargets.bao` entry, and its absence is
|
# 🚫 No `deploy.swarm-otel.scrapeTargets.bao` entry, and its absence is
|
||||||
# deliberate. That option is read only by the SWARM collector, over
|
# deliberate. That option is read only by the SWARM collector, over
|
||||||
# loopback, so a store declared there would have metrics exactly on the
|
# loopback, so a store declared there would have metrics exactly on the
|
||||||
# hosts that also run swarm-otel and nowhere else — silently, since a
|
# hosts that also run swarm-otel and nowhere else — silently, since a
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,20 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
# What stays above is what the controller IS to the swarm:
|
||||||
|
# the client id it is registered under, and how stale a
|
||||||
|
# snapshot may read. What the host running it decides is here — the quick
|
||||||
|
# links its UI serves, the accounts its matrix admin sync leaves alone, which two
|
||||||
|
# builds it runs, where its socket lives, and the three credentials it reads
|
||||||
|
# off local disk. `enable` already lives in ./deploy.nix, which also carries
|
||||||
|
# the renames.
|
||||||
|
#
|
||||||
|
# ⚠️ `deploy.swarm-controller`, NOT `deploy.hive-controller`. Both exist:
|
||||||
|
# the latter is hive-c0re's namespace, where `tls.*` lives. A definition on
|
||||||
|
# the wrong one lands on a live, unrelated option rather than failing.
|
||||||
|
options.services.hyperhive.deploy.swarm-controller = {
|
||||||
links = lib.mkOption {
|
links = lib.mkOption {
|
||||||
type = lib.types.listOf (
|
type = lib.types.listOf (
|
||||||
lib.types.submodule {
|
lib.types.submodule {
|
||||||
|
|
@ -437,19 +451,18 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
matrixAdminSync.ignoreUsers = lib.mkOption {
|
||||||
|
type = lib.types.listOf (lib.types.strMatching "@[^:[:space:]]+:[^[:space:]]+");
|
||||||
|
default = [ ];
|
||||||
|
example = [ "@mara:example.org" ];
|
||||||
|
description = ''
|
||||||
|
Full matrix user ids the controller's operator admin sync never
|
||||||
|
revokes homeserver admin from. The sync revokes admin only from
|
||||||
|
accounts it made admin itself, once their owner leaves authelia's
|
||||||
|
`admins` group; an account listed here keeps admin regardless.
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# What stays above is what the controller IS to the swarm: the quick-links
|
|
||||||
# it publishes, the client id it is registered under, and how stale a
|
|
||||||
# snapshot may read. What the host running it decides is here — which two
|
|
||||||
# builds it runs, where its socket lives, and the three credentials it reads
|
|
||||||
# off local disk. `enable` already lives in ./deploy.nix, which also carries
|
|
||||||
# the renames.
|
|
||||||
#
|
|
||||||
# ⚠️ `deploy.swarm-controller`, NOT `deploy.hive-controller`. Both exist:
|
|
||||||
# the latter is hive-c0re's namespace, where `tls.*` lives. A definition on
|
|
||||||
# the wrong one lands on a live, unrelated option rather than failing.
|
|
||||||
options.services.hyperhive.deploy.swarm-controller = {
|
|
||||||
queue = {
|
queue = {
|
||||||
natsUrl = lib.mkOption {
|
natsUrl = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -662,7 +675,7 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf deployCfg.swarm-controller.enable {
|
config = lib.mkIf deployCfg.swarm-controller.enable {
|
||||||
services.hyperhive.swarm.controller.links = swarmServiceLinks;
|
services.hyperhive.deploy.swarm-controller.links = swarmServiceLinks;
|
||||||
|
|
||||||
users.users.swarm-controller = {
|
users.users.swarm-controller = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
|
|
@ -848,7 +861,7 @@ in
|
||||||
# The merged links list — see `links`' description above for who
|
# The merged links list — see `links`' description above for who
|
||||||
# contributes to it. Consumed by `GET /api/links`
|
# contributes to it. Consumed by `GET /api/links`
|
||||||
# (swarm-controller/src/startup.rs::load_links).
|
# (swarm-controller/src/startup.rs::load_links).
|
||||||
SWARM_CONTROLLER_LINKS = builtins.toJSON cfg.links;
|
SWARM_CONTROLLER_LINKS = builtins.toJSON deployCfg.swarm-controller.links;
|
||||||
# Staleness threshold for `GET /api/hives/status` — see
|
# Staleness threshold for `GET /api/hives/status` — see
|
||||||
# `staleAfterSeconds`' description. Set unconditionally rather
|
# `staleAfterSeconds`' description. Set unconditionally rather
|
||||||
# than inside `queueEnv`: it is not a queue coordinate, and
|
# than inside `queueEnv`: it is not a queue coordinate, and
|
||||||
|
|
@ -858,7 +871,7 @@ in
|
||||||
# The accounts the operator admin sync never revokes. Whitespace-
|
# The accounts the operator admin sync never revokes. Whitespace-
|
||||||
# separated: a matrix user id cannot contain a space. Consumed by
|
# separated: a matrix user id cannot contain a space. Consumed by
|
||||||
# swarm-controller/src/matrix_account/operator_admin.rs.
|
# swarm-controller/src/matrix_account/operator_admin.rs.
|
||||||
SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS = lib.concatStringsSep " " matrixCfg.adminSync.ignoreUsers;
|
SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS = lib.concatStringsSep " " deployCfg.swarm-controller.matrixAdminSync.ignoreUsers;
|
||||||
# Where `GET /api/alerts` reads alerts (swarm-controller/src/alerts.rs).
|
# Where `GET /api/alerts` reads alerts (swarm-controller/src/alerts.rs).
|
||||||
# Every swarm has one Grafana, reached by name wherever it runs. The
|
# Every swarm has one Grafana, reached by name wherever it runs. The
|
||||||
# token is in the store, written by `swarm-bao-grafana-controller-token`
|
# token is in the store, written by `swarm-bao-grafana-controller-token`
|
||||||
|
|
|
||||||
|
|
@ -138,58 +138,6 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
scrapeTargets = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
example = lib.literalExpression ''{ collector = "127.0.0.1:8889"; }'';
|
|
||||||
description = ''
|
|
||||||
Prometheus exposition endpoints this collector scrapes, as
|
|
||||||
`<job name> = "<host>:<port>"`. The collector runs in a network namespace of
|
|
||||||
its own, so `127.0.0.1` is the collector container's loopback: a target
|
|
||||||
here is a listener inside that container, such as its own telemetry.
|
|
||||||
|
|
||||||
A path and query may follow the port —
|
|
||||||
`"127.0.0.1:9100/stats?format=prometheus"` — for an
|
|
||||||
exporter that does not serve `/metrics`. Both are optional and
|
|
||||||
omitted when absent, so a bare `host:port` is scraped exactly as
|
|
||||||
before.
|
|
||||||
|
|
||||||
**A service declares its own entry, from its own module, under its
|
|
||||||
own `enable`.** Do not assemble the list here: an entry then exists
|
|
||||||
only where the service that named it runs, so a target is never
|
|
||||||
declared on a host that does not serve it. A swarm service on the
|
|
||||||
host is not reachable here, since its loopback is not the collector's:
|
|
||||||
it is a `publishedScrapeTargets` entry instead, which a collector on
|
|
||||||
any host scrapes.
|
|
||||||
|
|
||||||
⚠️ **That constrains the TARGET, not the SCRAPER, and the difference
|
|
||||||
is a silent gap.** Nothing here puts the collector on the same host.
|
|
||||||
Services are co-located by *default* — `swarm-required-services.nix`
|
|
||||||
derives their `enable` flags from one `lib.mkDefault` — not by
|
|
||||||
construction, and an operator may split them.
|
|
||||||
|
|
||||||
When they are split the target is simply never scraped: the
|
|
||||||
service's host declares an entry no local collector reads, and the
|
|
||||||
collector's host never enabled that service so has no entry at all.
|
|
||||||
No error, no eval failure, no warning.
|
|
||||||
|
|
||||||
**No assertion can catch this.** Two hosts are separate NixOS
|
|
||||||
evaluations with no shared context, so neither can see what the
|
|
||||||
other runs. A service that would be seriously wrong to lose should
|
|
||||||
say so in its own contribution, because saying it is the only
|
|
||||||
mechanism available.
|
|
||||||
|
|
||||||
Samples land in a swarm-level pipeline that stamps `swarm` and
|
|
||||||
**never** `hive`: a swarm service does not belong to a hive, and
|
|
||||||
`hive` stays a property of which authenticated receiver accepted a
|
|
||||||
push, not something a scrape can acquire.
|
|
||||||
|
|
||||||
Empty by default, in which case no scrape receiver, processor or
|
|
||||||
pipeline is emitted at all — an enabled scraper with nothing to
|
|
||||||
scrape is the inert configuration this option exists to avoid.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
publishedScrapeTargets = lib.mkOption {
|
publishedScrapeTargets = lib.mkOption {
|
||||||
type = lib.types.attrsOf lib.types.str;
|
type = lib.types.attrsOf lib.types.str;
|
||||||
default = { };
|
default = { };
|
||||||
|
|
@ -200,7 +148,7 @@ in
|
||||||
|
|
||||||
**Entries are declared swarm-wide**: from this module's `config`,
|
**Entries are declared swarm-wide**: from this module's `config`,
|
||||||
unconditionally, keyed on the service's `swarm.<svc>.domain`, so
|
unconditionally, keyed on the service's `swarm.<svc>.domain`, so
|
||||||
every host evaluates the same set. Unlike `scrapeTargets`, an entry
|
every host evaluates the same set. Unlike `deploy.swarm-otel.scrapeTargets`, an entry
|
||||||
is not declared under the service's own `enable`: the collector
|
is not declared under the service's own `enable`: the collector
|
||||||
reads this list on its host and authelia's registration reads it on
|
reads this list on its host and authelia's registration reads it on
|
||||||
authelia's host, and neither need be the service's host. A swarm
|
authelia's host, and neither need be the service's host. A swarm
|
||||||
|
|
@ -222,7 +170,7 @@ in
|
||||||
slash, `http` for `https`) presents as a valid token rejected at
|
slash, `http` for `https`) presents as a valid token rejected at
|
||||||
the target, several layers from its cause.
|
the target, several layers from its cause.
|
||||||
|
|
||||||
⚠️ Deliberately separate from `scrapeTargets`. An entry there is
|
⚠️ Deliberately separate from `deploy.swarm-otel.scrapeTargets`. An entry there is
|
||||||
trusted because the scraper and the target share a host — that
|
trusted because the scraper and the target share a host — that
|
||||||
option is loopback-and-unauthenticated by contract. An entry here
|
option is loopback-and-unauthenticated by contract. An entry here
|
||||||
is trusted because it presents a credential. One shape for both
|
is trusted because it presents a credential. One shape for both
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ let
|
||||||
cfg = config.services.hyperhive.swarm.otel;
|
cfg = config.services.hyperhive.swarm.otel;
|
||||||
deployCfg = config.services.hyperhive.deploy;
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
upstream = deployCfg.swarm-otel.upstream;
|
upstream = deployCfg.swarm-otel.upstream;
|
||||||
|
scrapeTargets = deployCfg.swarm-otel.scrapeTargets;
|
||||||
vmCfg = config.services.hyperhive.swarm.victoriametrics;
|
vmCfg = config.services.hyperhive.swarm.victoriametrics;
|
||||||
vlCfg = config.services.hyperhive.swarm.victorialogs;
|
vlCfg = config.services.hyperhive.swarm.victorialogs;
|
||||||
hyperhiveCfg = config.services.hyperhive;
|
hyperhiveCfg = config.services.hyperhive;
|
||||||
|
|
@ -333,6 +334,58 @@ in
|
||||||
# operator's choice for this deployment, so both hang off the deployment.
|
# operator's choice for this deployment, so both hang off the deployment.
|
||||||
# `enable` already lives in ./deploy.nix, which also carries the rename.
|
# `enable` already lives in ./deploy.nix, which also carries the rename.
|
||||||
options.services.hyperhive.deploy.swarm-otel = {
|
options.services.hyperhive.deploy.swarm-otel = {
|
||||||
|
scrapeTargets = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''{ collector = "127.0.0.1:8889"; }'';
|
||||||
|
description = ''
|
||||||
|
Prometheus exposition endpoints this collector scrapes, as
|
||||||
|
`<job name> = "<host>:<port>"`. The collector runs in a network namespace of
|
||||||
|
its own, so `127.0.0.1` is the collector container's loopback: a target
|
||||||
|
here is a listener inside that container, such as its own telemetry.
|
||||||
|
|
||||||
|
A path and query may follow the port —
|
||||||
|
`"127.0.0.1:9100/stats?format=prometheus"` — for an
|
||||||
|
exporter that does not serve `/metrics`. Both are optional and
|
||||||
|
omitted when absent, so a bare `host:port` is scraped exactly as
|
||||||
|
before.
|
||||||
|
|
||||||
|
**A service declares its own entry, from its own module, under its
|
||||||
|
own `enable`.** Do not assemble the list here: an entry then exists
|
||||||
|
only where the service that named it runs, so a target is never
|
||||||
|
declared on a host that does not serve it. A swarm service on the
|
||||||
|
host is not reachable here, since its loopback is not the collector's:
|
||||||
|
it is a `publishedScrapeTargets` entry instead, which a collector on
|
||||||
|
any host scrapes.
|
||||||
|
|
||||||
|
⚠️ **That constrains the TARGET, not the SCRAPER, and the difference
|
||||||
|
is a silent gap.** Nothing here puts the collector on the same host.
|
||||||
|
Services are co-located by *default* — `swarm-required-services.nix`
|
||||||
|
derives their `enable` flags from one `lib.mkDefault` — not by
|
||||||
|
construction, and an operator may split them.
|
||||||
|
|
||||||
|
When they are split the target is simply never scraped: the
|
||||||
|
service's host declares an entry no local collector reads, and the
|
||||||
|
collector's host never enabled that service so has no entry at all.
|
||||||
|
No error, no eval failure, no warning.
|
||||||
|
|
||||||
|
**No assertion can catch this.** Two hosts are separate NixOS
|
||||||
|
evaluations with no shared context, so neither can see what the
|
||||||
|
other runs. A service that would be seriously wrong to lose should
|
||||||
|
say so in its own contribution, because saying it is the only
|
||||||
|
mechanism available.
|
||||||
|
|
||||||
|
Samples land in a swarm-level pipeline that stamps `swarm` and
|
||||||
|
**never** `hive`: a swarm service does not belong to a hive, and
|
||||||
|
`hive` stays a property of which authenticated receiver accepted a
|
||||||
|
push, not something a scrape can acquire.
|
||||||
|
|
||||||
|
Empty by default, in which case no scrape receiver, processor or
|
||||||
|
pipeline is emitted at all — an enabled scraper with nothing to
|
||||||
|
scrape is the inert configuration this option exists to avoid.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
telemetryPort = lib.mkOption {
|
telemetryPort = lib.mkOption {
|
||||||
type = lib.types.port;
|
type = lib.types.port;
|
||||||
default = 8889;
|
default = 8889;
|
||||||
|
|
@ -498,7 +551,7 @@ in
|
||||||
# alive, these counters say whether it is dropping what it receives.
|
# alive, these counters say whether it is dropping what it receives.
|
||||||
# Loopback is the container's own: this collector scrapes `telemetryPort`
|
# Loopback is the container's own: this collector scrapes `telemetryPort`
|
||||||
# inside its netns, and nothing else reads it.
|
# inside its netns, and nothing else reads it.
|
||||||
services.hyperhive.swarm.otel.scrapeTargets.collector =
|
services.hyperhive.deploy.swarm-otel.scrapeTargets.collector =
|
||||||
"127.0.0.1:${toString deployCfg.swarm-otel.telemetryPort}";
|
"127.0.0.1:${toString deployCfg.swarm-otel.telemetryPort}";
|
||||||
|
|
||||||
# OTLP/HTTP, not a browsable UI, but the same reverse-proxy shape as
|
# OTLP/HTTP, not a browsable UI, but the same reverse-proxy shape as
|
||||||
|
|
@ -935,11 +988,11 @@ in
|
||||||
# Same reason the published one is asserted: a value the grammar
|
# Same reason the published one is asserted: a value the grammar
|
||||||
# rejects surfaces as `attempt to call elemAt on null` from inside
|
# rejects surfaces as `attempt to call elemAt on null` from inside
|
||||||
# the renderer, naming neither the option nor the offending value.
|
# the renderer, naming neither the option nor the offending value.
|
||||||
assertion = lib.all (t: parseScrape t != null) (lib.attrValues cfg.scrapeTargets);
|
assertion = lib.all (t: parseScrape t != null) (lib.attrValues scrapeTargets);
|
||||||
message = ''
|
message = ''
|
||||||
services.hyperhive.swarm.otel.scrapeTargets has ${
|
services.hyperhive.deploy.swarm-otel.scrapeTargets has ${
|
||||||
lib.concatMapStringsSep ", " (kv: "${kv.name} = \"${kv.value}\"") (
|
lib.concatMapStringsSep ", " (kv: "${kv.name} = \"${kv.value}\"") (
|
||||||
lib.filter (kv: parseScrape kv.value == null) (lib.attrsToList cfg.scrapeTargets)
|
lib.filter (kv: parseScrape kv.value == null) (lib.attrsToList scrapeTargets)
|
||||||
)
|
)
|
||||||
}, which is not of the form <host>:<port>[/<path>][?<k>=<v>].
|
}, which is not of the form <host>:<port>[/<path>][?<k>=<v>].
|
||||||
|
|
||||||
|
|
@ -959,12 +1012,11 @@ in
|
||||||
# The message names which side each collision came from, which is
|
# The message names which side each collision came from, which is
|
||||||
# the part a rendered-config error could not tell an operator.
|
# the part a rendered-config error could not tell an operator.
|
||||||
assertion =
|
assertion =
|
||||||
lib.intersectLists (lib.attrNames cfg.scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
|
lib.intersectLists (lib.attrNames scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets) == [ ];
|
||||||
== [ ];
|
|
||||||
message = ''
|
message = ''
|
||||||
services.hyperhive.swarm.otel: ${
|
services.hyperhive.swarm.otel: ${
|
||||||
lib.concatMapStringsSep ", " (j: "'${j}'") (
|
lib.concatMapStringsSep ", " (j: "'${j}'") (
|
||||||
lib.intersectLists (lib.attrNames cfg.scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
|
lib.intersectLists (lib.attrNames scrapeTargets) (lib.attrNames cfg.publishedScrapeTargets)
|
||||||
)
|
)
|
||||||
} is declared as both a loopback
|
} is declared as both a loopback
|
||||||
scrapeTargets job and a publishedScrapeTargets job.
|
scrapeTargets job and a publishedScrapeTargets job.
|
||||||
|
|
@ -1149,9 +1201,9 @@ in
|
||||||
# target: a `prometheus` receiver with nothing to scrape is
|
# target: a `prometheus` receiver with nothing to scrape is
|
||||||
# the shape this whole issue is about, a config that renders
|
# the shape this whole issue is about, a config that renders
|
||||||
# and deploys perfectly while adding no data.
|
# and deploys perfectly while adding no data.
|
||||||
// lib.optionalAttrs (cfg.scrapeTargets != { } || cfg.publishedScrapeTargets != { }) {
|
// lib.optionalAttrs (scrapeTargets != { } || cfg.publishedScrapeTargets != { }) {
|
||||||
prometheus.config.scrape_configs =
|
prometheus.config.scrape_configs =
|
||||||
lib.mapAttrsToList loopbackScrapeConfig cfg.scrapeTargets
|
lib.mapAttrsToList loopbackScrapeConfig scrapeTargets
|
||||||
# ⚠️ `++`, so the two kinds of target land in ONE list —
|
# ⚠️ `++`, so the two kinds of target land in ONE list —
|
||||||
# which is exactly why a job name may not appear in both
|
# which is exactly why a job name may not appear in both
|
||||||
# options. A list concatenation does not resolve a
|
# options. A list concatenation does not resolve a
|
||||||
|
|
@ -1410,7 +1462,7 @@ in
|
||||||
# separated FOR is the credential it proves, and that is
|
# separated FOR is the credential it proves, and that is
|
||||||
# the receiver's job, not the pipeline's.
|
# the receiver's job, not the pipeline's.
|
||||||
++ map (n: "otlp/${n}") serviceProducerNames
|
++ map (n: "otlp/${n}") serviceProducerNames
|
||||||
++ lib.optional (cfg.scrapeTargets != { } || cfg.publishedScrapeTargets != { }) "prometheus";
|
++ lib.optional (scrapeTargets != { } || cfg.publishedScrapeTargets != { }) "prometheus";
|
||||||
processors = [ "resource/${swarmTierName}" ];
|
processors = [ "resource/${swarmTierName}" ];
|
||||||
exporters = exporterNames;
|
exporters = exporterNames;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ in
|
||||||
# so — unlike authelia/matrix/forge's entries — this one needs no
|
# so — unlike authelia/matrix/forge's entries — this one needs no
|
||||||
# host name and is never conditional on anything but this module
|
# host name and is never conditional on anything but this module
|
||||||
# being enabled at all. See
|
# being enabled at all. See
|
||||||
# `services.hyperhive.swarm.controller.links`'s description.
|
# `services.hyperhive.deploy.swarm-controller.links`'s description.
|
||||||
services.hyperhive.swarm.controller.links = [
|
services.hyperhive.deploy.swarm-controller.links = [
|
||||||
{
|
{
|
||||||
label = "API docs";
|
label = "API docs";
|
||||||
icon = "🧬";
|
icon = "🧬";
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ let
|
||||||
controllerPinnedMatrix = hive {
|
controllerPinnedMatrix = hive {
|
||||||
deploy.swarm-controller.enable = true;
|
deploy.swarm-controller.enable = true;
|
||||||
swarm.matrix.gatewayHost = "matrix.hive.t.local";
|
swarm.matrix.gatewayHost = "matrix.hive.t.local";
|
||||||
swarm.matrix.adminSync.ignoreUsers = [
|
deploy.swarm-controller.matrixAdminSync.ignoreUsers = [
|
||||||
"@keep-a:t.local"
|
"@keep-a:t.local"
|
||||||
"@keep-b:t.local"
|
"@keep-b:t.local"
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1594,6 +1594,15 @@ let
|
||||||
victorialogs.port = 19428;
|
victorialogs.port = 19428;
|
||||||
forge.httpPort = 13000;
|
forge.httpPort = 13000;
|
||||||
matrix.allowEncryption = true;
|
matrix.allowEncryption = true;
|
||||||
|
controller.links = [
|
||||||
|
{
|
||||||
|
label = "Legacy";
|
||||||
|
icon = "L";
|
||||||
|
url = "https://legacy.example/";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
matrix.adminSync.ignoreUsers = [ "@legacy:t.local" ];
|
||||||
|
otel.scrapeTargets.legacy = "127.0.0.1:9777";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = old.services.hyperhive.deploy;
|
d = old.services.hyperhive.deploy;
|
||||||
|
|
@ -1623,6 +1632,19 @@ let
|
||||||
&& d.forgejo.httpPort == 13000
|
&& d.forgejo.httpPort == 13000
|
||||||
&& d.matrix.allowEncryption
|
&& d.matrix.allowEncryption
|
||||||
&& d.builder.nixpkgsFlake == "path:/legacy/nixpkgs"
|
&& d.builder.nixpkgsFlake == "path:/legacy/nixpkgs"
|
||||||
|
&& lib.any (l: l.label == "Legacy" && l.url == "https://legacy.example/") d.swarm-controller.links
|
||||||
|
&& lib.any (l: l.label == "Legacy") (
|
||||||
|
builtins.fromJSON old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_LINKS
|
||||||
|
)
|
||||||
|
&& d.swarm-controller.matrixAdminSync.ignoreUsers == [ "@legacy:t.local" ]
|
||||||
|
&&
|
||||||
|
old.systemd.services.swarm-controller.environment.SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS
|
||||||
|
== "@legacy:t.local"
|
||||||
|
&& d.swarm-otel.scrapeTargets.legacy == "127.0.0.1:9777"
|
||||||
|
&&
|
||||||
|
(lib.findFirst (j: j.job_name == "legacy") null (
|
||||||
|
(otelSettings old).receivers.prometheus.config.scrape_configs
|
||||||
|
)).static_configs == [ { targets = [ "127.0.0.1:9777" ]; } ]
|
||||||
&&
|
&&
|
||||||
old.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_NIXPKGS_FLAKE
|
old.containers.swarm-builder.config.systemd.services.swarm-builder.environment.SWARM_BUILDER_NIXPKGS_FLAKE
|
||||||
== "path:/legacy/nixpkgs"
|
== "path:/legacy/nixpkgs"
|
||||||
|
|
@ -1642,6 +1664,9 @@ let
|
||||||
"swarm.forge.httpPort"
|
"swarm.forge.httpPort"
|
||||||
"swarm.matrix.allowEncryption"
|
"swarm.matrix.allowEncryption"
|
||||||
"c0re.nixpkgsFlake"
|
"c0re.nixpkgsFlake"
|
||||||
|
"swarm.controller.links"
|
||||||
|
"swarm.matrix.adminSync.ignoreUsers"
|
||||||
|
"swarm.otel.scrapeTargets"
|
||||||
]
|
]
|
||||||
&& !(lib.any (w: lib.hasInfix "swarm.controller.queue" w) allLocal.warnings)
|
&& !(lib.any (w: lib.hasInfix "swarm.controller.queue" w) allLocal.warnings)
|
||||||
&& refused old "deploy.nats.otel.telemetryPort"
|
&& refused old "deploy.nats.otel.telemetryPort"
|
||||||
|
|
@ -1672,7 +1697,15 @@ let
|
||||||
&& (locs h.swarm.nats.domain)."= /metrics".proxyPass == "http://127.0.0.1:7777/metrics"
|
&& (locs h.swarm.nats.domain)."= /metrics".proxyPass == "http://127.0.0.1:7777/metrics"
|
||||||
&& (locs h.swarm.victorialogs.domain)."= /metrics".proxyPass == "http://127.0.0.1:9428/metrics"
|
&& (locs h.swarm.victorialogs.domain)."= /metrics".proxyPass == "http://127.0.0.1:9428/metrics"
|
||||||
&& (locs h.swarm.forge.domain)."/".proxyPass == "http://127.0.0.1:3000/"
|
&& (locs h.swarm.forge.domain)."/".proxyPass == "http://127.0.0.1:3000/"
|
||||||
&& !allLocal.containers.hive-matrix.config.services.matrix-tuwunel.settings.global.allow_encryption;
|
&& !allLocal.containers.hive-matrix.config.services.matrix-tuwunel.settings.global.allow_encryption
|
||||||
|
&& env.SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS == ""
|
||||||
|
&& lib.any (l: l.label == "Forge" && l.url == "https://${h.swarm.forge.domain}/") (
|
||||||
|
builtins.fromJSON env.SWARM_CONTROLLER_LINKS
|
||||||
|
)
|
||||||
|
&&
|
||||||
|
(lib.findFirst (j: j.job_name == "collector") null (
|
||||||
|
(otelSettings allLocal).receivers.prometheus.config.scrape_configs
|
||||||
|
)).static_configs == [ { targets = [ "127.0.0.1:8889" ]; } ];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# Each old spelling lands on the new path, with its own rename warning;
|
# Each old spelling lands on the new path, with its own rename warning;
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ let
|
||||||
name = "a host running the collector, authelia, grafana and nats scrapes none of them on loopback";
|
name = "a host running the collector, authelia, grafana and nats scrapes none of them on loopback";
|
||||||
ok =
|
ok =
|
||||||
let
|
let
|
||||||
t = servicesHere.services.hyperhive.swarm.otel.scrapeTargets;
|
t = servicesHere.services.hyperhive.deploy.swarm-otel.scrapeTargets;
|
||||||
in
|
in
|
||||||
servicesHere.services.hyperhive.deploy.nats.enable
|
servicesHere.services.hyperhive.deploy.nats.enable
|
||||||
&& !(t ? authelia)
|
&& !(t ? authelia)
|
||||||
|
|
|
||||||
|
|
@ -300,8 +300,8 @@ let
|
||||||
name = "authelia and nats declare no scrape target on a host with no collector";
|
name = "authelia and nats declare no scrape target on a host with no collector";
|
||||||
ok =
|
ok =
|
||||||
!(autheliaAndNats.services.hyperhive.deploy.swarm-otel.enable)
|
!(autheliaAndNats.services.hyperhive.deploy.swarm-otel.enable)
|
||||||
&& !(autheliaAndNats.services.hyperhive.swarm.otel.scrapeTargets ? authelia)
|
&& !(autheliaAndNats.services.hyperhive.deploy.swarm-otel.scrapeTargets ? authelia)
|
||||||
&& !(autheliaAndNats.services.hyperhive.swarm.otel.scrapeTargets ? nats);
|
&& !(autheliaAndNats.services.hyperhive.deploy.swarm-otel.scrapeTargets ? nats);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# The xkey and the responder's OIDC client secret are read only from
|
# The xkey and the responder's OIDC client secret are read only from
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ in
|
||||||
baoWithCollector = hive {
|
baoWithCollector = hive {
|
||||||
deploy.bao.enable = true;
|
deploy.bao.enable = true;
|
||||||
deploy.swarm-otel.enable = true;
|
deploy.swarm-otel.enable = true;
|
||||||
swarm.otel.scrapeTargets.plain = "127.0.0.1:9999";
|
deploy.swarm-otel.scrapeTargets.plain = "127.0.0.1:9999";
|
||||||
};
|
};
|
||||||
|
|
||||||
# `clientSecretFile` is what ../host-modules/otel.nix's identity assertion
|
# `clientSecretFile` is what ../host-modules/otel.nix's identity assertion
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ let
|
||||||
# is a running collector with no self-scrape, not an absent one.
|
# is a running collector with no self-scrape, not an absent one.
|
||||||
otelOnlyPublished = hive {
|
otelOnlyPublished = hive {
|
||||||
deploy.swarm-otel.enable = true;
|
deploy.swarm-otel.enable = true;
|
||||||
swarm.otel.scrapeTargets = lib.mkForce { };
|
deploy.swarm-otel.scrapeTargets = lib.mkForce { };
|
||||||
swarm.otel.publishedScrapeTargets.remote = "https://remote.t.local/metrics";
|
swarm.otel.publishedScrapeTargets.remote = "https://remote.t.local/metrics";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ let
|
||||||
let
|
let
|
||||||
s = otelSettings otelOnlyPublished;
|
s = otelSettings otelOnlyPublished;
|
||||||
in
|
in
|
||||||
otelOnlyPublished.services.hyperhive.swarm.otel.scrapeTargets == { }
|
otelOnlyPublished.services.hyperhive.deploy.swarm-otel.scrapeTargets == { }
|
||||||
&& otelOnlyPublished.services.hyperhive.swarm.otel.publishedScrapeTargets != { }
|
&& otelOnlyPublished.services.hyperhive.swarm.otel.publishedScrapeTargets != { }
|
||||||
&& (s.receivers ? prometheus)
|
&& (s.receivers ? prometheus)
|
||||||
&& builtins.elem "prometheus" s.service.pipelines."metrics/swarm".receivers;
|
&& builtins.elem "prometheus" s.service.pipelines."metrics/swarm".receivers;
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,9 @@ let
|
||||||
};
|
};
|
||||||
swarmServiceLinksOf =
|
swarmServiceLinksOf =
|
||||||
cfg:
|
cfg:
|
||||||
lib.filter (l: swarmServiceLinkDomains ? ${l.label}) cfg.services.hyperhive.swarm.controller.links;
|
lib.filter (
|
||||||
|
l: swarmServiceLinkDomains ? ${l.label}
|
||||||
|
) cfg.services.hyperhive.deploy.swarm-controller.links;
|
||||||
|
|
||||||
# Every service container at once: the services-only host plus the CI
|
# Every service container at once: the services-only host plus the CI
|
||||||
# runner. The CI runner, the review classifier, the notification router, the
|
# runner. The CI runner, the review classifier, the notification router, the
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ const SERVER_USER_LOCALPART: &str = "conduit";
|
||||||
const GRANTED_FILE: &str = "operator-matrix-admins.json";
|
const GRANTED_FILE: &str = "operator-matrix-admins.json";
|
||||||
|
|
||||||
/// Whitespace-separated user ids the revoke never touches, set from
|
/// Whitespace-separated user ids the revoke never touches, set from
|
||||||
/// `services.hyperhive.swarm.matrix.adminSync.ignoreUsers`.
|
/// `services.hyperhive.deploy.swarm-controller.matrixAdminSync.ignoreUsers`.
|
||||||
const IGNORE_USERS_ENV: &str = "SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS";
|
const IGNORE_USERS_ENV: &str = "SWARM_CONTROLLER_MATRIX_ADMIN_SYNC_IGNORE_USERS";
|
||||||
|
|
||||||
/// The localpart an SSO login by authelia user `username` lands on, or `None`
|
/// The localpart an SSO login by authelia user `username` lands on, or `None`
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub(crate) async fn get_hives(State(state): State<AppState>) -> Json<Vec<HiveEnt
|
||||||
/// One quick link to a swarm-wide service (authelia, matrix, forge, this
|
/// One quick link to a swarm-wide service (authelia, matrix, forge, this
|
||||||
/// daemon's own swagger UI, …). Deliberately generic rather than named
|
/// daemon's own swagger UI, …). Deliberately generic rather than named
|
||||||
/// fields per service: each service's own nix module contributes its own
|
/// fields per service: each service's own nix module contributes its own
|
||||||
/// entry to `services.hyperhive.swarm.controller.links` (same list-merge
|
/// entry to `services.hyperhive.deploy.swarm-controller.links` (same list-merge
|
||||||
/// idiom `services.hyperhive.gateway.localNames` already uses), so adding
|
/// idiom `services.hyperhive.gateway.localNames` already uses), so adding
|
||||||
/// a new one is a nix-only change — no new field here, no swarm-ui change.
|
/// a new one is a nix-only change — no new field here, no swarm-ui change.
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ pub(crate) fn load_hives() -> Vec<HiveEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Env var the controller's NixOS module sets from the merged
|
/// Env var the controller's NixOS module sets from the merged
|
||||||
/// `services.hyperhive.swarm.controller.links` list, JSON-encoded — same
|
/// `services.hyperhive.deploy.swarm-controller.links` list, JSON-encoded — same
|
||||||
/// shape/rationale as [`HIVES_ENV`]. Consumed by `GET /api/links`.
|
/// shape/rationale as [`HIVES_ENV`]. Consumed by `GET /api/links`.
|
||||||
const LINKS_ENV: &str = "SWARM_CONTROLLER_LINKS";
|
const LINKS_ENV: &str = "SWARM_CONTROLLER_LINKS";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue