diff --git a/homeConfigurations/muede/default.nix b/homeConfigurations/muede/default.nix index 1a21c70..ba1c82c 100644 --- a/homeConfigurations/muede/default.nix +++ b/homeConfigurations/muede/default.nix @@ -2,7 +2,6 @@ { imports = [ # keep-sorted start - ./claude.nix ./cpucap.nix ./editorconfig.nix ./element.nix diff --git a/homeConfigurations/muede/claude.nix b/nixosConfigurations/muede-lpt2/claude.nix similarity index 90% rename from homeConfigurations/muede/claude.nix rename to nixosConfigurations/muede-lpt2/claude.nix index 6e6cd09..cc3835b 100644 --- a/homeConfigurations/muede/claude.nix +++ b/nixosConfigurations/muede-lpt2/claude.nix @@ -9,16 +9,16 @@ let claude-code = pkgs.unstable.claude-code; # Endpoint and wire protocol come straight off the hive's own OTLP config in - # nixosModules/constellation-swarm.nix, so there is one place to change them. + # ./constellation-swarm.nix, so there is one place to change them. # Only those two are shared: the hive's `agent=` / `hive=` / `swarm=` resource # labels are deliberately not carried over. hiveOtel = osConfig.services.hyperhive.otel; # Written by the `claude-otel-user-header` oneshot in that same module, which # copies the hive's root-only auth header into /run owned by this user. - # Absent on hosts without `my.constellation-swarm`, where the wrapper starts - # claude with no telemetry at all rather than retrying an unauthenticated - # export every interval. + # Absent until the oneshot has run (e.g. no credential yet), in which case the + # wrapper starts claude with no telemetry at all rather than retrying an + # unauthenticated export every interval. headerFile = "${osConfig.my.constellation-swarm.userOtelHeaderDir}/${config.home.username}"; # OTEL's `host.arch` is an enum of its own spelling, not uname's. @@ -70,7 +70,7 @@ let ); # Shadows claude-code's own `bin/claude`, so claude-code itself is kept out - # of home.packages (see ./default.nix) — two derivations installing the same + # of home.packages — two derivations installing the same # binary is a home-manager collision, not a precedence win. claude-wrapper = pkgs.writeShellScriptBin "claude" '' hdr=${lib.escapeShellArg headerFile} diff --git a/nixosConfigurations/muede-lpt2/constellation-swarm.nix b/nixosConfigurations/muede-lpt2/constellation-swarm.nix new file mode 100644 index 0000000..f5cfa78 --- /dev/null +++ b/nixosConfigurations/muede-lpt2/constellation-swarm.nix @@ -0,0 +1,129 @@ +{ + 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; + }; + }; + }; + + 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 + ''; + }; + }; + }; +} diff --git a/nixosConfigurations/muede-lpt2/default.nix b/nixosConfigurations/muede-lpt2/default.nix index 492afcd..a49b2d3 100644 --- a/nixosConfigurations/muede-lpt2/default.nix +++ b/nixosConfigurations/muede-lpt2/default.nix @@ -1,119 +1,75 @@ { pkgs, - config, lib, ... }: { imports = [ + ./constellation-swarm.nix ./containers.nix ./hardware.nix ]; - config = lib.mkMerge [ - { - my = { - # keep-sorted start - intelGraphics.enable = true; - muedeDesktopSettings.enable = true; - podman.enable = true; - secureBoot.enable = true; - steam.enable = true; - users.muede.enable = true; - wineGaming.enable = true; - # keep-sorted end - }; + my = { + # keep-sorted start + intelGraphics.enable = true; + muedeDesktopSettings.enable = true; + podman.enable = true; + secureBoot.enable = true; + steam.enable = true; + users.muede.enable = true; + wineGaming.enable = true; + # keep-sorted end + }; - nix.settings.extra-platforms = [ - "aarch64-linux" - "i686-linux" - ]; - - # Global default (nixosModules/distributed-builds.nix) is MemoryMax=90%, - # which isn't tight enough on this machine's 31GiB to avoid OOM. - systemd.services.nix-daemon.serviceConfig = { - MemoryHigh = "60%"; - MemoryMax = lib.mkForce "80%"; - }; - - services.xserver.xkb = { - # Configure keymap in X11 - layout = "de"; - variant = ""; - }; - - # Configure console keymap - console.keyMap = "de"; - - users.users.muede.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFCJUpbpB3KEKVoKWsKoar9J4RNah8gmQoSH6jQEw5dY pixel-JuiceSSH" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1CRn4yYTL4XUdCebE8Z4ZeuMujBjorTdWifg911EOv pc2 home roaming" - ]; - - # brightnessctl's udev rules grant the `video` group write access to backlight brightness (muede is in `video`) - environment.systemPackages = [ - pkgs.android-tools - pkgs.brightnessctl - ]; - services.udev.packages = [ pkgs.brightnessctl ]; - - networking.firewall.allowedTCPPorts = [ - 8776 - 1337 - ]; - - services.servicepoint-tanks = { - enable = false; - urls = [ - "http://localhost:5666" - "http://localhost:5667" - ]; - }; - nixpkgs.config.permittedInsecurePackages = [ - "mbedtls-2.28.10" - ]; - - boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; - } - - ( - let - old = "forge.pr1ma.darkest.space"; - new = "forge.constellation.darkest.space"; - in - { - my.constellation-swarm = { - enable = true; - hiveName = "pr1ma"; - }; - services.hyperhive = { - deploy = { - singleHostSwarm = true; - forgejo.ci = { - enable = true; - concurrency = 2; - }; - }; - - # 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`. - gateway.localNames = [ old ]; - }; - services.nginx.enable = true; - - services.nginx.virtualHosts.${old} = - let - gw = config.services.hyperhive.gateway; - in - (gw.lib.tlsFor old) - // { - listen = gw.lib.listen; - locations."/".return = "301 https://${new}$request_uri"; - }; - - #containers.swarm-authelia.config.services.authelia.instances.swarm.settings.log.level =lib.mkForce "debug"; - } - ) + nix.settings.extra-platforms = [ + "aarch64-linux" + "i686-linux" ]; + + # Global default (nixosModules/distributed-builds.nix) is MemoryMax=90%, + # which isn't tight enough on this machine's 31GiB to avoid OOM. + systemd.services.nix-daemon.serviceConfig = { + MemoryHigh = "60%"; + MemoryMax = lib.mkForce "80%"; + }; + + services.xserver.xkb = { + # Configure keymap in X11 + layout = "de"; + variant = ""; + }; + + # Configure console keymap + console.keyMap = "de"; + + users.users.muede.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFCJUpbpB3KEKVoKWsKoar9J4RNah8gmQoSH6jQEw5dY pixel-JuiceSSH" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1CRn4yYTL4XUdCebE8Z4ZeuMujBjorTdWifg911EOv pc2 home roaming" + ]; + + # brightnessctl's udev rules grant the `video` group write access to backlight brightness (muede is in `video`) + environment.systemPackages = [ + pkgs.android-tools + pkgs.brightnessctl + ]; + services.udev.packages = [ pkgs.brightnessctl ]; + + networking.firewall.allowedTCPPorts = [ + 8776 + 1337 + ]; + + services.servicepoint-tanks = { + enable = false; + urls = [ + "http://localhost:5666" + "http://localhost:5667" + ]; + }; + nixpkgs.config.permittedInsecurePackages = [ + "mbedtls-2.28.10" + ]; + + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; } diff --git a/nixosConfigurations/muede-pc2/default.nix b/nixosConfigurations/muede-pc2/default.nix index 2913fd4..139d556 100644 --- a/nixosConfigurations/muede-pc2/default.nix +++ b/nixosConfigurations/muede-pc2/default.nix @@ -47,10 +47,5 @@ # Factorio 34197 ]; - - my.constellation-swarm = { - enable = false; - hiveName = "umbra"; - }; }; } diff --git a/nixosModules/constellation-swarm.nix b/nixosModules/constellation-swarm.nix deleted file mode 100644 index 1d9ad78..0000000 --- a/nixosModules/constellation-swarm.nix +++ /dev/null @@ -1,100 +0,0 @@ -{ - hyperhive, - pkgs, - lib, - config, - ... -}: -let - cfg = config.my.constellation-swarm; - adminUsers = [ "muede" ]; - otelCredential = "/etc/hyperhive/otel-cred"; - runtimeDirName = "claude-otel"; -in -{ - options.my.constellation-swarm = { - enable = lib.mkEnableOption "hyperhive / constellation swarm"; - hiveName = lib.mkOption { type = lib.types.str; }; - - 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 (homeConfigurations/muede/claude.nix) can point at it via - `osConfig` instead of repeating the path. - ''; - }; - }; - - imports = [ hyperhive.nixosModules.default ]; - - config.services.hyperhive = lib.mkIf cfg.enable { - enable = true; - 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 = cfg.hiveName; - gateway = { - localHostsEntry = true; - openFirewall = true; - }; - otel = { - enable = true; - endpoint = "https://exponentials.vibec0re.mov/otel"; - headersCredential = otelCredential; - }; - c0re = { - inherit adminUsers; - agentMemoryMax = "5G"; - claudeCodePackage = pkgs.unstable.claude-code; - buildSlots = 2; - }; - }; - - # `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 - # homeConfigurations/muede/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. - config.systemd.services.claude-otel-user-header = lib.mkIf cfg.enable { - 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 - ''; - }; - }; -}