Merge pull request #225274 from tie/nixos-pufferpanel
nixos/pufferpanel: init
This commit is contained in:
commit
ed7dd78b3f
5 changed files with 254 additions and 0 deletions
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
176
nixos/modules/services/misc/pufferpanel.nix
Normal file
176
nixos/modules/services/misc/pufferpanel.nix
Normal 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 ];
|
||||
}
|
|
@ -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 {};
|
||||
|
|
74
nixos/tests/pufferpanel.nix
Normal file
74
nixos/tests/pufferpanel.nix
Normal 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)
|
||||
'';
|
||||
})
|
Loading…
Reference in a new issue