353 lines
9.8 KiB
Nix
353 lines
9.8 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.rabbi-gerzi;
|
|
|
|
backendPackage = cfg.backend.package;
|
|
frontendPackage = cfg.frontend.package;
|
|
appDir = "${backendPackage}/share/php/rabbi-gerzi-backend";
|
|
phpPackage = backendPackage.passthru.php;
|
|
poolName = "rabbi-gerzi";
|
|
storagePath = "${cfg.stateDir}/storage";
|
|
cachePath = "${cfg.cacheDir}/bootstrap-cache";
|
|
setupService = "rabbi-gerzi-setup";
|
|
phpfpmService = "phpfpm-${poolName}";
|
|
|
|
appEnvironment = {
|
|
APP_ENV = "production";
|
|
APP_DEBUG = "false";
|
|
APP_URL = cfg.backend.publicUrl;
|
|
APP_CONFIG_CACHE = "${cachePath}/config.php";
|
|
APP_EVENTS_CACHE = "${cachePath}/events.php";
|
|
APP_PACKAGES_CACHE = "${cachePath}/packages.php";
|
|
APP_ROUTES_CACHE = "${cachePath}/routes.php";
|
|
APP_SERVICES_CACHE = "${cachePath}/services.php";
|
|
CACHE_STORE = "file";
|
|
CORS_ALLOWED_ORIGINS = lib.concatStringsSep "," cfg.backend.corsAllowedOrigins;
|
|
DB_CONNECTION = "pgsql";
|
|
DB_DATABASE = cfg.database.name;
|
|
DB_HOST = "/run/postgresql";
|
|
DB_PASSWORD = "";
|
|
DB_PORT = "5432";
|
|
DB_USERNAME = cfg.database.user;
|
|
FILESYSTEM_DISK = "public";
|
|
LARAVEL_STORAGE_PATH = storagePath;
|
|
LOG_CHANNEL = "single";
|
|
MAIL_MAILER = "log";
|
|
QUEUE_CONNECTION = "sync";
|
|
SESSION_DRIVER = "database";
|
|
SESSION_DOMAIN = cfg.backend.cookieDomain;
|
|
SESSION_SAME_SITE = cfg.backend.cookieSameSite;
|
|
SESSION_SECURE_COOKIE = lib.boolToString cfg.backend.cookieSecure;
|
|
};
|
|
|
|
fpmBaseEnvironment = lib.filterAttrs (
|
|
environmentName: environmentValue: environmentValue != ""
|
|
) appEnvironment;
|
|
|
|
fpmEnvironment = fpmBaseEnvironment // {
|
|
APP_KEY = "$APP_KEY";
|
|
};
|
|
|
|
artisan = "${phpPackage}/bin/php ${appDir}/artisan";
|
|
|
|
makeDirectoryRule = path: "d ${path} 0750 ${cfg.user} ${cfg.group} - -";
|
|
in
|
|
{
|
|
options.services.rabbi-gerzi = {
|
|
enable = lib.mkEnableOption "the Rabbi Gerzi application";
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "rabbi-gerzi";
|
|
description = "User used to run the Rabbi Gerzi backend.";
|
|
};
|
|
|
|
group = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "rabbi-gerzi";
|
|
description = "Group used to run the Rabbi Gerzi backend.";
|
|
};
|
|
|
|
stateDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/var/lib/rabbi-gerzi";
|
|
description = "Writable state directory for uploads and storage.";
|
|
};
|
|
|
|
cacheDir = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "/var/cache/rabbi-gerzi";
|
|
description = "Writable cache directory for Laravel cache files.";
|
|
};
|
|
|
|
database = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = cfg.user;
|
|
description = "PostgreSQL database name.";
|
|
};
|
|
|
|
user = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = cfg.user;
|
|
description = "PostgreSQL database user.";
|
|
};
|
|
};
|
|
|
|
frontend = {
|
|
hostName = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Frontend nginx virtual host name.";
|
|
};
|
|
|
|
publicUrl = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "https://${cfg.frontend.hostName}";
|
|
description = "Public frontend origin.";
|
|
};
|
|
|
|
apiBaseUrl = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = cfg.backend.publicUrl;
|
|
description = "API origin baked into the frontend build.";
|
|
};
|
|
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
default = pkgs.callPackage ./packages/frontend.nix {
|
|
inherit (cfg.frontend) apiBaseUrl;
|
|
};
|
|
description = "Frontend package to serve.";
|
|
};
|
|
|
|
nginx = lib.mkOption {
|
|
type = lib.types.attrs;
|
|
default = { };
|
|
description = "Extra nginx virtual host options for the frontend.";
|
|
};
|
|
};
|
|
|
|
backend = {
|
|
hostName = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Backend nginx virtual host name.";
|
|
};
|
|
|
|
publicUrl = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "https://${cfg.backend.hostName}";
|
|
description = "Public backend origin.";
|
|
};
|
|
|
|
environmentFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Runtime dotenv file containing APP_KEY and admin secrets.";
|
|
};
|
|
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
default = pkgs.callPackage ./packages/backend.nix { };
|
|
description = "Backend package to run.";
|
|
};
|
|
|
|
corsAllowedOrigins = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ cfg.frontend.publicUrl ];
|
|
description = "Allowed CORS origins for the backend API.";
|
|
};
|
|
|
|
cookieSecure = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Whether auth cookies require HTTPS.";
|
|
};
|
|
|
|
cookieDomain = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "Auth cookie domain. Empty means host-only cookies.";
|
|
};
|
|
|
|
cookieSameSite = lib.mkOption {
|
|
type = lib.types.enum [
|
|
"lax"
|
|
"none"
|
|
"strict"
|
|
];
|
|
default = "none";
|
|
description = "SameSite value for auth cookies.";
|
|
};
|
|
|
|
nginx = lib.mkOption {
|
|
type = lib.types.attrs;
|
|
default = { };
|
|
description = "Extra nginx virtual host options for the backend.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.backend.environmentFile != null;
|
|
message = "services.rabbi-gerzi.backend.environmentFile is required.";
|
|
}
|
|
{
|
|
assertion = cfg.backend.cookieSameSite != "none" || cfg.backend.cookieSecure;
|
|
message = "SameSite=None auth cookies require cookieSecure = true.";
|
|
}
|
|
{
|
|
assertion = cfg.database.name == cfg.database.user;
|
|
message = "Managed PostgreSQL database and user names must match.";
|
|
}
|
|
];
|
|
|
|
users.groups.${cfg.group} = { };
|
|
users.users.${cfg.user} = {
|
|
isSystemUser = true;
|
|
group = cfg.group;
|
|
home = cfg.stateDir;
|
|
};
|
|
|
|
services.postgresql = {
|
|
enable = true;
|
|
ensureDatabases = [ cfg.database.name ];
|
|
ensureUsers = [
|
|
{
|
|
name = cfg.database.user;
|
|
ensureDBOwnership = true;
|
|
}
|
|
];
|
|
};
|
|
|
|
systemd.tmpfiles.rules = map makeDirectoryRule [
|
|
cfg.stateDir
|
|
cfg.cacheDir
|
|
storagePath
|
|
"${storagePath}/app"
|
|
"${storagePath}/app/private"
|
|
"${storagePath}/app/public"
|
|
"${storagePath}/framework"
|
|
"${storagePath}/framework/cache"
|
|
"${storagePath}/framework/cache/data"
|
|
"${storagePath}/framework/sessions"
|
|
"${storagePath}/framework/views"
|
|
"${storagePath}/logs"
|
|
cachePath
|
|
];
|
|
|
|
services.phpfpm.pools.${poolName} = {
|
|
inherit (cfg) user group;
|
|
inherit phpPackage;
|
|
phpEnv = fpmEnvironment;
|
|
settings = {
|
|
"catch_workers_output" = true;
|
|
"clear_env" = "no";
|
|
"listen.group" = config.services.nginx.group;
|
|
"listen.mode" = "0660";
|
|
"listen.owner" = cfg.user;
|
|
"pm" = "dynamic";
|
|
"pm.max_children" = 16;
|
|
"pm.max_requests" = 500;
|
|
"pm.max_spare_servers" = 4;
|
|
"pm.min_spare_servers" = 1;
|
|
"pm.start_servers" = 2;
|
|
};
|
|
};
|
|
|
|
systemd.services.${setupService} = {
|
|
description = "Prepare Rabbi Gerzi backend";
|
|
wantedBy = [ "multi-user.target" ];
|
|
before = [ "${phpfpmService}.service" ];
|
|
after = [ "postgresql.service" ];
|
|
requires = [ "postgresql.service" ];
|
|
path = [ phpPackage ];
|
|
environment = appEnvironment;
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
User = cfg.user;
|
|
Group = cfg.group;
|
|
UMask = "0077";
|
|
WorkingDirectory = appDir;
|
|
EnvironmentFile = cfg.backend.environmentFile;
|
|
ReadWritePaths = [
|
|
cfg.stateDir
|
|
cfg.cacheDir
|
|
];
|
|
};
|
|
script = ''
|
|
${artisan} migrate --force
|
|
${artisan} rabbi-gerzi:ensure-initial-admin
|
|
'';
|
|
};
|
|
|
|
systemd.services.${phpfpmService} = {
|
|
after = [ "${setupService}.service" ];
|
|
requires = [ "${setupService}.service" ];
|
|
serviceConfig = {
|
|
EnvironmentFile = cfg.backend.environmentFile;
|
|
ReadWritePaths = [
|
|
cfg.stateDir
|
|
cfg.cacheDir
|
|
];
|
|
};
|
|
};
|
|
|
|
services.nginx = {
|
|
enable = lib.mkDefault true;
|
|
recommendedGzipSettings = lib.mkDefault true;
|
|
recommendedOptimisation = lib.mkDefault true;
|
|
recommendedProxySettings = lib.mkDefault true;
|
|
recommendedTlsSettings = lib.mkDefault true;
|
|
|
|
virtualHosts = {
|
|
${cfg.frontend.hostName} = lib.mkMerge [
|
|
cfg.frontend.nginx
|
|
{
|
|
root = "${frontendPackage}";
|
|
locations."/" = {
|
|
tryFiles = "$uri $uri/ /index.html";
|
|
};
|
|
}
|
|
];
|
|
|
|
${cfg.backend.hostName} = lib.mkMerge [
|
|
cfg.backend.nginx
|
|
{
|
|
root = "${appDir}/public";
|
|
locations = {
|
|
"/" = {
|
|
index = "index.php";
|
|
tryFiles = "$uri $uri/ /index.php?$query_string";
|
|
};
|
|
|
|
"/storage/" = {
|
|
alias = "${storagePath}/app/public/";
|
|
extraConfig = ''
|
|
expires 30d;
|
|
access_log off;
|
|
'';
|
|
};
|
|
|
|
"~ \\.php$" = {
|
|
extraConfig = ''
|
|
include ${pkgs.nginx}/conf/fastcgi_params;
|
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
fastcgi_pass unix:${config.services.phpfpm.pools.${poolName}.socket};
|
|
'';
|
|
};
|
|
};
|
|
}
|
|
];
|
|
};
|
|
};
|
|
};
|
|
}
|