{ hyperhive, pkgs, lib, config, ... }: let adminUsers = [ "muede" ]; otelCredential = "/etc/hyperhive/otel-cred"; runtimeDirName = "claude-otel"; old = "forge.pr1ma.darkest.space"; new = "forge.constellation.darkest.space"; in { options.my.constellation-swarm.userOtelHeaderDir = lib.mkOption { type = lib.types.str; default = "/run/${runtimeDirName}"; readOnly = true; description = '' Directory holding the per-admin copies of the hive's OTLP auth header, one 0400 file per user named after them. Read-only so the home-manager side (./claude.nix) can point at it via `osConfig` instead of repeating the path. ''; }; imports = [ hyperhive.nixosModules.default ]; config = { services.hyperhive = { swarm = { name = "constellation"; domain = "constellation.darkest.space"; matrix = { # not default - both still on hive domain gatewayHost = "matrix.pr1ma.darkest.space"; serverName = "pr1ma.darkest.space"; }; hives = { "umbra" = { # not default - still not subdomain domain = "umbra.darkest.space"; }; "pr1ma" = { # not default - still not subdomain domain = "pr1ma.darkest.space"; }; }; }; ruthless = true; hiveName = "pr1ma"; gateway = { localHostsEntry = true; openFirewall = true; # DNS: the hive's resolver must still answer for the old name, or # nothing ever reaches the redirect. The forge module drops it from # this list the moment you change `swarm.forge.domain`. localNames = [ old ]; }; otel = { enable = true; endpoint = "https://exponentials.vibec0re.mov/otel"; headersCredential = otelCredential; }; c0re = { inherit adminUsers; agentMemoryMax = "5G"; claudeCodePackage = pkgs.unstable.claude-code; buildSlots = 2; }; deploy = { hive-controller.enable = true; singleHostSwarm = true; ci-runner = { enable = true; concurrency = 2; }; }; }; # fix tailscale dns networking.nameservers = lib.mkOverride 10 [ ]; networking.resolvconf.extraConfig = '' name_servers='${config.services.hyperhive.network.bridgeIp}' ''; services.nginx = { enable = true; virtualHosts.${old} = let gw = config.services.hyperhive.gateway; in (gw.lib.tlsFor old) // { listen = gw.lib.listen; locations."/".return = "301 https://${new}$request_uri"; }; }; # Needs the hive's otel config and the header service below, so it lives # here rather than in the shared home config. home-manager.users.muede.imports = [ ./claude.nix ]; #containers.swarm-authelia.config.services.authelia.instances.swarm.settings.log.level =lib.mkForce "debug"; # `otelCredential` is root:root 0600, so the interactive `claude` an admin # runs as themselves cannot read it — only the hive's agent units can, via # LoadCredential. Hand each admin their own copy so the wrapper in # ./claude.nix can pick it up. It lands in /run # (tmpfs) rather than the nix store because the store is world-readable, and # rather than /etc because the header should not outlive a reboot. systemd.services.claude-otel-user-header = { description = "Expose the hive OTLP auth header to interactive admin users"; wantedBy = [ "multi-user.target" ]; unitConfig.ConditionPathExists = otelCredential; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; RuntimeDirectory = runtimeDirName; # Traversable by everyone; the per-user files inside are the 0400 part. RuntimeDirectoryMode = "0755"; ExecStart = pkgs.writeShellScript "claude-otel-user-header" '' set -eu umask 077 for user in ${lib.escapeShellArgs adminUsers}; do ${lib.getExe' pkgs.coreutils "install"} -m0400 -o "$user" -g root \ ${lib.escapeShellArg otelCredential} "$RUNTIME_DIRECTORY/$user" done ''; }; }; }; }