lol

nixos/pufferpanel: init

+254
+2
nixos/doc/manual/release-notes/rl-2305.section.md
··· 81 81 82 82 - [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). 83 83 84 + - [PufferPanel](https://pufferpanel.com), game server management panel designed to be easy to use. Available as [services.pufferpanel](#opt-services.pufferpanel.enable). 85 + 84 86 - [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable). 85 87 86 88 - [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
··· 667 667 ./services/misc/polaris.nix 668 668 ./services/misc/portunus.nix 669 669 ./services/misc/prowlarr.nix 670 + ./services/misc/pufferpanel.nix 670 671 ./services/misc/pykms.nix 671 672 ./services/misc/radarr.nix 672 673 ./services/misc/readarr.nix
+176
nixos/modules/services/misc/pufferpanel.nix
··· 1 + { config, pkgs, lib, ... }: 2 + let 3 + cfg = config.services.pufferpanel; 4 + in 5 + { 6 + options.services.pufferpanel = { 7 + enable = lib.mkOption { 8 + type = lib.types.bool; 9 + default = false; 10 + description = lib.mdDoc '' 11 + Whether to enable PufferPanel game management server. 12 + 13 + Note that [PufferPanel templates] and binaries downloaded by PufferPanel 14 + expect [FHS environment]. It is possible to set {option}`package` option 15 + to use PufferPanel wrapper with FHS environment. For example, to use 16 + `Download Game from Steam` and `Download Java` template operations: 17 + ```Nix 18 + { lib, pkgs, ... }: { 19 + services.pufferpanel = { 20 + enable = true; 21 + extraPackages = with pkgs; [ bash curl gawk gnutar gzip ]; 22 + package = pkgs.buildFHSUserEnv { 23 + name = "pufferpanel-fhs"; 24 + runScript = lib.getExe pkgs.pufferpanel; 25 + targetPkgs = pkgs': with pkgs'; [ icu openssl zlib ]; 26 + }; 27 + }; 28 + } 29 + ``` 30 + 31 + [PufferPanel templates]: https://github.com/PufferPanel/templates 32 + [FHS environment]: https://wikipedia.org/wiki/Filesystem_Hierarchy_Standard 33 + ''; 34 + }; 35 + 36 + package = lib.mkPackageOptionMD pkgs "pufferpanel" { }; 37 + 38 + extraGroups = lib.mkOption { 39 + type = lib.types.listOf lib.types.str; 40 + default = [ ]; 41 + example = [ "podman" ]; 42 + description = lib.mdDoc '' 43 + Additional groups for the systemd service. 44 + ''; 45 + }; 46 + 47 + extraPackages = lib.mkOption { 48 + type = lib.types.listOf lib.types.package; 49 + default = [ ]; 50 + example = lib.literalExpression "[ pkgs.jre ]"; 51 + description = lib.mdDoc '' 52 + Packages to add to the PATH environment variable. Both the {file}`bin` 53 + and {file}`sbin` subdirectories of each package are added. 54 + ''; 55 + }; 56 + 57 + environment = lib.mkOption { 58 + type = lib.types.attrsOf lib.types.str; 59 + default = { }; 60 + example = lib.literalExpression '' 61 + { 62 + PUFFER_WEB_HOST = ":8080"; 63 + PUFFER_DAEMON_SFTP_HOST = ":5657"; 64 + PUFFER_DAEMON_CONSOLE_BUFFER = "1000"; 65 + PUFFER_DAEMON_CONSOLE_FORWARD = "true"; 66 + PUFFER_PANEL_REGISTRATIONENABLED = "false"; 67 + } 68 + ''; 69 + description = lib.mdDoc '' 70 + Environment variables to set for the service. Secrets should be 71 + specified using {option}`environmentFile`. 72 + 73 + Refer to the [PufferPanel source code][] for the list of available 74 + configuration options. Variable name is an upper-cased configuration 75 + entry name with underscores instead of dots, prefixed with `PUFFER_`. 76 + For example, `panel.settings.companyName` entry can be set using 77 + {env}`PUFFER_PANEL_SETTINGS_COMPANYNAME`. 78 + 79 + When running with panel enabled (configured with `PUFFER_PANEL_ENABLE` 80 + environment variable), it is recommended disable registration using 81 + `PUFFER_PANEL_REGISTRATIONENABLED` environment variable (registration is 82 + enabled by default). To create the initial administrator user, run 83 + {command}`pufferpanel --workDir /var/lib/pufferpanel user add --admin`. 84 + 85 + Some options override corresponding settings set via web interface (e.g. 86 + `PUFFER_PANEL_REGISTRATIONENABLED`). Those options can be temporarily 87 + toggled or set in settings but do not persist between restarts. 88 + 89 + [PufferPanel source code]: https://github.com/PufferPanel/PufferPanel/blob/master/config/entries.go 90 + ''; 91 + }; 92 + 93 + environmentFile = lib.mkOption { 94 + type = lib.types.nullOr lib.types.path; 95 + default = null; 96 + description = lib.mdDoc '' 97 + File to load environment variables from. Loaded variables override 98 + values set in {option}`environment`. 99 + ''; 100 + }; 101 + }; 102 + 103 + config = lib.mkIf cfg.enable { 104 + systemd.services.pufferpanel = { 105 + description = "PufferPanel game management server"; 106 + wantedBy = [ "multi-user.target" ]; 107 + after = [ "network.target" ]; 108 + 109 + path = cfg.extraPackages; 110 + environment = cfg.environment; 111 + 112 + # Note that we export environment variables for service directories if the 113 + # value is not set. An empty environment variable is considered to be set. 114 + # E.g. 115 + # export PUFFER_LOGS=${PUFFER_LOGS-$LOGS_DIRECTORY} 116 + # would set PUFFER_LOGS to $LOGS_DIRECTORY if PUFFER_LOGS environment 117 + # variable is not defined. 118 + script = '' 119 + ${lib.concatLines (lib.mapAttrsToList (name: value: '' 120 + export ${name}="''${${name}-${value}}" 121 + '') { 122 + PUFFER_LOGS = "$LOGS_DIRECTORY"; 123 + PUFFER_DAEMON_DATA_CACHE = "$CACHE_DIRECTORY"; 124 + PUFFER_DAEMON_DATA_SERVERS = "$STATE_DIRECTORY/servers"; 125 + PUFFER_DAEMON_DATA_BINARIES = "$STATE_DIRECTORY/binaries"; 126 + })} 127 + exec ${lib.getExe cfg.package} run --workDir "$STATE_DIRECTORY" 128 + ''; 129 + 130 + serviceConfig = { 131 + Type = "simple"; 132 + Restart = "always"; 133 + 134 + UMask = "0077"; 135 + 136 + SupplementaryGroups = cfg.extraGroups; 137 + 138 + StateDirectory = "pufferpanel"; 139 + StateDirectoryMode = "0700"; 140 + CacheDirectory = "pufferpanel"; 141 + CacheDirectoryMode = "0700"; 142 + LogsDirectory = "pufferpanel"; 143 + LogsDirectoryMode = "0700"; 144 + 145 + EnvironmentFile = cfg.environmentFile; 146 + 147 + # Command "pufferpanel shutdown --pid $MAINPID" sends SIGTERM (code 15) 148 + # to the main process and waits for termination. This is essentially 149 + # KillMode=mixed we are using here. See 150 + # https://freedesktop.org/software/systemd/man/systemd.kill.html#KillMode= 151 + KillMode = "mixed"; 152 + 153 + DynamicUser = true; 154 + ProtectHome = true; 155 + ProtectProc = "invisible"; 156 + ProtectClock = true; 157 + ProtectHostname = true; 158 + ProtectControlGroups = true; 159 + ProtectKernelLogs = true; 160 + ProtectKernelModules = true; 161 + ProtectKernelTunables = true; 162 + PrivateUsers = true; 163 + PrivateDevices = true; 164 + RestrictRealtime = true; 165 + RestrictNamespaces = [ "user" "mnt" ]; # allow buildFHSUserEnv 166 + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; 167 + LockPersonality = true; 168 + DeviceAllow = [ "" ]; 169 + DevicePolicy = "closed"; 170 + CapabilityBoundingSet = [ "" ]; 171 + }; 172 + }; 173 + }; 174 + 175 + meta.maintainers = [ lib.maintainers.tie ]; 176 + }
+1
nixos/tests/all-tests.nix
··· 587 587 pt2-clone = handleTest ./pt2-clone.nix {}; 588 588 pykms = handleTest ./pykms.nix {}; 589 589 public-inbox = handleTest ./public-inbox.nix {}; 590 + pufferpanel = handleTest ./pufferpanel.nix {}; 590 591 pulseaudio = discoverTests (import ./pulseaudio.nix); 591 592 qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {}; 592 593 qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
+74
nixos/tests/pufferpanel.nix
··· 1 + import ./make-test-python.nix ({ lib, ... }: { 2 + name = "pufferpanel"; 3 + meta.maintainers = [ lib.maintainers.tie ]; 4 + 5 + nodes.machine = { pkgs, ... }: { 6 + environment.systemPackages = [ pkgs.pufferpanel ]; 7 + services.pufferpanel = { 8 + enable = true; 9 + extraPackages = [ pkgs.netcat ]; 10 + environment = { 11 + PUFFER_PANEL_REGISTRATIONENABLED = "false"; 12 + PUFFER_PANEL_SETTINGS_COMPANYNAME = "NixOS"; 13 + }; 14 + }; 15 + }; 16 + 17 + testScript = '' 18 + import shlex 19 + import json 20 + 21 + curl = "curl --fail-with-body --silent" 22 + baseURL = "http://localhost:8080" 23 + adminName = "admin" 24 + adminEmail = "admin@nixos.org" 25 + adminPass = "admin" 26 + adminCreds = json.dumps({ 27 + "email": adminEmail, 28 + "password": adminPass, 29 + }) 30 + stopCode = 9 # SIGKILL 31 + serverPort = 1337 32 + serverDefinition = json.dumps({ 33 + "name": "netcat", 34 + "node": 0, 35 + "users": [ 36 + adminName, 37 + ], 38 + "type": "netcat", 39 + "run": { 40 + "stopCode": stopCode, 41 + "command": f"nc -l {serverPort}", 42 + }, 43 + "environment": { 44 + "type": "standard", 45 + }, 46 + }) 47 + 48 + start_all() 49 + 50 + machine.wait_for_unit("pufferpanel.service") 51 + machine.wait_for_open_port(5657) # SFTP 52 + machine.wait_for_open_port(8080) # HTTP 53 + 54 + # Note that PufferPanel does not initialize database unless necessary. 55 + # /api/config endpoint creates database file and triggers migrations. 56 + # On success, we run a command to create administrator user that we use to 57 + # interact with HTTP API. 58 + resp = json.loads(machine.succeed(f"{curl} {baseURL}/api/config")) 59 + assert resp["branding"]["name"] == "NixOS", "Invalid company name in configuration" 60 + assert resp["registrationEnabled"] == False, "Expected registration to be disabled" 61 + 62 + machine.succeed(f"pufferpanel --workDir /var/lib/pufferpanel user add --admin --name {adminName} --email {adminEmail} --password {adminPass}") 63 + 64 + resp = json.loads(machine.succeed(f"{curl} -d '{adminCreds}' {baseURL}/auth/login")) 65 + assert "servers.admin" in resp["scopes"], "User is not administrator" 66 + token = resp["session"] 67 + authHeader = shlex.quote(f"Authorization: Bearer {token}") 68 + 69 + resp = json.loads(machine.succeed(f"{curl} -H {authHeader} -H 'Content-Type: application/json' -d '{serverDefinition}' {baseURL}/api/servers")) 70 + serverID = resp["id"] 71 + machine.succeed(f"{curl} -X POST -H {authHeader} {baseURL}/proxy/daemon/server/{serverID}/start") 72 + machine.wait_for_open_port(serverPort) 73 + ''; 74 + })