pterodactyl-nix/modules/pterodactyl.nix
2025-07-07 22:07:05 +02:00

262 lines
7.9 KiB
Nix

{ flake }:
{ lib, config, pkgs, ... }:
let
thisConfig = config.services.pterodactyl;
defaultUser = "pterodactyl";
in {
options.services.pterodactyl = {
enable = lib.mkEnableOption "Enable the pterodacytl game server panel";
pkg = lib.mkOption {
type = lib.types.package;
description = "The package in which to source the php files used by pterodactyl";
default = flake.outputs.packages.${pkgs.system}.pterodactyl;
};
dataDir = lib.mkOption {
type = lib.types.str;
description = "The directory in which to store stateful data";
default = "/data/services/pterodactyl";
};
server = {
enable = lib.mkEnableOption ''
Automatically configure Nginx to serve the panel
If you need more control over the nginx proxy, for now you must leave this option disabled
and write your own nginx configuration. Feel free to copy ours as a starting point.
'';
name = lib.mkOption {
type = lib.types.str;
description = "The canonical domain name on which the panel will be hosted";
default = "localhost";
example = "pterodactyl.example.com";
};
listenAddresses = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "The addresses the nginx server should listen on";
default = [ "127.0.0.1" "[::1]" ];
};
};
php = {
socket = lib.mkOption {
type = lib.types.path;
description = "The location of the unix socket used by php-fpm";
default = config.services.phpfpm.pools.pterodactyl.socket;
};
user = lib.mkOption {
type = lib.types.str;
description = ''
The user that the php-fpm processes will run under. See also the `group` option.
If you change this, make sure the unix socket is accessible by your www user
(probably "nginx").
'';
default = defaultUser;
};
group = lib.mkOption {
type = lib.types.str;
description = ''
The group that the php-fpm processes will run under. See also the `user` option.
If you change this, make sure the unix socket is accessible by your www user
(probably "nginx").
'';
default = defaultUser;
};
root = lib.mkOption {
type = lib.types.path;
description = "The main application directory";
default = "/srv/pterodactyl";
};
pkg = lib.mkOption {
type = lib.types.package;
description = "The php package to use";
default = flake.outputs.packages.${pkgs.system}.php;
};
};
redis = {
configureLocally = lib.mkEnableOption "Configure a local redis server for pterodactyl";
name = lib.mkOption {
type = lib.types.str;
description = "The Redis server name.";
default = "redis-pterodactyl";
};
port = lib.mkOption {
type = lib.types.port;
description = "The port the redis server listens on";
default = 6379;
};
};
};
config = lib.mkIf thisConfig.enable {
users = {
users = {
${thisConfig.php.user} = lib.mkIf (thisConfig.php.user == defaultUser) {
isSystemUser = true;
createHome = true;
home = "/var/lib/pterodactyl";
group = thisConfig.php.group;
};
${config.services.nginx.user} = lib.mkIf (thisConfig.php.group == defaultUser) {
extraGroups = [ thisConfig.php.group ];
};
};
groups = {
${thisConfig.php.group} = lib.mkIf (thisConfig.php.group == defaultUser) {};
};
};
services.redis.servers.${thisConfig.redis.name} = lib.mkIf thisConfig.redis.configureLocally {
enable = true;
port = thisConfig.redis.port;
};
services.nginx = lib.mkIf thisConfig.server.enable {
enable = true;
virtualHosts."pterodactyl" = {
serverName = thisConfig.server.name;
root = "${config.services.pterodactyl.pkg}/public";
extraConfig = ''
index index.html index.htm index.php;
'';
locations = {
"~ \\.php$".extraConfig = ''
include ${pkgs.nginx}/conf/fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:${thisConfig.php.socket};
fastcgi_index index.php;
fastcgi_param PHP_VALUE "upload_max_filesize = 100M \n post_max_size=100M";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_PROXY "";
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
'';
"/" = {
tryFiles = "$uri $uri/ /index.php?$query_string";
};
};
};
};
services.phpfpm.pools.pterodactyl = {
user = thisConfig.php.user;
settings = {
"listen.owner" = config.services.nginx.user;
"pm" = "dynamic";
"pm.start_servers" = 4;
"pm.min_spare_servers" = 4;
"pm.max_spare_servers" = 16;
"pm.max_children" = 64;
"pm.max_requests" = 256;
"clear_env" = false;
"catch_workers_output" = true;
"decorate_workers_output" = false;
"php_admin_value[error_log]" = "stderr";
"php_admin_flag[daemonize]" = "false";
};
};
systemd = {
tmpfiles.settings."10-pterodactyl" = let
user = thisConfig.php.user;
group = thisConfig.php.group;
in {
"${thisConfig.dataDir}/cache" = { "d" = { inherit group user; mode = "750"; }; };
"${thisConfig.dataDir}/storage" = { "d" = { inherit group user; mode = "750"; }; };
"${thisConfig.php.root}" = { "d" = { inherit group user; mode = "750"; }; };
};
services = {
pterodactyl-setup = {
description = "Setup the necessary files for the pterodactyl panel to work";
before = [ "pteroq.service" ]
++ (if thisConfig.server.enable then [ "nginx.service" ] else []);
wantedBy = [ "multi-user.target" "pteroq.service" ]
++ (if thisConfig.server.enable then [ "nginx.service" ] else []);
serviceConfig = {
Type = "oneshot";
User = thisConfig.php.user;
Group = thisConfig.php.group;
ExecStart = pkgs.writeShellScript "pterodactyl-setup.sh" ''
SRC="${thisConfig.pkg}/share/php/pterodactyl-panel/";
DEST="${thisConfig.php.root}";
USER="${thisConfig.php.user}";
GROUP="${thisConfig.php.group}";
rm $DEST/* -r;
for file in app config database public resources routes vendor artisan; do
cp -r "$SRC/$file" "$DEST"
chmod -R 0550 "$DEST/$file"
done;
mkdir -p -m 0750 "$DEST/bootstrap"
cp "$SRC/bootstrap/app.php" "$DEST/bootstrap/app.php";
chmod 0550 "$DEST/bootstrap/app.php";
# ln -s "${thisConfig.dataDir}/cache" "$DEST/bootstrap/cache";
ln -s "${thisConfig.dataDir}/storage" "$DEST/storage";
'';
};
};
pteroq = {
enable = true;
description = "Pterodactyl Queue Worker";
after = [ "redis-${thisConfig.redis.name}.service" ];
unitConfig = { StartLimitInterval = 180; };
serviceConfig = {
User = thisConfig.php.user;
Group = thisConfig.php.group;
WorkingDirectory = thisConfig.php.root;
ExecStart = "${thisConfig.php.pkg}/bin/php ${thisConfig.php.root}/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3";
Restart = "always";
RestartSec = "5s";
StartLimitBurst = 30;
};
wantedBy = [ "multi-user.target" ];
};
};
};
};
}