feat(sterni/machines/ingeborg): regularly back up minecraft worlds

This is just intended as a local backup in case things go wrong
horribly, so you can revert to a recent state.

Change-Id: I1d666bad77045a1c807204df144422ba69d1d99f
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10417
Autosubmit: sterni <sternenseemann@systemli.org>
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
This commit is contained in:
sterni 2023-12-25 12:21:18 +01:00 committed by clbot
parent d4dc28b675
commit 32e8e16e1e
2 changed files with 131 additions and 2 deletions

View file

@ -38,11 +38,10 @@ let
}; };
in in
# TODO(sterni): regular backups of carpet world
{ {
imports = [ imports = [
../../modules/minecraft-fabric.nix ../../modules/minecraft-fabric.nix
../../modules/backup-minecraft-fabric.nix
]; ];
config = { config = {
@ -65,6 +64,11 @@ in
minecraft-rcon.file = depot.users.sterni.secrets."minecraft-rcon.age"; minecraft-rcon.file = depot.users.sterni.secrets."minecraft-rcon.age";
}; };
services.backup-minecraft-fabric-servers = {
enable = true;
repository = "/srv/backup/from-local/minecraft";
};
services.minecraft-fabric-server = { services.minecraft-fabric-server = {
creative = { creative = {
enable = false; # not actively used enable = false; # not actively used

View file

@ -0,0 +1,125 @@
# Companion module to minecraft-fabric.nix which automatically and regularly
# creates backups of all minecraft servers' worlds to a shared borg(1)
# repository.
#
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2023 sterni <sternenseemann@systemli.org>
{ pkgs, depot, config, lib, ... }:
let
inherit (depot.nix) getBins;
bins = getBins pkgs.borgbackup [ "borg" ]
// getBins pkgs.mcrcon [ "mcrcon" ];
unvaried = ls: builtins.all (l: l == builtins.head ls) ls;
cfg = config.services.backup-minecraft-fabric-servers;
instances = lib.filterAttrs (_: i: i.enable) config.services.minecraft-fabric-server;
users = lib.mapAttrsToList (_: i: i.user) instances;
groups = lib.mapAttrsToList (_: i: i.group) instances;
mkBackupScript = instanceName: instanceCfg:
let
archivePrefix = "minecraft-fabric-${instanceName}-world-${builtins.baseNameOf instanceCfg.world}-";
in
pkgs.writeShellScript "backup-minecraft-fabric-${instanceName}" ''
export MCRCON_HOST="localhost"
export MCRCON_PORT="${toString instanceCfg.serverProperties."rcon.port"}"
# Unfortunately, mcrcon can't read the password from a file
export MCRCON_PASS="$(cat "''${CREDENTIALS_DIRECTORY}/${instanceName}-rcon-password")"
${bins.mcrcon} save-all
unset MCRCON_PASS
# Give the server plenty of time to save
sleep 60
${bins.borg} ${lib.escapeShellArgs [
"create"
"--verbose" "--filter" "AMEU" "--list"
"--stats" "--show-rc"
"--compression" "zlib"
"${cfg.repository}::${archivePrefix}{now}"
instanceCfg.world
]}
${bins.borg} ${lib.escapeShellArgs [
"prune"
"--list"
"--show-rc"
"--glob-archives" "${archivePrefix}*"
"--keep-hourly" "168"
"--keep-daily" "31"
"--keep-monthly" "6"
"--keep-yearly" "2"
cfg.repository
]}
${bins.borg} compact ${lib.escapeShellArg cfg.repository}
'';
in
{
imports = [
./minecraft-fabric.nix
];
options = {
services.backup-minecraft-fabric-servers = {
enable = lib.mkEnableOption "backups of all Minecraft fabric servers";
repository = lib.mkOption {
type = lib.types.path;
description = "Path to the borg(1) repository to use for all backups.";
default = "/var/lib/backup/minecraft-fabric";
};
};
};
config = lib.mkIf (cfg.enable && builtins.length (builtins.attrNames instances) > 0) {
assertions = [
{
assertion = unvaried users && unvaried groups;
message = "all instances under services.minecraft-fabric-server must use the same user and group";
}
];
environment.systemPackages = [
pkgs.borgbackup
];
systemd = {
services.backup-minecraft-fabric-servers = {
description = "Backup world of all fabric based Minecraft servers";
wantedBy = [ ];
after = builtins.map
(name: "minecraft-fabric-${name}.service")
(builtins.attrNames instances);
script = lib.concatStrings (lib.mapAttrsToList mkBackupScript instances);
serviceConfig = {
Type = "oneshot";
User = builtins.head users;
Group = builtins.head groups;
LoadCredential = lib.mapAttrsToList
(instanceName: instanceCfg: "${instanceName}-rcon-password:${instanceCfg.rconPasswordFile}")
instances;
};
};
timers.backup-minecraft-fabric-servers = {
description = "Regularly backup Minecraft fabric servers";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "*-*-* 00/3:00:00";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
};
};
}