{config, pkgs, ...}: let root_host = "grimmauld.de"; root_email = "contact@${root_host}"; ptero_host = "ptero.${root_host}"; DATA_DIR = "/var/lib/pterodactylpanel"; panel_user = "pterodactyl"; local_bridge = "ptero-local-br"; ptero_ver = "1.11.5"; ptero_port = "8042"; in { users.users.${panel_user} = { isSystemUser = true; extraGroups = ["docker"]; group = panel_user; }; users.groups.${panel_user} = {}; systemd.services.init-ptero-data-dir = { description = "Create the pterodactyl panel data dir"; wantedBy = [ "multi-user.target" ]; serviceConfig.Type = "oneshot"; script ='' mkdir -p ${DATA_DIR}/database mkdir -p ${DATA_DIR}/panel chown ${panel_user}:${panel_user} -R ${DATA_DIR} chmod +777 -R ${DATA_DIR} ''; }; virtualisation.oci-containers.backend = "docker"; # maybe podman in the future systemd.services.init-ptero-local-network = { description = "Create the network bridge ${local_bridge} for ptero."; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig.Type = "oneshot"; script = let dockercli = "${config.virtualisation.docker.package}/bin/docker"; in '' # Put a true at the end to prevent getting non-zero return code, which will # crash the whole service. check=$(${dockercli} network ls | grep "${local_bridge}" || true) if [ -z "$check" ]; then ${dockercli} network create --internal ${local_bridge} else echo "${local_bridge} already exists in docker" fi ''; }; virtualisation.oci-containers.containers."ptero-mysql" = { image = "library/mysql:8.0"; workdir = "${DATA_DIR}/database"; extraOptions = [ "--network=${local_bridge}" ]; environment = { "MYSQL_ROOT_PASSWORD" = "JMK1VmZDwoVAUhvClQ7DncOEw5B1XcKXwqERw45Cw4/CoMKKwqHCocKXwqZrwr9b"; "MYSQL_USER" = "pterodactyl"; "MYSQL_PASSWORD" = "JMK1VmZDwoVAUhvClQ7DncOEw5B1XcKXwqERw45Cw4/CoMKKwqHCocKXwqZrwr9b"; "MYSQL_DATABASE" = "panel"; }; volumes = ["${DATA_DIR}/database:/var/lib/mysql"]; cmd=["--default-authentication-plugin=mysql_native_password"]; }; virtualisation.oci-containers.containers."ptero-cache" = { image = "redis:alpine"; workdir = "${DATA_DIR}/cache"; extraOptions = [ "--network=${local_bridge}" ]; }; virtualisation.oci-containers.containers."ptero-panel" = { image = "ghcr.io/pterodactyl/panel:v${ptero_ver}"; # workdir = "${DATA_DIR}/panel"; volumes = [ "${DATA_DIR}/panel/var/:/app/var/" "${DATA_DIR}/panel/logs/:/app/storage/logs" "${DATA_DIR}/panel/nginx/:/etc/nginx/conf.d/" ]; extraOptions = [ "--network=${local_bridge}"]; environment = { "APP_URL" = "https://${ptero_host}"; "APP_TIMEZONE" = "Europe/Berlin"; "APP_SERVICE_AUTHOR" = root_email; "MAIL_FROM" = "noreply@${root_host}"; "MAIL_DRIVER" = "smtp"; "MAIL_HOST" = "mail"; "MAIL_PORT" = "25"; "MAIL_USERNAME" = ""; "MAIL_PASSWORD" = ""; "MAIL_ENCRYPTION" = "true"; "DB_PASSWORD" = "JMK1VmZDwoVAUhvClQ7DncOEw5B1XcKXwqERw45Cw4/CoMKKwqHCocKXwqZrwr9b"; "APP_ENV"= "production"; "APP_ENVIRONMENT_ONLY"= "false"; "CACHE_DRIVER" = "redis"; "SESSION_DRIVER" = "redis"; "QUEUE_DRIVER" = "redis"; "REDIS_HOST" = "ptero-cache"; "DB_HOST" = "ptero-mysql"; "TRUSTED_PROXIES" = "*"; }; labels = { "traefik.http.routers.pterodactyl_panel.entrypoints"="web"; # "traefik.http.routers.pterodactyl_panel.rule"="Host(`${ptero_host}`)"; # "traefik.http.routers.pterodactyl_panel.middlewares"="panel_https"; # "traefik.http.middlewares.panel_https.redirectscheme.scheme"="https"; # "traefik.http.routers.pterodactyl_panel-https.entrypoints"="websecure"; # "traefik.http.routers.pterodactyl_panel-https.rule"="Host(`${ptero_host}`)"; # "traefik.http.routers.pterodactyl_panel-https.tls"="true"; # "traefik.http.routers.pterodactyl_panel-https.tls.certresolver"="letsencrypt"; # "traefik.http.services.pterodactyl_panel-https.loadbalancer.server.port"="80"; }; ports = [ "${ptero_port}:80" ]; }; security.acme.certs."${root_host}".extraDomainNames = [ ptero_host ]; services.nginx = { enable = true; virtualHosts."${ptero_host}" = { serverName = ptero_host; forceSSL = true; useACMEHost = root_host; locations."/" = { proxyPass = "http://127.0.0.1:${ptero_port}"; }; }; }; }