{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.vuizvui.services.starbound;
mkListenerOptions = what: defaultPort: {
bind = mkOption {
type = types.str;
default = "::";
description = ''
Host/IP address to listen for incoming connections to the ${what}.
'';
};
port = mkOption {
type = types.int;
default = defaultPort;
description = ''
Port to listen for incoming connections to the ${what}.
'';
};
};
serverConfig = {
allowAdminCommands = cfg.adminCommands.allow;
allowAdminCommandsFromAnyone = cfg.adminCommands.allowFromAnyone;
allowAnonymousConnections = cfg.anonymousConnections.allow;
anonymousConnectionsAreAdmin = cfg.anonymousConnections.adminPrivileges;
serverUsers = mapAttrs (user: attrs: {
inherit (attrs) admin password;
}) cfg.users;
inherit (cfg) checkAssetsDigest clearPlayerFiles clearUniverseFiles;
inherit (cfg) maxPlayers safeScripts serverName;
inherit (cfg) upnpPortForwarding;
gameServerBind = cfg.bind;
gameServerPort = cfg.port;
bannedIPs = cfg.bannedIPs;
bannedUuids = cfg.bannedUUIDs;
runRconServer = cfg.rconServer.enable;
rconServerBind = cfg.rconServer.bind;
rconServerPort = cfg.rconServer.port;
rconServerPassword = cfg.rconServer.password;
rconServerTimeout = cfg.rconServer.timeout;
runQueryServer = cfg.queryServer.enable;
queryServerBind = cfg.queryServer.bind;
queryServerPort = cfg.queryServer.port;
} // cfg.extraConfig;
bootConfig = pkgs.runCommand "sbboot.config" {
overrides = pkgs.writeText "sbboot.overrides" (builtins.toJSON {
logFileBackups = 0;
modSource = "";
storageDirectory = cfg.dataDir;
defaultConfiguration = serverConfig;
});
} ''
"${pkgs.jq}/bin/jq" -s '.[0] * .[1]' \
"${cfg.package}/etc/sbboot.config" "$overrides" \
> "$out"
'';
# Traverse a given path with ../ until we get to the root directory (/).
gotoRoot = p: concatStringsSep "/" (map (const "..") (splitString "/" p));
in {
options.vuizvui.services.starbound = {
enable = mkEnableOption "Starbound game server";
adminCommands = {
allow = mkOption {
type = types.bool;
default = true;
description = ''
Whether to allow admin commands in general.
'';
# XXX: Make this dependant on whether an account is defined with enabled
# admin.
};
allowFromAnyone = mkOption {
type = types.bool;
default = false;
description = ''
Allow anyone, even anonymous users to use admin commands.
'';
# XXX: Check whether this is true!
};
};
anonymousConnections = {
allow = mkOption {
type = types.bool;
default = true;
description = ''
Whether to allow anonymous connections to the server.
Set this to false and use
to only allow specific accounts to
connect.
'';
};
adminPrivileges = mkOption {
type = types.bool;
default = false;
description = ''
Whether all anonymous connections have administrative privileges.
'';
};
};
users = mkOption {
type = types.attrsOf (types.submodule {
options.admin = mkOption {
type = types.bool;
default = false;
description = ''
Whether this user has admin privileges.
'';
};
options.password = mkOption {
type = types.str;
example = "supersecure";
description = ''
The password for the user.
'';
};
});
default = {};
description = ''
User accounts to allow connection to the Starbound server.
'';
};
checkAssetsDigest = mkOption {
type = types.bool;
default = false;
description = ''
Check whether the assets on the client match the ones from the server
and deny connection if they don't match.
'';
};
clearPlayerFiles = mkOption {
# XXX: Figure out the exact semantics of this.
type = types.bool;
default = false;
description = ''
Forces players to use new characters or to have no gear or tech.
'';
};
clearUniverseFiles = mkOption {
# XXX: Figure out the exact semantics of this.
type = types.bool;
default = false;
description = ''
Forces player characters to use fresh universe data and navigation maps.
'';
};
bannedIPs = mkOption {
type = types.listOf types.str;
default = [];
description = ''
IP addresses disallowed for connection to the server.
'';
};
bannedUUIDs = mkOption {
type = types.listOf types.str;
default = [];
description = ''
User IDs disallowed for connection to the server.
'';
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/starbound";
description = ''
The directory where Starbound stores its universe/player files.
'';
};
package = mkOption {
type = types.package;
default = pkgs.vuizvui.games.steam.starbound;
defaultText = "pkgs.vuizvui.games.steam.starbound";
description = ''
The starbound package to use for running this game server.
'';
};
extraConfig = mkOption {
type = types.attrs;
default = {};
description = ''
Extra configuration options to add to the server config.
'';
};
rconServer = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to run an RCON server which allows to run administrative
commands on this game server instance.
See the RCON protocol documentation for more information about this.
'';
};
password = mkOption {
type = types.str;
default = "";
description = ''
The password needed to authorize with the RCON server.
'';
};
timeout = mkOption {
type = types.int;
default = 1000;
# XXX: Find out what this timeout is for and whether it's in seconds.
description = ''
After how many seconds the RCON server drops the connection.
'';
};
} // mkListenerOptions "RCON server" 21026;
queryServer = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to run a query server that shows information such as currently
connected players.
'';
};
} // mkListenerOptions "query server" 21025;
safeScripts = mkOption {
type = types.bool;
default = true;
# XXX: The description is just a guess and we need to find out what this
# really does.
description = ''
This is to make sure scripts can't call unsafe functions.
'';
};
serverName = mkOption {
type = types.str;
default = "A Starbound Server";
example = "My shiny Starbound Server";
description = ''
A short description or name of the Starbound server to run.
'';
};
upnpPortForwarding = mkOption {
type = types.bool;
default = true;
description = ''
Whether to use UPnP to forward ports from NAT gateways.
'';
};
maxPlayers = mkOption {
type = types.int;
default = 8;
description = ''
Maximum amount of players to allow concurrently.
'';
};
} // mkListenerOptions "game server" 21025;
config = mkIf cfg.enable {
users.groups.starbound = {
gid = config.ids.gids.starbound;
};
users.users.starbound = {
uid = config.ids.uids.starbound;
description = "Starbound Game Server User";
group = "starbound";
home = cfg.dataDir;
createHome = true;
};
systemd.services.starbound = {
description = "Starbound Server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "fs.target" ];
serviceConfig = {
User = "starbound";
Group = "starbound";
PrivateTmp = true;
KillSignal = "SIGINT";
ExecStart = toString [
"${cfg.package}/bin/starbound-server"
"-bootconfig \"${bootConfig}\""
# Workaround to disable logging to file
"-logfile \"${gotoRoot cfg.dataDir}/dev/null\""
"-verbose"
];
};
};
};
}