{ config, pkgs, ... }: let foundry-vtt = pkgs.callPackage ../pkgs/foundry-vtt {}; cids = import ../modules/staticids.nix; hostConfig = config; url = "https://foundry-vtt.cloonar.com"; # URL to check targetService = "container@foundry-vtt.service"; # systemd unit to restart (e.g. "docker-container@myapp.service") threshold = 3; # consecutive failures before restart interval = "1min"; # how often to run timeoutSeconds = 10; # curl timeout checkUrlScript = pkgs.writeShellScript "check-foundry-up" '' #!/usr/bin/env bash set -euo pipefail URL="$1" TARGET="$2" THRESHOLD="$3" TIMEOUT="$4" STATE_DIR="/run/url-watchdog" mkdir -p "$STATE_DIR" SAFE_TARGET="$(systemd-escape --path "$TARGET")" STATE_FILE="$STATE_DIR/$SAFE_TARGET.count" TMP="$(mktemp)" # Get HTTP status; "000" if curl fails. status="$(curl -sS -m "$TIMEOUT" -o "$TMP" -w "%{http_code}" "$URL" || echo "000")" fail=0 if [[ "$status" == "502" || "$status" == "504" || "$status" == "000" ]]; then fail=1 fi count=0 if [[ -f "$STATE_FILE" ]]; then count="$(cat "$STATE_FILE" 2>/dev/null || echo 0)" fi if [[ "$fail" -eq 1 ]]; then count=$((count+1)) else count=0 fi if [[ "$count" -ge "$THRESHOLD" ]]; then printf '[%s] %s failing (%s) %sx -> restarting %s\n' "$(date -Is)" "$URL" "$status" "$count" "$TARGET" systemctl restart "$TARGET" count=0 fi echo "$count" > "$STATE_FILE" rm -f "$TMP" ''; in { users.users.foundry-vtt = { isSystemUser = true; uid = cids.uids.foundry-vtt; home = "/var/lib/foundry-vtt"; group = "foundry-vtt"; createHome = true; }; users.groups.foundry-vtt = { gid = cids.gids.foundry-vtt; }; containers.foundry-vtt = { autoStart = true; ephemeral = true; privateNetwork = true; hostBridge = "server"; hostAddress = "${hostConfig.networkPrefix}.97.1"; localAddress = "${hostConfig.networkPrefix}.97.21/24"; bindMounts = { "/var/lib/foundry-vtt" = { hostPath = "/var/lib/foundry-vtt"; isReadOnly = false; }; }; config = { lib, config, pkgs, ... }: { networking = { hostName = "foundry-vtt"; useHostResolvConf = false; defaultGateway = { address = "${hostConfig.networkPrefix}.96.1"; interface = "eth0"; }; firewall.enable = false; nameservers = [ "${hostConfig.networkPrefix}.97.1" ]; }; systemd.services.foundry-vtt = { description = "Foundry VTT Server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; environment = { NODE_ENV = "production"; }; serviceConfig = { ExecStart = "${pkgs.nodejs}/bin/node ${foundry-vtt}/share/foundry-vtt/main.js --dataPath=${config.users.users.foundry-vtt.home}"; Restart = "always"; User = "foundry-vtt"; WorkingDirectory = "${config.users.users.foundry-vtt.home}"; }; }; users.users.foundry-vtt = { isSystemUser = true; uid = cids.uids.foundry-vtt; home = "/var/lib/foundry-vtt"; group = "foundry-vtt"; }; users.groups.foundry-vtt = { gid = cids.gids.foundry-vtt; }; system.stateVersion = "24.05"; }; }; systemd.services."restart-foundry-vtt" = { description = "Restart foundry-vtt container"; serviceConfig = { Type = "oneshot"; ExecStart = "${pkgs.systemd}/bin/systemctl restart container@foundry-vtt.service"; }; }; systemd.timers."restart-foundry-vtt" = { wantedBy = [ "timers.target" ]; timerConfig = { # 03:00 local time (Europe/Vienna for you) OnCalendar = "03:00"; # If the machine was off at 03:00, run once at next boot Persistent = true; Unit = "restart-foundry-vtt.service"; }; }; systemd.services.foundry-vtt-watchdog = { description = "Foundry VTT watchdog: restart ${targetService} on Nginx gateway errors"; serviceConfig = { Type = "oneshot"; ExecStart = "${checkUrlScript} ${url} ${targetService} ${toString threshold} ${toString timeoutSeconds}"; }; # Ensure needed tools are on PATH inside the unit path = [ pkgs.curl pkgs.coreutils pkgs.systemd ]; # Wait until networking is really up wants = [ "network-online.target" ]; after = [ "network-online.target" ]; }; systemd.timers.foundry-vtt-watchdog = { wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = interval; OnUnitActiveSec = interval; AccuracySec = "10s"; }; }; }