ci: run Renovate from asxpi/renovate
Some checks failed
nixos / asxpio-services (push) Failing after 54s

Renovate now runs from its own repository for every repo, with the
shared rules in its preset. Extend that preset here and keep only what
is specific to this repo (the Ansible var manager, the Paper JRE, the
runner and nix notes); drop the workflow that ran it for iac alone.

Assisted-by: Claude:opus-5-5
This commit is contained in:
Sergei Poljanski 2026-11-13 00:00:06 +04:00
commit 71047d179d
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
15 changed files with 705 additions and 284 deletions

View file

@ -1,183 +1,81 @@
# Containers run under Podman, one systemd unit each
# (podman-<name>.service). Game data stays in /opt and is not managed here.
{
config,
lib,
pkgs,
...
}:
# Minecraft and SillyTavern. Their data stays in /opt and is not managed
# here.
{ config, ... }:
let
podman = lib.getExe pkgs.podman;
# Time for a Minecraft server to save the world before it is killed
# Paper needs Java 21; the tag is a floating JRE line
javaImage = "docker.io/library/eclipse-temurin:21-jre";
paperJar = "paper-1.21.11-54.jar";
# Time for the server to save the world before it is killed
stopTimeout = 120;
# Nothing that owns data starts on a freshly installed disk: a game
# server would generate a new world where the restore is about to land.
# After restoring /opt, `touch /opt/.restored` and start the units.
needsData.unitConfig.ConditionPathExists = "/opt/.restored";
gameServer = needsData // {
serviceConfig = {
TimeoutStopSec = lib.mkForce (stopTimeout + 30);
# A JVM stopped by SIGTERM exits 143 after saving; that is a clean stop
SuccessExitStatus = [ 143 ];
};
};
# renovate: datasource=docker depName=ghcr.io/sillytavern/sillytavern
sillytavernVersion = "1.19.0";
in
{
virtualisation.podman = {
enable = true;
dockerCompat = true; # `docker` runs podman
dockerSocket.enable = true; # /run/docker.sock, used by the CI runner
defaultNetwork.settings.dns_enabled = true;
autoPrune = {
enable = true;
dates = "Mon 03:00";
flags = [ "--all" ];
};
};
virtualisation.containers.registries.search = [ "docker.io" ];
virtualisation.oci-containers.backend = "podman";
sops.secrets."minecraft_fossils/env" = { };
virtualisation.oci-containers.containers = {
# Paper server; the jar and the world live in /opt/minecraft
minecraft = {
image = "docker.io/library/eclipse-temurin:21-jre";
workdir = "/data";
cmd = [
"java"
"-Xmx4G"
"-Xms2G"
"-jar"
"paper-1.21.11-54.jar"
"nogui"
];
ports = [
"25565:25565"
"19132:19132/udp"
];
volumes = [ "/opt/minecraft:/data" ];
extraOptions = [
"--tty"
"--interactive"
"--stop-timeout=${toString stopTimeout}"
];
};
minecraft-fossils = {
image = "docker.io/itzg/minecraft-server:java21";
environment = {
EULA = "TRUE";
TYPE = "FABRIC";
VERSION = "1.20.1";
FABRIC_LOADER_VERSION = "0.18.4";
MEMORY = "8G";
ONLINE_MODE = "FALSE";
ENFORCE_WHITELIST = "TRUE";
ENFORCE_SECURE_PROFILE = "FALSE";
WHITELIST_FILE = "/data/whitelist.json";
OPS_FILE = "/data/ops.json";
ENABLE_RCON = "TRUE";
MOTD = "[mc.czsk.dev] Fossils Archaeology Revival: Genesis";
USE_AIKAR_FLAGS = "TRUE";
};
environmentFiles = [ config.sops.secrets."minecraft_fossils/env".path ];
ports = [ "25566:25565" ];
volumes = [ "/opt/minecraft-fossils/data:/data" ];
extraOptions = [
"--tty"
"--interactive"
"--network=fossils"
"--stop-timeout=${toString stopTimeout}"
];
};
# Copies whitelist, ops and ban lists from the Paper server
minecraft-fossils-wlsync = {
image = "docker.io/library/alpine:3";
dependsOn = [ "minecraft-fossils" ];
environment = {
RCON_HOST = "minecraft-fossils";
INTERVAL = "15";
};
environmentFiles = [ config.sops.secrets."minecraft_fossils/env".path ];
volumes = [
"/opt/minecraft:/src:ro"
"/opt/minecraft-fossils/data:/dst"
"${./wlsync.sh}:/wlsync.sh:ro"
];
cmd = [
"sh"
"/wlsync.sh"
];
extraOptions = [ "--network=fossils" ];
};
# Reachable on the WireGuard address only
sillytavern = {
image = "ghcr.io/sillytavern/sillytavern:1.19.0";
ports = [ "10.20.0.20:8000:8000" ];
volumes = [
"/opt/sillytavern/config:/home/node/app/config"
"/opt/sillytavern/data:/home/node/app/data"
"/opt/sillytavern/plugins:/home/node/app/plugins"
];
# Paper server; the jar and the world live in /opt/minecraft. Not an
# oci-containers unit: the console needs a TTY and an open stdin, and
# `docker run -it` refuses to start without a terminal. The container
# is created first and then attached to, output only.
#
# docker attach minecraft console; leave with Ctrl-p Ctrl-q
systemd.services.docker-minecraft = {
description = "Minecraft (Paper)";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
# requires: Docker restarting stops the server first, with time to save
requires = [ "docker.service" ];
after = [
"docker.service"
"network-online.target"
];
unitConfig.ConditionPathExists = "/opt/.restored";
path = [ config.virtualisation.docker.package ];
preStart = ''
docker rm -f minecraft || true
docker create --name minecraft --tty --interactive \
--stop-timeout ${toString stopTimeout} \
--workdir /data --volume /opt/minecraft:/data \
--publish 25565:25565 --publish 19132:19132/udp \
${javaImage} java -Xmx4G -Xms2G -jar ${paperJar} nogui
'';
script = "exec docker start --attach minecraft";
preStop = "docker stop minecraft || true";
postStop = "docker rm -f minecraft || true";
serviceConfig = {
TimeoutStartSec = 0; # the first start pulls the image
TimeoutStopSec = stopTimeout + 30;
# A JVM stopped by SIGTERM exits 143 after saving; that is a clean stop
SuccessExitStatus = [ 143 ];
Restart = "on-failure";
};
};
systemd.services = {
# Network shared by the Fossils server and its sidecar
podman-network-fossils = {
description = "Podman network fossils";
wantedBy = [ "multi-user.target" ];
before = [
"podman-minecraft-fossils.service"
"podman-minecraft-fossils-wlsync.service"
];
requiredBy = [
"podman-minecraft-fossils.service"
"podman-minecraft-fossils-wlsync.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = "${podman} network exists fossils || ${podman} network create fossils";
};
podman-minecraft = gameServer;
podman-minecraft-fossils = gameServer;
podman-minecraft-fossils-wlsync = needsData;
# The WireGuard address must exist before a port can be bound to it
podman-sillytavern = needsData // {
after = [ "wireguard-asxpio-services.service" ];
requires = [ "wireguard-asxpio-services.service" ];
};
minecraft-fossils-restart = {
description = "Daily restart of minecraft-fossils";
serviceConfig.Type = "oneshot";
script = "systemctl restart podman-minecraft-fossils.service";
};
# Reachable on the WireGuard address only
virtualisation.oci-containers.containers.sillytavern = {
image = "ghcr.io/sillytavern/sillytavern:${sillytavernVersion}";
ports = [ "10.20.0.20:8000:8000" ];
volumes = [
"/opt/sillytavern/config:/home/node/app/config"
"/opt/sillytavern/data:/home/node/app/data"
"/opt/sillytavern/plugins:/home/node/app/plugins"
];
};
systemd.timers.minecraft-fossils-restart = {
description = "Restart minecraft-fossils daily at 08:00 Europe/Tallinn";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 08:00:00 Europe/Tallinn";
Persistent = true;
};
# The WireGuard address must exist before a port can be bound to it
systemd.services.docker-sillytavern = {
unitConfig.ConditionPathExists = "/opt/.restored";
after = [ "wireguard-asxpio-services.service" ];
requires = [ "wireguard-asxpio-services.service" ];
};
# Published ports reach the containers without passing these rules over
# IPv4. Over IPv6 Docker's proxy listens on the host, so they are needed.
networking.firewall.allowedTCPPorts = [ 25565 ];
networking.firewall.allowedUDPPorts = [ 19132 ];
systemd.tmpfiles.rules = [
"d /opt 0755 root root -"
"d /opt/minecraft 0755 root root -"
"d /opt/minecraft-fossils 0755 asxpi users -"
"d /opt/minecraft-fossils/data 0755 asxpi users -"
"d /opt/sillytavern/config 0755 root root -"
"d /opt/sillytavern/data 0755 root root -"
"d /opt/sillytavern/plugins 0755 root root -"