@jaspermayone.com's dotfiles
at main 192 lines 5.0 kB view raw
1# Status monitoring module - serves /status endpoints for shields.io badges 2{ 3 config, 4 lib, 5 pkgs, 6 ... 7}: 8 9with lib; 10 11let 12 cfg = config.atelier.services.status; 13 14 # Script to check services and write status JSON 15 statusScript = pkgs.writeShellScript "status-check" '' 16 set -euo pipefail 17 STATUS_DIR="/var/lib/status" 18 mkdir -p "$STATUS_DIR" 19 20 # Check each configured service 21 ${concatStringsSep "\n" ( 22 map (svc: '' 23 if systemctl is-active --quiet ${escapeShellArg svc}; then 24 echo "ok" > "$STATUS_DIR/${svc}" 25 else 26 rm -f "$STATUS_DIR/${svc}" 27 fi 28 '') cfg.services 29 )} 30 31 # Always write host status (if this runs, host is up) 32 echo "ok" > "$STATUS_DIR/${cfg.hostname}" 33 34 # Check remote hosts via ping (Tailscale) 35 ${concatStringsSep "\n" ( 36 map (host: '' 37 if ${pkgs.iputils}/bin/ping -c 1 -W 2 ${escapeShellArg host} >/dev/null 2>&1; then 38 echo "ok" > "$STATUS_DIR/${host}" 39 else 40 rm -f "$STATUS_DIR/${host}" 41 fi 42 '') cfg.remoteHosts 43 )} 44 45 # Build services JSON 46 SERVICES_JSON="{" 47 ${concatStringsSep "\n" ( 48 imap0 (i: svc: '' 49 if systemctl is-active --quiet ${escapeShellArg svc}; then 50 SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":true" 51 else 52 SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":false" 53 fi 54 '') cfg.services 55 )} 56 SERVICES_JSON="$SERVICES_JSON}" 57 58 # Write full status JSON 59 cat > "$STATUS_DIR/status.json" << EOF 60 { 61 "hostname": "${cfg.hostname}", 62 "timestamp": "$(date -Iseconds)", 63 "services": $SERVICES_JSON 64 } 65 EOF 66 ''; 67in 68{ 69 options.atelier.services.status = { 70 enable = mkEnableOption "status monitoring endpoints"; 71 72 hostname = mkOption { 73 type = types.str; 74 description = "Hostname for this machine's status endpoint"; 75 }; 76 77 domain = mkOption { 78 type = types.str; 79 description = "Domain to serve status on"; 80 }; 81 82 services = mkOption { 83 type = types.listOf types.str; 84 default = [ ]; 85 description = "List of systemd services to monitor"; 86 }; 87 88 remoteHosts = mkOption { 89 type = types.listOf types.str; 90 default = [ ]; 91 description = "List of remote hosts to check via ping (e.g. Tailscale hosts)"; 92 }; 93 94 cloudflareCredentialsFile = mkOption { 95 type = types.nullOr types.path; 96 default = null; 97 description = "Path to Cloudflare credentials file for DNS challenge"; 98 }; 99 }; 100 101 config = mkIf cfg.enable { 102 # Timer to update status every minute 103 systemd.services.status-check = { 104 description = "Update status endpoints"; 105 serviceConfig = { 106 Type = "oneshot"; 107 ExecStart = statusScript; 108 }; 109 }; 110 111 systemd.timers.status-check = { 112 description = "Run status check every minute"; 113 wantedBy = [ "timers.target" ]; 114 timerConfig = { 115 OnBootSec = "30s"; 116 OnUnitActiveSec = "1min"; 117 }; 118 }; 119 120 # Ensure status directory exists 121 systemd.tmpfiles.rules = [ 122 "d /var/lib/status 0755 root root -" 123 ]; 124 125 # Caddy virtual host for status 126 services.caddy.virtualHosts."${cfg.domain}".extraConfig = '' 127 ${optionalString (cfg.cloudflareCredentialsFile != null) '' 128 tls { 129 dns cloudflare {env.CLOUDFLARE_API_TOKEN} 130 } 131 ''} 132 133 # Individual host status (returns 200 if file exists) 134 @status_host path /status/${cfg.hostname} 135 handle @status_host { 136 @online file /var/lib/status/${cfg.hostname} 137 handle @online { 138 respond "ok" 200 139 } 140 handle { 141 respond "offline" 503 142 } 143 } 144 145 # Service status endpoints 146 ${concatStringsSep "\n" ( 147 map (svc: '' 148 @status_${svc} path /status/service/${svc} 149 handle @status_${svc} { 150 @online_${svc} file /var/lib/status/${svc} 151 handle @online_${svc} { 152 respond "ok" 200 153 } 154 handle { 155 respond "offline" 503 156 } 157 } 158 '') cfg.services 159 )} 160 161 # Remote host status endpoints (Tailscale) 162 ${concatStringsSep "\n" ( 163 map (host: '' 164 @status_${host} path /status/${host} 165 handle @status_${host} { 166 @online_${host} file /var/lib/status/${host} 167 handle @online_${host} { 168 respond "ok" 200 169 } 170 handle { 171 respond "offline" 503 172 } 173 } 174 '') cfg.remoteHosts 175 )} 176 177 # Full status JSON 178 @status_json path /status 179 handle @status_json { 180 root * /var/lib/status 181 rewrite * /status.json 182 file_server 183 header Content-Type application/json 184 } 185 186 # Root redirect to status 187 handle { 188 respond "alastor.hogwarts.channel - see /status" 200 189 } 190 ''; 191 }; 192}