From 39a5e2c76bc457cfca1dc6b91a15aed98f475d5e Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 17 Nov 2020 07:44:28 +0100 Subject: [PATCH] nixos/freeciv: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/games/freeciv.nix | 187 +++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 nixos/modules/services/games/freeciv.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0f8a7ba79044..911f0434e1b8 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -349,6 +349,7 @@ ./services/editors/emacs.nix ./services/editors/infinoted.nix ./services/games/factorio.nix + ./services/games/freeciv.nix ./services/games/minecraft-server.nix ./services/games/minetest-server.nix ./services/games/openarena.nix diff --git a/nixos/modules/services/games/freeciv.nix b/nixos/modules/services/games/freeciv.nix new file mode 100644 index 000000000000..4923891a6179 --- /dev/null +++ b/nixos/modules/services/games/freeciv.nix @@ -0,0 +1,187 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.freeciv; + inherit (config.users) groups; + rootDir = "/run/freeciv"; + argsFormat = { + type = with lib.types; let + valueType = nullOr (oneOf [ + bool int float str + (listOf valueType) + ]) // { + description = "freeciv-server params"; + }; + in valueType; + generate = name: value: + let mkParam = k: v: + if v == null then [] + else if isBool v then if v then [("--"+k)] else [] + else [("--"+k) v]; + mkParams = k: v: map (mkParam k) (if isList v then v else [v]); + in escapeShellArgs (concatLists (concatLists (mapAttrsToList mkParams value))); + }; +in +{ + options = { + services.freeciv = { + enable = mkEnableOption ''freeciv''; + settings = mkOption { + description = '' + Parameters of freeciv-server. + ''; + default = {}; + type = types.submodule { + freeformType = argsFormat.type; + options.Announce = mkOption { + type = types.enum ["IPv4" "IPv6" "none"]; + default = "none"; + description = "Announce game in LAN using given protocol."; + }; + options.auth = mkEnableOption "server authentication"; + options.Database = mkOption { + type = types.nullOr types.str; + apply = pkgs.writeText "auth.conf"; + default = '' + [fcdb] + backend="sqlite" + database="/var/lib/freeciv/auth.sqlite" + ''; + description = "Enable database connection with given configuration."; + }; + options.debug = mkOption { + type = types.ints.between 0 3; + default = 0; + description = "Set debug log level."; + }; + options.exit-on-end = mkEnableOption "exit instead of restarting when a game ends."; + options.Guests = mkEnableOption "guests to login if auth is enabled"; + options.Newusers = mkEnableOption "new users to login if auth is enabled"; + options.port = mkOption { + type = types.port; + default = 5556; + description = "Listen for clients on given port"; + }; + options.quitidle = mkOption { + type = types.nullOr types.int; + default = null; + description = "Quit if no players for given time in seconds."; + }; + options.read = mkOption { + type = types.lines; + apply = v: pkgs.writeTextDir "read.serv" v + "/read"; + default = '' + /fcdb lua sqlite_createdb() + ''; + description = "Startup script."; + }; + options.saves = mkOption { + type = types.nullOr types.str; + default = "/var/lib/freeciv/saves/"; + description = '' + Save games to given directory, + a sub-directory named after the starting date of the service + will me inserted to preserve older saves. + ''; + }; + }; + }; + openFirewall = mkEnableOption "opening the firewall for the port listening for clients"; + }; + }; + config = mkIf cfg.enable { + users.groups.freeciv = {}; + # Use with: + # journalctl -u freeciv.service -f -o cat & + # cat >/run/freeciv.stdin + # load saves/2020-11-14_05-22-27/freeciv-T0005-Y-3750-interrupted.sav.bz2 + systemd.sockets.freeciv = { + wantedBy = [ "sockets.target" ]; + socketConfig = { + ListenFIFO = "/run/freeciv.stdin"; + SocketGroup = groups.freeciv.name; + SocketMode = "660"; + RemoveOnStop = true; + }; + }; + systemd.services.freeciv = { + description = "Freeciv Service"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment.HOME = "/var/lib/freeciv"; + serviceConfig = { + Restart = "on-failure"; + RestartSec = "5s"; + StandardInput = "fd:freeciv.socket"; + StandardOutput = "journal"; + StandardError = "journal"; + ExecStart = pkgs.writeShellScript "freeciv-server" ('' + set -eux + savedir=$(date +%Y-%m-%d_%H-%M-%S) + '' + "${pkgs.freeciv}/bin/freeciv-server" + + " " + optionalString (cfg.settings.saves != null) + (concatStringsSep " " [ "--saves" "${escapeShellArg cfg.settings.saves}/$savedir" ]) + + " " + argsFormat.generate "freeciv-server" (cfg.settings // { saves = null; })); + DynamicUser = true; + # Create rootDir in the host's mount namespace. + RuntimeDirectory = [(baseNameOf rootDir)]; + RuntimeDirectoryMode = "755"; + StateDirectory = [ "freeciv" ]; + WorkingDirectory = "/var/lib/freeciv"; + # Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace. + InaccessiblePaths = ["-+${rootDir}"]; + # This is for BindPaths= and BindReadOnlyPaths= + # to allow traversal of directories they create in RootDirectory=. + UMask = "0066"; + RootDirectory = rootDir; + RootDirectoryStartOnly = true; + MountAPIVFS = true; + BindReadOnlyPaths = [ + builtins.storeDir + "/etc" + "/run" + ]; + # The following options are only for optimizing: + # systemd-analyze security freeciv + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + # ProtectClock= adds DeviceAllow=char-rtc r + DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateNetwork = mkDefault false; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallFilter = [ + "@system-service" + # Groups in @system-service which do not contain a syscall listed by: + # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' freeciv-server + # in tests, and seem likely not necessary for freeciv-server. + "~@aio" "~@chown" "~@ipc" "~@keyring" "~@memlock" + "~@resources" "~@setuid" "~@sync" "~@timer" + ]; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + }; + }; + networking.firewall = mkIf cfg.openFirewall + { allowedTCPPorts = [ cfg.settings.port ]; }; + }; + meta.maintainers = with lib.maintainers; [ julm ]; +}