{ config, lib, pkgs, ... }: with lib; let cfg = config.homelab.services.hypervisor-gitops; hypervisorSyncScript = pkgs.writeShellApplication { name = "hypervisor-sync"; runtimeInputs = with pkgs; [ git opentofu coreutils ]; text = builtins.readFile ../../../../scripts/hypervisor-sync.sh; }; stagingSyncScript = pkgs.writeShellApplication { name = "staging-sync"; runtimeInputs = with pkgs; [ opentofu coreutils jq curl ]; text = builtins.readFile ../../../../scripts/staging-sync.sh; }; natsConsumerScript = pkgs.writeShellApplication { name = "nats-consumer"; runtimeInputs = with pkgs; [ natscli jq stagingSyncScript ]; text = '' set -euo pipefail NATS_URL=''${NATS_URL:-"nats://192.168.0.20:4222"} echo "Starting NATS JetStream consumer for staging env..." # Try to create stream and consumer if they don't exist nats --server "$NATS_URL" stream add FORGEJO_EVENTS --subjects "forgejo.staging" --ack --max-msgs=-1 --max-bytes=-1 --max-age=1y --storage file -f || true nats --server "$NATS_URL" consumer add FORGEJO_EVENTS STAGING --pull --ack explicit --filter forgejo.staging --deliver all -f || true echo "Listening for messages..." while true; do # We use a simple sub to pull messages. In a real environment, # a dedicated Go/Python client is better for manual explicit acks. # This will auto-ack upon receipt and pass to the staging script. nats --server "$NATS_URL" sub "forgejo.staging" | awk '/\[#.*\]/{flag=1; next} flag' | staging-sync || true done ''; }; in { options.homelab.services.hypervisor-gitops = { enable = mkEnableOption "Hypervisor GitOps Service"; repoUrl = mkOption { type = types.str; description = "The URL of the git repository to pull"; }; # TODO Replace with webhooks pollInterval = mkOption { type = types.str; default = "hourly"; description = "Systemd calendar event for polling interval"; }; }; config = mkIf cfg.enable { environment.systemPackages = with pkgs; [ git opentofu natscli jq ]; systemd.services.hypervisor-gitops = { description = "Hypervisor GitOps Polling Service"; # We need network access to reach Forgejo and Proxmox API after = [ "network-online.target" ]; wants = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; User = "root"; # Needs root to read SOPS secrets potentially # We will create a state directory for the repo StateDirectory = "hypervisor-gitops"; WorkingDirectory = "/var/lib/hypervisor-gitops"; ExecStart = "${hypervisorSyncScript}/bin/hypervisor-sync ${cfg.repoUrl} opentofu/nodes/mikoshi"; }; }; systemd.timers.hypervisor-gitops = { description = "Timer for Hypervisor GitOps Service"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = cfg.pollInterval; Persistent = true; }; }; systemd.services.staging-sync = { description = "Staging Environment NATS Consumer"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; User = "root"; # Needs root to read SOPS secrets Restart = "always"; RestartSec = "10s"; StateDirectory = "hypervisor-gitops"; WorkingDirectory = "/var/lib/hypervisor-gitops"; # We assume TRUENAS_API_KEY is provided via a sops EnvironmentFile # EnvironmentFile = config.sops.secrets."truenas-api-key".path; ExecStart = "${natsConsumerScript}/bin/nats-consumer"; }; }; }; }