summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/games/freeciv.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/services/games/freeciv.nix')
-rw-r--r--nixos/modules/services/games/freeciv.nix187
1 files changed, 187 insertions, 0 deletions
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 ];
+}