indexer/option.nix
Ricardo (XenGi) Band 63c6f4b761
Some checks failed
CI pipeline for ffd-indexer / checks (go test + golangci) (push) Failing after 26s
CI pipeline for ffd-indexer / build binaries + containers (push) Failing after 3s
CI pipeline for ffd-indexer / format (gofmt + nixfmt) (push) Failing after 3s
CI pipeline for ffd-indexer / integration (live scrape + Typesense) (push) Failing after 20s
🔥
2026-10-01 00:37:44 +02:00

234 lines
7.5 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.federated-forge-discovery.indexer;
yaml = pkgs.lib.generators.toYAML { };
# NixOS package for the indexer, injected via the flake.
pkg = cfg.package;
# Location where the rendered config is written.
configDir = "/etc/ffd-indexer";
configFile = "${configDir}/config.yaml";
# The rendered YAML config, mirroring the config.go structure.
nixConfig = {
db_path = cfg.dbPath;
typesense = {
host = cfg.typesense.host;
api_key = cfg.typesense.apiKey;
}
// lib.optionalAttrs (cfg.typesense.apiKeyFile != null) {
# A secret file (sops/agenix) overrides the inline api_key in Go's
# config resolution, so it also wins here.
api_key_file = cfg.typesense.apiKeyFile;
};
# The metrics daemon is an optional component: only render the block
# when it is enabled, so an operator running just ffd-indexer can omit
# the metrics section entirely.
}
// lib.optionalAttrs cfg.metrics.enable {
metrics = {
listen = cfg.metrics.listen;
};
};
forges = cfg.forges;
};
configYAML = pkgs.writeText "config.yaml" (yaml nixConfig);
in
{
options.services.federated-forge-discovery.indexer = {
enable = lib.mkEnableOption "Enables the Federated Forge Discovery Indexer systemd service and timer.";
package = lib.mkOption {
description = "Indexer package to use.";
type = lib.types.package;
# Provided by the flake overlay (see flake.nix overlays.default), so
# `pkgs.ffd-indexer` resolves to this project's package.
default = pkgs.ffd-indexer;
};
schedule = lib.mkOption {
description = ''
How often the indexer runs. Either one of "daily", "hourly", "weekly",
"monthly", or any systemd OnCalendar expression.
'';
type = lib.types.str;
default = "daily";
example = "daily";
};
dbPath = lib.mkOption {
description = "Path to the SQLite database holding the index.";
type = lib.types.path;
default = "/var/lib/ffd-indexer/index.db";
example = "/var/lib/ffd-indexer/index.db";
};
typesense = {
host = lib.mkOption {
description = "Typesense server URL.";
type = lib.types.str;
default = "http://localhost:8108";
example = "http://localhost:8108";
};
apiKey = lib.mkOption {
description = ''
Typesense API key. May be inlined here, or supplied via a secret file with apiKeyFile (which takes precedence)
'';
type = lib.types.str;
default = "";
};
apiKeyFile = lib.mkOption {
description = ''
Path to a file containing the Typesense API key. Takes precedence over apiKey, so the key need not appear in
the rendered config or the Nix store.
'';
type = lib.types.nullOr lib.types.path;
default = null;
example = "/run/secrets/typesense-api-key";
};
};
metrics = {
enable = lib.mkEnableOption "the ffd-metrics Prometheus daemon (also controls whether the metrics block is rendered into the config)";
listen = lib.mkOption {
description = "Listen address the ffd-metrics daemon binds for /metrics.";
type = lib.types.str;
default = "127.0.0.1:9090";
example = "127.0.0.1:9090";
};
};
forges = lib.mkOption {
description = ''
Forge instances to scrape, as a list of "url" -> "type" entries,
e.g. [{ url = "git.example.org"; type = "forgejo"; }]. The url is a
bare hostname (FQDN); a scheme prefix (http:// or https://) is
allowed to force the connection protocol, but the stored identity and
metrics labels always use the bare hostname.
The recommended type is a pinned "forgejo" or "gitea", which selects
the scraper deterministically and skips probing. The default "auto"
re-detects the type on every run and is EXPERIMENTAL: detection is
probed via the version API, honors robots.txt (a forge that disallows
/api/ is reported "unknown" unless hard-coded), and may misclassify
until more detectors land. Prefer setting the type by hand.
'';
type = lib.types.listOf (
lib.types.submodule {
options = {
url = lib.mkOption {
description = "Forge hostname (FQDN) to scrape, e.g. \"git.example.org\".";
type = lib.types.str;
};
type = lib.mkOption {
description = ''
Forge type. Recommended: "forgejo" or "gitea" (pin the scraper,
skip detection). Default "auto" re-detects on every scan
(EXPERIMENTAL).
'';
type = lib.types.enum [
"auto"
"forgejo"
"gitea"
];
default = "auto";
};
};
}
);
default = [ ];
example = [
{
url = "git.example.org";
type = "forgejo";
}
{
url = "git.example.net";
type = "forgejo";
}
];
};
};
config = lib.mkIf cfg.enable {
# Write the rendered config and make it readable by the service user.
environment.etc."${lib.removePrefix "/etc/" configFile}" = {
source = configYAML;
user = "ffd-indexer";
group = "ffd-indexer";
mode = "0400";
};
users.users.ffd-indexer = {
description = "Federated Forge Discovery Indexer";
isSystemUser = true;
group = "ffd-indexer";
home = "/var/lib/ffd-indexer";
createHome = true;
};
users.groups.ffd-indexer = { };
systemd.services.ffd-indexer = {
description = "Federated Forge Discovery Indexer";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkg}/bin/ffd-indexer -config ${configFile}";
User = "ffd-indexer";
Group = "ffd-indexer";
NoNewPrivileges = true;
# The DB and Typesense data live under /var/lib/ffd-indexer.
StateDirectory = "ffd-indexer";
Restart = "on-failure";
RestartSec = 30;
};
};
# The timer drives the schedule; the service is oneshot.
systemd.timers.ffd-indexer = {
description = "Runs the Federated Forge Discovery Indexer on schedule";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = cfg.schedule;
Persistent = true;
Unit = "ffd-indexer.service";
};
};
# Long-running metrics daemon: reads the same DB the indexer writes to and
# serves Prometheus metrics at the configured address. It is a separate
# service so the indexer can stay a oneshot scheduled job, and it is
# optional (disable metrics.enable to run only the indexer).
systemd.services.ffd-metrics = lib.mkIf cfg.metrics.enable {
description = "Federated Forge Discovery Indexer metrics daemon";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [
"network-online.target"
"ffd-indexer.service"
];
serviceConfig = {
Type = "simple";
ExecStart = "${pkg}/bin/ffd-metrics -config ${configFile}";
User = "ffd-indexer";
Group = "ffd-indexer";
NoNewPrivileges = true;
StateDirectory = "ffd-indexer";
Restart = "on-failure";
RestartSec = 5;
};
};
};
meta.maintainers = with lib.maintainers; [ ];
}