diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4a847d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result diff --git a/flake.nix b/flake.nix index b74252b..093cd33 100644 --- a/flake.nix +++ b/flake.nix @@ -11,9 +11,13 @@ ); packages = nixpkgs.lib.genAttrs [ "x86_64-linux" ] ( - system: nixpkgs.lib.genAttrs [ "pterodactyl" "php" "wings" ] ( - package: import ./packages/${package}.nix { pkgs = import nixpkgs { inherit system; }; }; - ) - }); + system: let + pkgs = import nixpkgs { inherit system; }; + in rec { + php = import ./packages/php.nix { inherit pkgs; }; + pterodactyl = import ./packages/pterodactyl.nix { inherit pkgs php; }; + wings = import ./packages/wings.nix { inherit pkgs; }; + } + ); }; } diff --git a/modules/pterodactyl.nix b/modules/pterodactyl.nix index 7dfbc1c..fc97a3b 100644 --- a/modules/pterodactyl.nix +++ b/modules/pterodactyl.nix @@ -2,20 +2,36 @@ { lib, config, pkgs, ... }: let - cfg = config.services.pterodactyl; - - flakePkgs = flake.outputs.packages.${pkgs.system}; + thisConfig = config.services.pterodactyl; defaultUser = "pterodactyl"; in { options.services.pterodactyl = { enable = lib.mkEnableOption "Enable the pterodacytl game server panel"; - proxy = { - enable = lib.mkEnableOption "Automatically configure Nginx to serve the panel"; - serverName = lib.mkOption { + 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 on which the panel will be hosted"; + description = "The canonical domain name on which the panel will be hosted"; default = "localhost"; example = "pterodactyl.example.com"; }; @@ -27,27 +43,46 @@ in { }; }; - user = lib.mkOption { - type = lib.types.str; - description = '' - The user that owns the files managed by the panel. - If you change this, make sure their files are accessible by your www user - (probably "nginx"). - ''; - default = defaultUser; - example = defaultUser; - }; + 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; + }; - pkg = lib.mkOption { - type = lib.types.package; - description = "The package in which to source the php files used by pterodactyl"; - default = flakePkgs.pterodactyl; - }; + user = lib.mkOption { + type = lib.types.str; + description = '' + The user that the php-fpm processes will run under. See also the `group` option. - dataDir = lib.mkOption { - type = lib.types.str; - description = "The directory in which to store stateful data"; - default = "/var/lib/pterodactyl"; + 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 = { @@ -55,42 +90,107 @@ in { name = lib.mkOption { type = lib.types.str; - description = "The Redis server name."; + description = "The Redis server name. Used when configuring locally"; default = "redis-pterodactyl"; }; port = lib.mkOption { type = lib.types.port; - description = "The port the redis server listens on"; + description = "The port Redis listens on"; default = 6379; }; + + host = lib.mkOption { + type = lib.types.str; + description = "The address with which to reach Redis"; + default = "127.0.0.1"; + }; + }; + + database = { + configureLocally = lib.mkEnableOption "Configure a local MariaDB instance"; + + type = lib.mkOption { + type = lib.types.str; + description = '' + The type of database pterodactyl should connect to (supports "mariadb" and "mysql") + ''; + default = "mariadb"; + }; + + host = lib.mkOption { + type = lib.types.str; + description = "The address with which to reach the database"; + default = "127.0.0.1"; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "The port the database listens on"; + default = 3336; + }; + + name = lib.mkOption { + type = lib.types.str; + description = "The name of the database that you've created in mysql / mariadb"; + default = "pterodactyl"; + }; + + username = lib.mkOption { + type = lib.types.str; + description = "The name of the user that you've created in mysql / mariadb"; + default = thisConfig.php.user; + }; }; }; - config = lib.mkIf cfg.enable { - users = lib.mkIf (cfg.user == defaultUser) { - users.${cfg.user} = { - isSystemUser = true; - createHome = true; - home = cfg.dataDir; - group = cfg.user; + 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.${cfg.user} = { }; - - users.${config.services.nginx.user}.extraGroups = [ cfg.user ]; + groups = { + ${thisConfig.php.group} = lib.mkIf (thisConfig.php.group == defaultUser) {}; + }; }; - services.redis.servers.${cfg.redis.name} = lib.mkIf cfg.redis.configureLocally { + services.redis.servers.${thisConfig.redis.name} = lib.mkIf thisConfig.redis.configureLocally { enable = true; - port = cfg.redis.port; + port = thisConfig.redis.port; }; - services.nginx = lib.mkIf cfg.proxy.enable { + services.mysql = lib.mkIf thisConfig.database.configureLocally { + enable = true; + package = pkgs.mariadb; + + ensureDatabases = [ thisConfig.database.name ]; + ensureUsers = [ + { + name = thisConfig.database.name; + ensurePermissions = { + "${thisConfig.database.name}.*" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + services.nginx = lib.mkIf thisConfig.server.enable { enable = true; - virtualHosts."${cfg.serverName}" = { - root = "${config.services.pterodactyl.pkg}/public"; + virtualHosts."pterodactyl" = { + serverName = thisConfig.server.name; + + root = "${thisConfig.php.root}/public"; extraConfig = '' index index.html index.htm index.php; @@ -101,7 +201,7 @@ in { include ${pkgs.nginx}/conf/fastcgi_params; fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:${config.services.phpfpm.pools.pterodactyl.socket}; + fastcgi_pass unix:${thisConfig.php.socket}; fastcgi_index index.php; @@ -127,7 +227,7 @@ in { }; services.phpfpm.pools.pterodactyl = { - user = cfg.user; + user = thisConfig.php.user; settings = { "listen.owner" = config.services.nginx.user; "pm" = "dynamic"; @@ -143,23 +243,125 @@ in { "php_admin_value[error_log]" = "stderr"; "php_admin_flag[daemonize]" = "false"; }; + phpEnv = { + APP_ENV = "production"; + APP_DEBUG = "false"; + # APP_KEY = ""; # TODO: Figure how to make this persistent but automatically generated + APP_THEME = "pterodactyl"; # TODO: Configure + APP_TIMEZONE = "Europe/Paris"; # TODO: Configure + APP_URL = "https://${thisConfig.server.name}"; # TODO: create another config option for this + APP_LOCALE = "en"; # TODO: Configure + APP_ENVIRONMENT_ONLY = "true"; + + LOG_CHANNEL = "daily"; + LOG_DEPRECATIONS_CHANNEL = "null"; + LOG_LEVEL = "debug"; + + DB_CONNECTION = thisConfig.database.type; + DB_HOST = thisConfig.database.host; + DB_PORT = builtins.toString thisConfig.database.port; + DB_DATABASE = thisConfig.database.name; + DB_USERNAME = thisConfig.database.username; + # DB_PASSWORD = ""; # TODO: allow a password + + REDIS_HOST = thisConfig.redis.host; + # REDIS_PASSWORD = ""; # TODO: allow a password + REDIS_PORT = builtins.toString thisConfig.redis.port; + + # TODO: Configure + CACHE_DRIVER = "redis"; + QUEUE_CONNECTION = "redis"; + SESSION_DRIVER = "redis"; + + HASHIDS_SALT = "Tbl~GYn;t6s'iKCDMe7u"; # TODO: configure + HASHIDS_LENGTH = "8"; + + # MAIL_MAILER = "smtp"; + # MAIL_HOST = "smtp.example.com"; + # MAIL_PORT = "25"; + # MAIL_USERNAME = ""; + # MAIL_PASSWORD = ""; + # MAIL_ENCRYPTION = "tls"; + # MAIL_FROM_ADDRESS = "no-reply@example.com"; + # MAIL_FROM_NAME="Pterodactyl Panel" + + # You should set this to your domain to prevent it defaulting to 'localhost', causing + # mail servers such as Gmail to reject your mail. + # + # @see: https://github.com/pterodactyl/panel/pull/3110 + # MAIL_EHLO_DOMAIN=panel.example.com + }; }; - systemd.services.pteroq = { - enable = true; - description = "Pterodactyl Queue Worker"; - after = [ "redis-${cfg.redis.name}.service" ]; - unitConfig = { StartLimitInterval = 180; }; - serviceConfig = { - User = cfg.user; - Group = cfg.user; - Restart = "always"; - ExecStart = - "${flakePkgs.php}/bin/php ${cfg.pkg}/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3"; - StartLimitBurst = 30; - RestartSec = "5s"; + 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" ]; + }; }; - wantedBy = [ "multi-user.target" ]; }; }; } diff --git a/packages/php.nix b/packages/php.nix index 6a6affa..9732524 100644 --- a/packages/php.nix +++ b/packages/php.nix @@ -1,12 +1,12 @@ -{ pkgs }: +{ pkgs ? import }: pkgs.php83.buildEnv { extensions = { enabled, all, }: enabled ++ (with all; [ redis - # xdebug + xdebug ]); - # extraConfig = '' - # xdebug.mode=debug - # ''; + extraConfig = '' + xdebug.mode=debug + ''; } diff --git a/packages/pterodactyl.nix b/packages/pterodactyl.nix index 15a8a24..6e3a168 100644 --- a/packages/pterodactyl.nix +++ b/packages/pterodactyl.nix @@ -1,9 +1,16 @@ -{ pkgs }: +{ pkgs ? import , php ? pkgs.php }: -let +php.buildComposerProject2 (finalAttrs: { + pname = "pterodactyl-panel"; version = "1.11.11"; -in pkgs.fetchzip { - url = "https://github.com/pterodactyl/panel/releases/download/v${ version }/panel.tar.gz"; - hash = "sha256-0nOHtReVUVXYQY/glS4x0gkbhetoXSWg4rRwOJlkcM8="; - stripRoot = false; -} + + src = pkgs.fetchzip { + url = "https://github.com/pterodactyl/panel/releases/download/v${finalAttrs.version}/panel.tar.gz"; + hash = "sha256-0nOHtReVUVXYQY/glS4x0gkbhetoXSWg4rRwOJlkcM8="; + stripRoot = false; + }; + + vendorHash = "sha256-WBzGGWCWNqvU4Ws7wBh8Ads7RBawBaxN1QNIUI4ivOQ="; + + inherit php; +}) diff --git a/packages/wings.nix b/packages/wings.nix index 9309948..ed923c5 100644 --- a/packages/wings.nix +++ b/packages/wings.nix @@ -1,4 +1,4 @@ -{ pkgs }: +{ pkgs ? import }: let version = "1.11.13";