Merge pull request #225274 from tie/nixos-pufferpanel

nixos/pufferpanel: init

Sandro ed7dd78b 2e532b8b

Changed files
+254
nixos
doc
manual
release-notes
modules
tests
+2
nixos/doc/manual/release-notes/rl-2305.section.md
···
- [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
+
- [PufferPanel](https://pufferpanel.com), game server management panel designed to be easy to use. Available as [services.pufferpanel](#opt-services.pufferpanel.enable).
+
- [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
- [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
+1
nixos/modules/module-list.nix
···
./services/misc/polaris.nix
./services/misc/portunus.nix
./services/misc/prowlarr.nix
+
./services/misc/pufferpanel.nix
./services/misc/pykms.nix
./services/misc/radarr.nix
./services/misc/readarr.nix
+176
nixos/modules/services/misc/pufferpanel.nix
···
+
{ config, pkgs, lib, ... }:
+
let
+
cfg = config.services.pufferpanel;
+
in
+
{
+
options.services.pufferpanel = {
+
enable = lib.mkOption {
+
type = lib.types.bool;
+
default = false;
+
description = lib.mdDoc ''
+
Whether to enable PufferPanel game management server.
+
+
Note that [PufferPanel templates] and binaries downloaded by PufferPanel
+
expect [FHS environment]. It is possible to set {option}`package` option
+
to use PufferPanel wrapper with FHS environment. For example, to use
+
`Download Game from Steam` and `Download Java` template operations:
+
```Nix
+
{ lib, pkgs, ... }: {
+
services.pufferpanel = {
+
enable = true;
+
extraPackages = with pkgs; [ bash curl gawk gnutar gzip ];
+
package = pkgs.buildFHSUserEnv {
+
name = "pufferpanel-fhs";
+
runScript = lib.getExe pkgs.pufferpanel;
+
targetPkgs = pkgs': with pkgs'; [ icu openssl zlib ];
+
};
+
};
+
}
+
```
+
+
[PufferPanel templates]: https://github.com/PufferPanel/templates
+
[FHS environment]: https://wikipedia.org/wiki/Filesystem_Hierarchy_Standard
+
'';
+
};
+
+
package = lib.mkPackageOptionMD pkgs "pufferpanel" { };
+
+
extraGroups = lib.mkOption {
+
type = lib.types.listOf lib.types.str;
+
default = [ ];
+
example = [ "podman" ];
+
description = lib.mdDoc ''
+
Additional groups for the systemd service.
+
'';
+
};
+
+
extraPackages = lib.mkOption {
+
type = lib.types.listOf lib.types.package;
+
default = [ ];
+
example = lib.literalExpression "[ pkgs.jre ]";
+
description = lib.mdDoc ''
+
Packages to add to the PATH environment variable. Both the {file}`bin`
+
and {file}`sbin` subdirectories of each package are added.
+
'';
+
};
+
+
environment = lib.mkOption {
+
type = lib.types.attrsOf lib.types.str;
+
default = { };
+
example = lib.literalExpression ''
+
{
+
PUFFER_WEB_HOST = ":8080";
+
PUFFER_DAEMON_SFTP_HOST = ":5657";
+
PUFFER_DAEMON_CONSOLE_BUFFER = "1000";
+
PUFFER_DAEMON_CONSOLE_FORWARD = "true";
+
PUFFER_PANEL_REGISTRATIONENABLED = "false";
+
}
+
'';
+
description = lib.mdDoc ''
+
Environment variables to set for the service. Secrets should be
+
specified using {option}`environmentFile`.
+
+
Refer to the [PufferPanel source code][] for the list of available
+
configuration options. Variable name is an upper-cased configuration
+
entry name with underscores instead of dots, prefixed with `PUFFER_`.
+
For example, `panel.settings.companyName` entry can be set using
+
{env}`PUFFER_PANEL_SETTINGS_COMPANYNAME`.
+
+
When running with panel enabled (configured with `PUFFER_PANEL_ENABLE`
+
environment variable), it is recommended disable registration using
+
`PUFFER_PANEL_REGISTRATIONENABLED` environment variable (registration is
+
enabled by default). To create the initial administrator user, run
+
{command}`pufferpanel --workDir /var/lib/pufferpanel user add --admin`.
+
+
Some options override corresponding settings set via web interface (e.g.
+
`PUFFER_PANEL_REGISTRATIONENABLED`). Those options can be temporarily
+
toggled or set in settings but do not persist between restarts.
+
+
[PufferPanel source code]: https://github.com/PufferPanel/PufferPanel/blob/master/config/entries.go
+
'';
+
};
+
+
environmentFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
description = lib.mdDoc ''
+
File to load environment variables from. Loaded variables override
+
values set in {option}`environment`.
+
'';
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
systemd.services.pufferpanel = {
+
description = "PufferPanel game management server";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
+
path = cfg.extraPackages;
+
environment = cfg.environment;
+
+
# Note that we export environment variables for service directories if the
+
# value is not set. An empty environment variable is considered to be set.
+
# E.g.
+
# export PUFFER_LOGS=${PUFFER_LOGS-$LOGS_DIRECTORY}
+
# would set PUFFER_LOGS to $LOGS_DIRECTORY if PUFFER_LOGS environment
+
# variable is not defined.
+
script = ''
+
${lib.concatLines (lib.mapAttrsToList (name: value: ''
+
export ${name}="''${${name}-${value}}"
+
'') {
+
PUFFER_LOGS = "$LOGS_DIRECTORY";
+
PUFFER_DAEMON_DATA_CACHE = "$CACHE_DIRECTORY";
+
PUFFER_DAEMON_DATA_SERVERS = "$STATE_DIRECTORY/servers";
+
PUFFER_DAEMON_DATA_BINARIES = "$STATE_DIRECTORY/binaries";
+
})}
+
exec ${lib.getExe cfg.package} run --workDir "$STATE_DIRECTORY"
+
'';
+
+
serviceConfig = {
+
Type = "simple";
+
Restart = "always";
+
+
UMask = "0077";
+
+
SupplementaryGroups = cfg.extraGroups;
+
+
StateDirectory = "pufferpanel";
+
StateDirectoryMode = "0700";
+
CacheDirectory = "pufferpanel";
+
CacheDirectoryMode = "0700";
+
LogsDirectory = "pufferpanel";
+
LogsDirectoryMode = "0700";
+
+
EnvironmentFile = cfg.environmentFile;
+
+
# Command "pufferpanel shutdown --pid $MAINPID" sends SIGTERM (code 15)
+
# to the main process and waits for termination. This is essentially
+
# KillMode=mixed we are using here. See
+
# https://freedesktop.org/software/systemd/man/systemd.kill.html#KillMode=
+
KillMode = "mixed";
+
+
DynamicUser = true;
+
ProtectHome = true;
+
ProtectProc = "invisible";
+
ProtectClock = true;
+
ProtectHostname = true;
+
ProtectControlGroups = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
PrivateUsers = true;
+
PrivateDevices = true;
+
RestrictRealtime = true;
+
RestrictNamespaces = [ "user" "mnt" ]; # allow buildFHSUserEnv
+
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+
LockPersonality = true;
+
DeviceAllow = [ "" ];
+
DevicePolicy = "closed";
+
CapabilityBoundingSet = [ "" ];
+
};
+
};
+
};
+
+
meta.maintainers = [ lib.maintainers.tie ];
+
}
+1
nixos/tests/all-tests.nix
···
pt2-clone = handleTest ./pt2-clone.nix {};
pykms = handleTest ./pykms.nix {};
public-inbox = handleTest ./public-inbox.nix {};
+
pufferpanel = handleTest ./pufferpanel.nix {};
pulseaudio = discoverTests (import ./pulseaudio.nix);
qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
+74
nixos/tests/pufferpanel.nix
···
+
import ./make-test-python.nix ({ lib, ... }: {
+
name = "pufferpanel";
+
meta.maintainers = [ lib.maintainers.tie ];
+
+
nodes.machine = { pkgs, ... }: {
+
environment.systemPackages = [ pkgs.pufferpanel ];
+
services.pufferpanel = {
+
enable = true;
+
extraPackages = [ pkgs.netcat ];
+
environment = {
+
PUFFER_PANEL_REGISTRATIONENABLED = "false";
+
PUFFER_PANEL_SETTINGS_COMPANYNAME = "NixOS";
+
};
+
};
+
};
+
+
testScript = ''
+
import shlex
+
import json
+
+
curl = "curl --fail-with-body --silent"
+
baseURL = "http://localhost:8080"
+
adminName = "admin"
+
adminEmail = "admin@nixos.org"
+
adminPass = "admin"
+
adminCreds = json.dumps({
+
"email": adminEmail,
+
"password": adminPass,
+
})
+
stopCode = 9 # SIGKILL
+
serverPort = 1337
+
serverDefinition = json.dumps({
+
"name": "netcat",
+
"node": 0,
+
"users": [
+
adminName,
+
],
+
"type": "netcat",
+
"run": {
+
"stopCode": stopCode,
+
"command": f"nc -l {serverPort}",
+
},
+
"environment": {
+
"type": "standard",
+
},
+
})
+
+
start_all()
+
+
machine.wait_for_unit("pufferpanel.service")
+
machine.wait_for_open_port(5657) # SFTP
+
machine.wait_for_open_port(8080) # HTTP
+
+
# Note that PufferPanel does not initialize database unless necessary.
+
# /api/config endpoint creates database file and triggers migrations.
+
# On success, we run a command to create administrator user that we use to
+
# interact with HTTP API.
+
resp = json.loads(machine.succeed(f"{curl} {baseURL}/api/config"))
+
assert resp["branding"]["name"] == "NixOS", "Invalid company name in configuration"
+
assert resp["registrationEnabled"] == False, "Expected registration to be disabled"
+
+
machine.succeed(f"pufferpanel --workDir /var/lib/pufferpanel user add --admin --name {adminName} --email {adminEmail} --password {adminPass}")
+
+
resp = json.loads(machine.succeed(f"{curl} -d '{adminCreds}' {baseURL}/auth/login"))
+
assert "servers.admin" in resp["scopes"], "User is not administrator"
+
token = resp["session"]
+
authHeader = shlex.quote(f"Authorization: Bearer {token}")
+
+
resp = json.loads(machine.succeed(f"{curl} -H {authHeader} -H 'Content-Type: application/json' -d '{serverDefinition}' {baseURL}/api/servers"))
+
serverID = resp["id"]
+
machine.succeed(f"{curl} -X POST -H {authHeader} {baseURL}/proxy/daemon/server/{serverID}/start")
+
machine.wait_for_open_port(serverPort)
+
'';
+
})