Files
Battle-for-Tismo/nix/module.nix

143 lines
4.7 KiB
Nix

# NixOS module: runs the dedicated game server as a systemd service and, by
# default, serves the static web client through nginx on the same host.
#
# services.battle-for-tismo.enable = true;
# services.battle-for-tismo.web.hostName = "tismo.example.com";
#
# The web client connects to the page's own origin, so nginx serves the static
# files at "/" and forwards "/ws" to the game server.
#
# By default the server runs the immutable copy packaged in the Nix store, so
# updating the game means rebuilding the system configuration. To update the
# game on its own instead, point the service at a writable checkout:
#
# services.battle-for-tismo.appDirectory = "/opt/battle-for-tismo";
#
# then `git -C /opt/battle-for-tismo pull` and
# `systemctl restart battle-for-tismo`.
{ config, lib, pkgs, ... }:
let
cfg = config.services.battle-for-tismo;
pkg = pkgs.callPackage ./package.nix { };
in
{
options.services.battle-for-tismo = {
enable = lib.mkEnableOption "the Battle for 'Tismo game server";
appDirectory = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/opt/battle-for-tismo";
description = ''
Directory containing a writable checkout of the game (`client/`,
`server/` and `shared/`). When set, the server runs straight from this
directory with the Node.js from Nixpkgs, so the game can be updated
with a Git pull and a service restart instead of a system rebuild.
When null (the default) the immutable copy packaged in the Nix store
is used, and updates require a rebuild.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkg.server;
description = ''
Package providing the `battle-for-tismo-server` command. Ignored when
`appDirectory` is set.
'';
};
port = lib.mkOption {
type = lib.types.port;
default = 27015;
description = "Local WebSocket port the game server listens on.";
};
bindAddress = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = ''
Address the game server binds to. Keep the default to expose it only
through the reverse proxy; use "*" to accept direct connections.
'';
};
web = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Serve the static web client through nginx.";
};
package = lib.mkOption {
type = lib.types.package;
default = pkg.web;
description = ''
Directory containing the static web client (client/index.html). Point
this at the `appDirectory` to serve the mutable checkout as well.
'';
};
hostName = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "nginx virtual host name for the web client.";
};
enableSSL = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable ACME/HTTPS for the virtual host (browsers then use wss://).";
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Open the HTTP (and HTTPS when enabled) ports in the firewall.";
};
};
};
config = lib.mkIf cfg.enable {
systemd.services.battle-for-tismo = {
description = "Battle for 'Tismo game server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment.HOME = "%S/battle-for-tismo";
serviceConfig = {
ExecStart =
if cfg.appDirectory != null then
"${pkgs.nodejs}/bin/node ${toString cfg.appDirectory}/server/server.js"
+ " --port ${toString cfg.port} --bind ${cfg.bindAddress}"
else
"${cfg.package}/bin/battle-for-tismo-server --port ${toString cfg.port} --bind ${cfg.bindAddress}";
DynamicUser = true;
StateDirectory = "battle-for-tismo";
Restart = "on-failure";
RestartSec = 3;
};
};
services.nginx = lib.mkIf cfg.web.enable {
enable = true;
virtualHosts.${cfg.web.hostName} = {
root = cfg.web.package;
# The client lives under client/; send the bare root to its index.
locations."= /".return = "302 /client/index.html";
locations."/ws" = {
proxyPass = "http://127.0.0.1:${toString cfg.port}";
proxyWebsockets = true;
};
addSSL = cfg.web.enableSSL;
enableACME = cfg.web.enableSSL;
};
};
networking.firewall.allowedTCPPorts = lib.mkIf (cfg.web.enable && cfg.web.openFirewall) (
[ 80 ] ++ lib.optionals cfg.web.enableSSL [ 443 ]
);
};
}