Skip to content

Instantly share code, notes, and snippets.

@theoparis
Created August 11, 2025 08:05
Show Gist options
  • Save theoparis/5a4306f2bbf534ee15b8db28b74bbcaf to your computer and use it in GitHub Desktop.
Save theoparis/5a4306f2bbf534ee15b8db28b74bbcaf to your computer and use it in GitHub Desktop.
pyrodactyl nixos setup (WIP)
{
pkgs,
...
}: {
virtualisation.docker.enable = true;
virtualisation.docker.storageDriver = "btrfs";
services.pyrodactyl.enable = true;
services.pyrodactyl.url = "https://pyro.example.com";
services.mysql.enable = true;
services.mysql.package = pkgs.mariadb;
services.redis.enable = true;
services.caddy = {
enable = true;
virtualHosts = {
"pyro.example.com".extraConfig = ''
reverse_proxy http://localhost:8080
'';
};
};
}
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.pyrodactyl;
php = (
pkgs.php.withExtensions (
{
enabled,
all,
}:
enabled
++ (with all; [
openssl
gd
pdo_mysql
pdo
mbstring
tokenizer
bcmath
xml
dom
curl
zip
])
)
);
in
{
options = {
services.pyrodactyl = {
enable = lib.mkEnableOption "Enable Pyrodactyl panel";
url = lib.mkOption {
type = lib.types.str;
description = "The url where pyrodactyl is accessible";
};
databasePassword = lib.mkOption {
type = lib.types.str;
description = "Database password";
};
listenAddress = lib.mkOption {
type = lib.types.str;
default = "localhost";
description = "Address for the built-in Laravel server to bind.";
};
listenPort = lib.mkOption {
type = lib.types.int;
default = 8080;
description = "Port for the built-in Laravel server.";
};
adminUser = {
email = lib.mkOption {
type = lib.types.str;
default = "[email protected]";
};
username = lib.mkOption {
type = lib.types.str;
default = "admin";
};
firstName = lib.mkOption {
type = lib.types.str;
default = "Admin";
};
lastName = lib.mkOption {
type = lib.types.str;
default = "User";
};
password = lib.mkOption {
type = lib.types.str;
description = "Admin user password.";
default = "admin";
};
};
package = lib.mkOption {
type = lib.types.package;
default =
with pkgs;
pkgs.php.buildComposerProject2 (finalAttrs: {
pname = "pyrodactyl";
version = "4.0.0-dev";
# src = self;
src = fetchFromGitHub {
owner = "pyrohost";
repo = "pyrodactyl";
rev = "4c3bccf1d5533f7fc55bdbcf4a371219818d41c4";
hash = "sha256-SBiRdG8NuK9uccX+qZ9IFpcK1YEPksm8iPBXLoUMO4w=";
};
nativeBuildInputs = [
nodejs_latest
gitMinimal
pnpm.configHook
];
buildInputs = [
php
];
vendorHash = "sha256-gomWp2zyD+OFbyuD8NqjTaJChTsle5/tvqcDkPXqM64=";
pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs) pname version src;
fetcherVersion = 2;
hash = "sha256-1wcKqnHHTcfBM39h5K1PaDLUwR3cAxYRbngvtU95FZc=";
};
buildPhase = ''
pnpm run build
'';
# postInstall = ''
# cp .env.example ${placeholder "out"}/
# '';
});
};
};
};
config = lib.mkIf cfg.enable {
users.users.pyrodactyl = {
isSystemUser = true;
home = "/var/lib/pyrodactyl";
createHome = true;
group = "pyrodactyl";
};
users.groups.pyrodactyl = { };
environment.systemPackages = [ cfg.package ];
systemd.services.pyrodactyl-copy = {
description = "Copy pyrodactyl files to /var/lib/pyrodactyl";
before = [
"pyrodactyl-db-init.service"
"pyrodactyl.service"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
User = "root"; # Needs permission to copy from Nix store and chown
};
script = ''
set -eu
if [ -z "$(ls -A /var/lib/pyrodactyl)" ]; then
cp -ar ${cfg.package}/share/php/pyrodactyl/{*,.*} /var/lib/pyrodactyl/
chown -R pyrodactyl:pyrodactyl /var/lib/pyrodactyl
chmod -R 0755 /var/lib/pyrodactyl
fi
'';
};
systemd.services.mariadb-user-setup = {
description = "Ensure MariaDB pyrodactyl user, database, and permissions";
wants = [ "mysql.service" ];
after = [ "mysql.service" ];
serviceConfig = {
Type = "oneshot";
};
script = ''
set -eu
# Use socket auth to connect as root, no password needed
${pkgs.mariadb}/bin/mysql -u root <<SQL
CREATE DATABASE IF NOT EXISTS pyrodactyl;
CREATE USER IF NOT EXISTS 'pyro'@'localhost' IDENTIFIED BY 'supersecret';
GRANT ALL PRIVILEGES ON pyrodactyl.* TO 'pyro'@'localhost';
FLUSH PRIVILEGES;
SQL
'';
wantedBy = [ "multi-user.target" ];
};
# Initial DB + .env setup
systemd.services.pyrodactyl-db-init = {
description = "Initialise Pyrodactyl database";
after = [
"mysql.service"
"mariadb-user-setup.service"
"pyrodactyl-copy.service"
];
wants = [
"mysql.service"
"mariadb-user-setup.service"
"pyrodactyl-copy.service"
];
before = [ "pyrodactyl.service" ];
serviceConfig = {
Type = "oneshot";
User = "pyrodactyl";
Group = "pyrodactyl";
WorkingDirectory = "/var/lib/pyrodactyl";
};
script = ''
set -eu
export PATH="${pkgs.mariadb}/bin:$PATH"
sleep 5
# Generate .env only if missing
if [ ! -f .env ]; then
cp .env.example .env
${php}/bin/php artisan key:generate --force
${php}/bin/php artisan p:environment:database \
--host=localhost \
--port=3306 \
--database=pyrodactyl \
--username=pyro \
--password=supersecret \
--no-interaction
sed -i \
-e "s|^APP_URL=.*|APP_URL=${cfg.url}|" \
-e "s|^REDIS_HOST=.*|REDIS_HOST=localhost|" \
.env
fi
# Run migrations
${php}/bin/php artisan migrate --seed --force
if ! ${pkgs.php}/bin/php artisan tinker --execute="exit(App\Models\User::where('email', '${cfg.adminUser.email}')->exists() ? 0 : 1)"; then
${pkgs.php}/bin/php artisan p:user:make \
--email="${cfg.adminUser.email}" \
--name-first="${cfg.adminUser.firstName}" \
--name-last="${cfg.adminUser.lastName}" \
--password="${cfg.adminUser.password}" \
--admin
mysql -u pyro -psupersecret -h localhost -e "USE pyrodactyl; UPDATE users SET root_admin = 1;"
fi
'';
wantedBy = [ "multi-user.target" ];
};
systemd.services.pyrodactyl = {
description = "Pyrodactyl Panel";
after = [
"network.target"
"pyodactyl-db-init.service"
"pyrodactyl-copy.service"
];
requires = [
"pyrodactyl-db-init.service"
"pyrodactyl-copy.service"
];
serviceConfig = {
User = "pyrodactyl";
Group = "pyrodactyl";
WorkingDirectory = "/var/lib/pyrodactyl";
ExecStart = ''
${php}/bin/php artisan serve \
--host=${cfg.listenAddress} \
--port=${toString cfg.listenPort}
'';
Restart = "always";
};
wantedBy = [ "multi-user.target" ];
};
};
}
@imeesa
Copy link

imeesa commented Aug 25, 2025

sandcats

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment