forked from jan-leila/nix-config
		
	
		
			
				
	
	
		
			353 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| {
 | |
|   config,
 | |
|   lib,
 | |
|   pkgs,
 | |
|   ...
 | |
| }: {
 | |
|   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 [
 | |
|     {
 | |
|       # Create panoramax user and group
 | |
|       users.users.${config.services.panoramax.user} = {
 | |
|         isSystemUser = true;
 | |
|         group = config.services.panoramax.group;
 | |
|         home = "/var/lib/panoramax";
 | |
|         createHome = true;
 | |
|       };
 | |
| 
 | |
|       users.groups.${config.services.panoramax.group} = {};
 | |
| 
 | |
|       # Ensure storage directory exists with correct permissions
 | |
|       systemd.tmpfiles.rules = [
 | |
|         "d '${config.services.panoramax.settings.storage.fsUrl}' 0755 ${config.services.panoramax.user} ${config.services.panoramax.group} - -"
 | |
|       ];
 | |
| 
 | |
|       systemd.services.panoramax-api = {
 | |
|         description = "Panoramax API server (self hosted map street view)";
 | |
|         after = ["network.target" "postgresql.service"];
 | |
|         wantedBy = ["multi-user.target"];
 | |
| 
 | |
|         environment =
 | |
|           {
 | |
|             # Core Flask configuration
 | |
|             FLASK_APP = "geovisio";
 | |
| 
 | |
|             # Database configuration
 | |
|             DB_HOST = config.services.panoramax.database.host;
 | |
|             DB_PORT = toString config.services.panoramax.database.port;
 | |
|             DB_USERNAME = config.services.panoramax.database.user;
 | |
|             DB_NAME = config.services.panoramax.database.name;
 | |
| 
 | |
|             # Storage configuration
 | |
|             FS_URL = config.services.panoramax.settings.storage.fsUrl;
 | |
| 
 | |
|             # Infrastructure configuration
 | |
|             INFRA_NB_PROXIES = toString config.services.panoramax.settings.infrastructure.nbProxies;
 | |
| 
 | |
|             # Application configuration
 | |
|             PORT = toString config.services.panoramax.port;
 | |
| 
 | |
|             # Python path to include the panoramax package
 | |
|             PYTHONPATH = "${config.services.panoramax.package}/${pkgs.python3.sitePackages}";
 | |
|           }
 | |
|           // (lib.optionalAttrs (config.services.panoramax.settings.flask.secretKey != null) {
 | |
|             FLASK_SECRET_KEY = config.services.panoramax.settings.flask.secretKey;
 | |
|           })
 | |
|           // (lib.optionalAttrs (config.services.panoramax.settings.flask.sessionCookieDomain != null) {
 | |
|             FLASK_SESSION_COOKIE_DOMAIN = config.services.panoramax.settings.flask.sessionCookieDomain;
 | |
|           })
 | |
|           // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseSpdxId != null) {
 | |
|             API_PICTURES_LICENSE_SPDX_ID = config.services.panoramax.settings.api.pictures.licenseSpdxId;
 | |
|           })
 | |
|           // (lib.optionalAttrs (config.services.panoramax.settings.api.pictures.licenseUrl != null) {
 | |
|             API_PICTURES_LICENSE_URL = config.services.panoramax.settings.api.pictures.licenseUrl;
 | |
|           })
 | |
|           // (lib.optionalAttrs config.services.sgblur.enable {
 | |
|             SGBLUR_API_URL = config.services.sgblur.url;
 | |
|           })
 | |
|           // config.services.panoramax.settings.extraEnvironment;
 | |
| 
 | |
|         path = with pkgs; [
 | |
|           (python3.withPackages (ps: with ps; [config.services.panoramax.package waitress]))
 | |
|         ];
 | |
| 
 | |
|         serviceConfig = {
 | |
|           ExecStart = "${pkgs.python3.withPackages (ps: with ps; [config.services.panoramax.package waitress])}/bin/waitress-serve --port ${toString config.services.panoramax.port} --call geovisio:create_app";
 | |
|           User = config.services.panoramax.user;
 | |
|           Group = config.services.panoramax.group;
 | |
|           WorkingDirectory = "/var/lib/panoramax";
 | |
|           Restart = "always";
 | |
|           RestartSec = 5;
 | |
| 
 | |
|           # Security hardening
 | |
|           PrivateTmp = true;
 | |
|           ProtectSystem = "strict";
 | |
|           ProtectHome = true;
 | |
|           ReadWritePaths = [
 | |
|             "/var/lib/panoramax"
 | |
|             config.services.panoramax.settings.storage.fsUrl
 | |
|           ];
 | |
|           NoNewPrivileges = true;
 | |
|           PrivateDevices = true;
 | |
|           ProtectKernelTunables = true;
 | |
|           ProtectKernelModules = true;
 | |
|           ProtectControlGroups = true;
 | |
|           RestrictSUIDSGID = true;
 | |
|           RestrictRealtime = true;
 | |
|           RestrictNamespaces = true;
 | |
|           LockPersonality = true;
 | |
|           MemoryDenyWriteExecute = true;
 | |
|           SystemCallArchitectures = "native";
 | |
|         };
 | |
|       };
 | |
| 
 | |
|       # Open firewall if requested
 | |
|       networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [
 | |
|         config.services.panoramax.port
 | |
|       ];
 | |
|     }
 | |
|     (lib.mkIf config.services.sgblur.enable {
 | |
|       # SGBlur service configuration
 | |
|       systemd.services.sgblur = {
 | |
|         description = "SGBlur face and license plate blurring service";
 | |
|         after = ["network.target"];
 | |
|         wantedBy = ["multi-user.target"];
 | |
| 
 | |
|         path = with pkgs; [
 | |
|           config.services.sgblur.package
 | |
|           python3
 | |
|           python3Packages.waitress
 | |
|         ];
 | |
| 
 | |
|         serviceConfig = {
 | |
|           ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --host ${config.services.sgblur.host} --port ${toString config.services.sgblur.port} src.detect.detect_api:app";
 | |
|           WorkingDirectory = "${config.services.sgblur.package}";
 | |
|           Restart = "always";
 | |
|           RestartSec = 5;
 | |
| 
 | |
|           # Basic security hardening
 | |
|           PrivateTmp = true;
 | |
|           ProtectSystem = "strict";
 | |
|           ProtectHome = true;
 | |
|           NoNewPrivileges = true;
 | |
|           PrivateDevices = true;
 | |
|           ProtectKernelTunables = true;
 | |
|           ProtectKernelModules = true;
 | |
|           ProtectControlGroups = true;
 | |
|           RestrictSUIDSGID = true;
 | |
|           RestrictRealtime = true;
 | |
|           RestrictNamespaces = true;
 | |
|           LockPersonality = true;
 | |
|           MemoryDenyWriteExecute = true;
 | |
|           SystemCallArchitectures = "native";
 | |
|         };
 | |
|       };
 | |
| 
 | |
|       networking.firewall.allowedTCPPorts = lib.mkIf config.services.panoramax.openFirewall [
 | |
|         config.services.sgblur.port
 | |
|       ];
 | |
|     })
 | |
|     (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}"
 | |
|         ''
 | |
|       ];
 | |
|     })
 | |
|   ]);
 | |
| }
 |