Merge pull request #225274 from tie/nixos-pufferpanel

nixos/pufferpanel: init
This commit is contained in:
Sandro 2023-04-18 16:57:14 +02:00 committed by GitHub
commit ed7dd78b3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 254 additions and 0 deletions

View file

@ -89,6 +89,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [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).

View file

@ -670,6 +670,7 @@
./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

View file

@ -0,0 +1,176 @@
{ 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 ];
}

View file

@ -587,6 +587,7 @@ in {
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 {};

View file

@ -0,0 +1,74 @@
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)
'';
})