{ config, lib, pkgs, ... }: let dbUrlConfigured = config.services.panoramax.database.url != null; individualDbConfigured = lib.all (x: x != null) [ config.services.panoramax.database.host config.services.panoramax.database.port config.services.panoramax.database.username config.services.panoramax.database.password config.services.panoramax.database.name ]; envContent = '' # Panoramax Configuration FLASK_APP=geovisio ${ if dbUrlConfigured then "DB_URL=${config.services.panoramax.database.url}" else '' DB_HOST=${config.services.panoramax.database.host} DB_PORT=${toString config.services.panoramax.database.port} DB_USERNAME=${config.services.panoramax.database.username} DB_PASSWORD=${config.services.panoramax.database.password} DB_NAME=${config.services.panoramax.database.name} '' } ${lib.optionalString (config.services.panoramax.storage.fsUrl != null) "FS_URL=${config.services.panoramax.storage.fsUrl}"} ${lib.optionalString (config.services.panoramax.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString config.services.panoramax.infrastructure.nbProxies}"} ${lib.optionalString (config.services.panoramax.flask.secretKey != null) "FLASK_SECRET_KEY=${config.services.panoramax.flask.secretKey}"} ${lib.optionalString (config.services.panoramax.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${config.services.panoramax.flask.sessionCookieDomain}"} ${lib.optionalString (config.services.panoramax.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${config.services.panoramax.api.pictures.licenseSpdxId}"} ${lib.optionalString (config.services.panoramax.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${config.services.panoramax.api.pictures.licenseUrl}"} ${lib.optionalString (config.services.panoramax.port != null) "PORT=${toString config.services.panoramax.port}"} ${lib.optionalString (config.services.panoramax.sgblur.enable) "SGBLUR_API_URL=${config.services.panoramax.sgblur.url}"} ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name}=${value}") config.services.panoramax.extraEnvironment)} ''; envFile = pkgs.writeText "panoramax.env" envContent; in { options.services = { panoramax = { enable = lib.mkEnableOption "panoramax"; package = lib.mkOption { type = lib.types.package; default = pkgs.panoramax; description = "The panoramax package to use"; }; user = lib.mkOption { type = lib.types.str; default = "panoramax"; description = "The user panoramax should run as."; }; group = lib.mkOption { type = lib.types.str; default = "panoramax"; description = "The group panoramax should run as."; }; host = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "Host to bind the panoramax service to"; }; port = lib.mkOption { type = lib.types.nullOr lib.types.port; default = 5000; description = "Port for the panoramax service"; }; openFirewall = lib.mkOption { type = lib.types.bool; default = false; description = "Whether to open the panoramax port in the firewall"; }; settings = { urlScheme = lib.mkOption { type = lib.types.enum ["http" "https"]; default = "https"; description = "URL scheme for the application"; }; storage = { fsUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = "/var/lib/panoramax/storage"; description = "File system URL for storage"; }; }; infrastructure = { nbProxies = lib.mkOption { type = lib.types.nullOr lib.types.int; default = 1; description = "Number of proxies in front of the application"; }; }; flask = { secretKey = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Flask secret key for session security"; }; sessionCookieDomain = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Flask session cookie domain"; }; }; api = { pictures = { licenseSpdxId = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "SPDX license identifier for API pictures"; }; licenseUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "License URL for API pictures"; }; }; }; extraEnvironment = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = {}; description = "Additional environment variables"; example = { CUSTOM_SETTING = "value"; DEBUG = "true"; }; }; }; database = { createDB = lib.mkOption { type = lib.types.bool; default = true; description = "Whether to automatically create the database and user"; }; name = lib.mkOption { type = lib.types.str; default = "panoramax"; description = "The name of the panoramax database"; }; host = lib.mkOption { type = lib.types.nullOr lib.types.str; default = "/run/postgresql"; description = "Hostname or address of the postgresql server. If an absolute path is given here, it will be interpreted as a unix socket path."; }; port = lib.mkOption { type = lib.types.nullOr lib.types.port; default = 5432; description = "Port of the postgresql server."; }; user = lib.mkOption { type = lib.types.nullOr lib.types.str; default = "panoramax"; description = "The database user for panoramax."; }; # TODO: password file for external database }; sgblur = { # TODO: configs to bind to sgblur }; }; sgblur = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "Whether to enable sgblur integration for face and license plate blurring"; }; package = lib.mkOption { type = lib.types.package; default = pkgs.sgblur; description = "The sgblur package to use"; }; port = lib.mkOption { type = lib.types.port; default = 8080; description = "Port for the sgblur service"; }; host = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "Host to bind the sgblur service to"; }; url = lib.mkOption { type = lib.types.str; default = "http://127.0.0.1:8080"; description = "URL where sgblur service is accessible"; }; }; }; config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ { # TODO: start panoramax service } (lib.mkIf config.services.sgblur.enable { # TODO: start sg blur config }) (lib.mkIf config.services.panoramax.database.createDB { services.postgresql = { enable = true; ensureDatabases = lib.mkIf config.services.panoramax.database.createDB [config.services.panoramax.database.name]; ensureUsers = lib.mkIf config.services.panoramax.database.createDB [ { name = config.services.panoramax.database.user; ensureDBOwnership = true; ensureClauses.login = true; } ]; extensions = ps: with ps; [postgis]; }; systemd.services.postgresql.serviceConfig.ExecStartPost = let sqlFile = pkgs.writeText "panoramax-postgis-setup.sql" '' CREATE EXTENSION IF NOT EXISTS postgis; -- TODO: how can we ensure that this runs after the databases have been created -- ALTER DATABASE ${config.services.panoramax.database.name} SET TIMEZONE TO 'UTC'; GRANT SET ON PARAMETER session_replication_role TO ${config.services.panoramax.database.user}; ''; in [ '' ${lib.getExe' config.services.postgresql.package "psql"} -d "${config.services.panoramax.database.user}" -f "${sqlFile}" '' ]; }) ]); }