diff --git a/configuration/default.nix b/configuration/default.nix index d6cc5cf..76a1bf7 100644 --- a/configuration/default.nix +++ b/configuration/default.nix @@ -6,6 +6,7 @@ ./services/minecraft.nix ./services/nextcloud.nix ./services/webserver.nix + ./services/starbound.nix ./ids.nix ]; @@ -20,6 +21,9 @@ trustedUsers = [ "@wheel" ]; }; + nixpkgs.config.allowUnfreePredicate = pkg: + builtins.elem (lib.getName pkg) ["forge-server" "steam-runtime" "steamcmd"]; + sops = { defaultSopsFile = ../keys/external.yaml; secrets.steam = { }; @@ -34,7 +38,7 @@ useDHCP = false; interfaces.eth0.useDHCP = true; - firewall.allowedTCPPorts = [ 80 443 2222 2221 25565 ]; + firewall.allowedTCPPorts = [ 80 443 2222 2221 25565 21025 ]; }; time.timeZone = "Europe/London"; diff --git a/configuration/services/configs/starbound.json b/configuration/services/configs/starbound.json new file mode 100644 index 0000000..d995fdf --- /dev/null +++ b/configuration/services/configs/starbound.json @@ -0,0 +1,50 @@ +{ + "allowAdminCommands" : true, + "allowAdminCommandsFromAnyone" : false, + "allowAnonymousConnections" : true, + "allowAssetsMismatch" : true, + "anonymousConnectionsAreAdmin" : false, + "bannedIPs" : [], + "bannedUuids" : [], + "checkAssetsDigest" : false, + "clearPlayerFiles" : false, + "clearUniverseFiles" : false, + "clientIPJoinable" : false, + "clientP2PJoinable" : true, + "configurationVersion" : { + "basic" : 2, + "server" : 4 + }, + "crafting" : { + "filterHaveMaterials" : false + }, + "gameServerBind" : "::", + "gameServerPort" : 21025, + "interactiveHighlight" : true, + "inventory" : { + "pickupToActionBar" : true + }, + "maxPlayers" : 8, + "maxTeamSize" : 4, + "monochromeLighting" : false, + "playerBackupFileCount" : 3, + "queryServerBind" : "::", + "queryServerPort" : 21025, + "rconServerBind" : "::", + "rconServerPassword" : "", + "rconServerPort" : 21026, + "rconServerTimeout" : 1000, + "runQueryServer" : false, + "runRconServer" : false, + "safeScripts" : true, + "scriptInstructionLimit" : 10000000, + "scriptInstructionMeasureInterval" : 10000, + "scriptProfilingEnabled" : false, + "scriptRecursionLimit" : 100, + "serverFidelity" : "automatic", + "serverName" : "tlater.net", + "serverOverrideAssetsDigest" : null, + "serverUsers" : { + }, + "tutorialMessages" : true +} diff --git a/configuration/services/minecraft.nix b/configuration/services/minecraft.nix index 23705ac..80b4ff5 100644 --- a/configuration/services/minecraft.nix +++ b/configuration/services/minecraft.nix @@ -51,9 +51,6 @@ let eula = pkgs.writeText "eula.txt" "eula=true"; in { - nixpkgs.config.allowUnfreePredicate = pkg: - builtins.elem (lib.getName pkg) [ "forge-server" ]; - users = { extraUsers.minecraft = { uid = config.ids.uids.minecraft; diff --git a/configuration/services/starbound.nix b/configuration/services/starbound.nix new file mode 100644 index 0000000..e23028f --- /dev/null +++ b/configuration/services/starbound.nix @@ -0,0 +1,110 @@ +{ + pkgs, + lib, + ... +}: let + inherit (lib) concatStringsSep; +in { + systemd.services.starbound = { + description = "Starbound"; + after = ["network.target"]; + + serviceConfig = { + ExecStart = "${pkgs.local.starbound}/bin/launch-starbound ${./configs/starbound.json}"; + + Type = "simple"; + + # Credential loading for steam auth (if necessary; prefer + # anonymous login wherever possible). + LoadCredential = "steam:/run/secrets/steam"; + + # Security settings + DynamicUser = true; + + # This is where the StateDirectory ends up + WorkingDirectory = "/var/lib/starbound"; + # Creates /var/lib/starbound (or rather, a symlink there to + # /var/lib/private/starbound), and sets it up to be writeable to + # by the dynamic user. + StateDirectory = "starbound"; + + # Note some settings below are basically tautologous with + # `NoNewPrivileges`, but they all work slightly differently so + # add additional layers in case of bugs. + + ## THESE SETTINGS ARE A GOOD IDEA BUT THE STEAM CLIENT IS + ## REALLY, REALLY BAD, AND FOR SOME REASON I NEED TO USE IT TO + ## DOWNLOAD GAME SERVERS AS WELL: + ## + # To guarantee the above (only permits 64-bit syscalls, 32-bit + # syscalls can circumvent the above restrictions). + # + # Obviously, if running a 32 bit game server, change this. + # SystemCallArchitectures = "native"; + # Game servers shouldn't need to create new namespaces ever. + # + # TODO: Since steam uses namespaces for things *entirely + # unrelated* to installing game servers, we need to allow + # namespace access. Ideally I'd instead do this in an + # ExecStartPre, but alas, this isn't possible because of + # https://github.com/systemd/systemd/issues/19604. + # + # RestrictNamespaces = true; + + # Don't need to let the game server see other user accounts + PrivateUsers = true; + # *Probably* not harmful for game servers, which probably don't update dynamically + ProtectHostname = true; + # Yeah, if a game server tries to edit the hardware clock something's fishy + ProtectClock = true; + # Don't let game servers modify kernel settings, duh + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + # Game servers shouldn't use cgroups themselves either + ProtectControlGroups = true; + # Most game servers will never need other socket types + RestrictAddressFamilies = ["AF_UNIX AF_INET AF_INET6"]; + # Also a no-brainer, no game server should ever need this + LockPersonality = true; + # Some game servers will probably try to set this, but they + # don't need it. It's only required for audio processing and + # such, which the server end doesn't need to do. + RestrictRealtime = true; + # Don't allow a variety of syscalls that gameservers have no + # business using anyway + SystemCallFilter = + "~" + + (concatStringsSep " " [ + "@clock" + "@cpu-emulation" + "@debug" + "@keyring" + "@memlock" + "@module" + # "@mount" TODO: Consider adding when steamcmd is run in ExecStartPre + "@obsolete" + "@raw-io" + "@reboot" + "@resources" + "@setuid" + "@swap" + ]); + # Normally only "read-only", but steamcmd will puke if there is + # no home directory to write to (though the nix package will + # implicitly symlink to the path that we set in its override, so + # no actual files are created, besides a symlink). + ProtectHome = "tmpfs"; + + # Implied by DynamicUser anyway, but it doesn't hurt to add + # these explicitly, at least for reference. + RemoveIPC = true; + PrivateTmp = true; + PrivateDevices = true; + NoNewPrivileges = true; + RestrictSUIDSGID = true; + ProtectSystem = "strict"; + # ProtectHome = "read-only"; # See further up + }; + }; +} diff --git a/flake.nix b/flake.nix index d8928df..772580b 100644 --- a/flake.nix +++ b/flake.nix @@ -130,9 +130,26 @@ ]) ])) ]; - shellHook = '' + shellHook = let + inherit (pkgs.lib.attrsets) mapAttrsToList; + inherit (pkgs.lib.strings) concatStringsSep; + ports = { + "3022" = "2222"; + "3080" = "80"; + "3443" = "443"; + "3021" = "2221"; + "25565" = "25565"; + "21025" = "21025"; # Starbound + }; + QEMU_NET_OPTS = + concatStringsSep "," + (mapAttrsToList + (host: vm: "hostfwd=::${host}-:${vm}") + ports); + in + '' export QEMU_OPTS="-m 3941 -smp 2 -curses" - export QEMU_NET_OPTS="hostfwd=::3022-:2222,hostfwd=::3080-:80,hostfwd=::3443-:443,hostfwd=::3021-:2221,hostfwd=::25565-:25565" + export QEMU_NET_OPTS="${QEMU_NET_OPTS}" # Work around sudo requiring a full terminal export NIX_SSHOPTS="-t" diff --git a/pkgs/default.nix b/pkgs/default.nix index 88a0112..dd334bc 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -13,4 +13,7 @@ in { # Minecraft modpacks voor-kia = callPackage ./minecraft/voor-kia.nix { }; voor-kia-client = callPackage ./minecraft/voor-kia-client.nix { }; + + # Starbound + starbound = callPackage ./starbound { }; } diff --git a/pkgs/starbound/default.nix b/pkgs/starbound/default.nix new file mode 100644 index 0000000..304f0f3 --- /dev/null +++ b/pkgs/starbound/default.nix @@ -0,0 +1,34 @@ +{ + stdenv, + lib, + makeWrapper, + patchelf, + steamPackages, + replace-secret, +}: let + # Use the directory in which starbound is installed so steamcmd + # doesn't have to be reinstalled constantly (we're using DynamicUser + # with StateDirectory to persist this). + steamcmd = steamPackages.steamcmd.override { + steamRoot = "/var/lib/starbound/.steamcmd"; + }; + wrapperPath = lib.makeBinPath [patchelf steamcmd replace-secret]; +in + stdenv.mkDerivation { + name = "starbound-update-script"; + nativeBuildInputs = [makeWrapper]; + dontUnpack = true; + patchPhase = '' + interpreter="$(cat $NIX_CC/nix-support/dynamic-linker)" + substitute ${./launch-starbound.sh} launch-starbound --subst-var interpreter + ''; + installPhase = '' + mkdir -p $out/bin + cp launch-starbound $out/bin/launch-starbound + chmod +x $out/bin/launch-starbound + ''; + postFixup = '' + wrapProgram $out/bin/launch-starbound \ + --prefix PATH : "${wrapperPath}" + ''; + } diff --git a/pkgs/starbound/launch-starbound.sh b/pkgs/starbound/launch-starbound.sh new file mode 100644 index 0000000..24d4db1 --- /dev/null +++ b/pkgs/starbound/launch-starbound.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -eu + +if ! [[ -v STATE_DIRECTORY && -v CREDENTIALS_DIRECTORY ]]; then + echo "Error: Runtime dir or credential not set" + exit 1 +fi + +# Update the server to the latest version +echo "Updating/installing starbound" + +mkdir -p "${STATE_DIRECTORY}/.steamcmd" +steamcmd <