- modules/server-deploy-guard.nix: extendable aggregator registered via
services.deployGuard.checks.<name>.{description,command}. Installs
deploy-guard-check with per-check timeout, pass/block reporting, JSON
output, DEPLOY_GUARD_BYPASS / /run/deploy-guard-bypass (single-shot).
- services/jellyfin/jellyfin-deploy-guard.nix: curl+jq on /Sessions,
blocks when any session carries NowPlayingItem; soft-fails when unreachable.
- services/minecraft-deploy-guard.nix: mcstatus SLP query on 25565, blocks
when players.online > 0; soft-fails when unreachable.
- flake.nix: wrap deploy.nodes.muffin activation with activate.custom so
deploy-guard-check runs before switch-to-configuration. Auto-rollback
catches the failure. dryActivate/boot branches preserved.
- deploy.sh: SSH preflight for ./deploy.sh muffin with --force /
DEPLOY_GUARD_FORCE=1 (touches remote bypass marker). Connectivity
failure is soft; activation still enforces.
- tests/deploy-guard.nix: aggregator contract, bypass mechanics, timeout,
JSON output.
68 lines
1.9 KiB
Nix
68 lines
1.9 KiB
Nix
# Deploy guard check for the Minecraft server.
|
|
#
|
|
# Queries the standard Server List Ping (SLP) handshake on the game port —
|
|
# no RCON, no query, no extra config. SLP is always enabled and returns the
|
|
# live player count plus (usually) a short name sample.
|
|
#
|
|
# Contract (deploy-guard-check plug-in):
|
|
# - exit 0: no players online, or the server isn't reachable at all (down ⇒
|
|
# no users to disrupt).
|
|
# - exit 1: at least one player is connected; stdout lists the names that
|
|
# made it into the SLP sample.
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
service_configs,
|
|
...
|
|
}:
|
|
let
|
|
minecraftPort = service_configs.ports.public.minecraft.port;
|
|
|
|
check =
|
|
pkgs.writers.writePython3Bin "deploy-guard-check-minecraft"
|
|
{
|
|
libraries = [ pkgs.python3Packages.mcstatus ];
|
|
flakeIgnore = [
|
|
"E501"
|
|
"E402"
|
|
];
|
|
}
|
|
''
|
|
import sys
|
|
|
|
try:
|
|
from mcstatus import JavaServer
|
|
except ImportError as e:
|
|
print(f"minecraft: mcstatus unavailable ({e}); assuming safe", file=sys.stderr)
|
|
sys.exit(0)
|
|
|
|
try:
|
|
status = JavaServer.lookup("127.0.0.1:${toString minecraftPort}", timeout=5).status()
|
|
except Exception as e:
|
|
print(f"minecraft: unreachable ({e}); assuming safe to deploy", file=sys.stderr)
|
|
sys.exit(0)
|
|
|
|
online = status.players.online
|
|
if online <= 0:
|
|
sys.exit(0)
|
|
|
|
sample = getattr(status.players, "sample", None) or []
|
|
names = ", ".join(p.name for p in sample) or "<names not reported>"
|
|
print(f"Minecraft: {online} player(s) online: {names}")
|
|
sys.exit(1)
|
|
'';
|
|
in
|
|
{
|
|
imports = [
|
|
../modules/server-deploy-guard.nix
|
|
];
|
|
|
|
config = lib.mkIf config.services.minecraft-servers.enable {
|
|
services.deployGuard.checks.minecraft = {
|
|
description = "Players connected to the Minecraft server";
|
|
command = check;
|
|
};
|
|
};
|
|
}
|