251 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{
 | 
						|
  config,
 | 
						|
  lib,
 | 
						|
  pkgs,
 | 
						|
  osConfig,
 | 
						|
  ...
 | 
						|
}:
 | 
						|
with lib; let
 | 
						|
  cfg = config.services.panoramax;
 | 
						|
 | 
						|
  # Database configuration assertions
 | 
						|
  dbUrlConfigured = cfg.database.url != null;
 | 
						|
  individualDbConfigured = all (x: x != null) [
 | 
						|
    cfg.database.host
 | 
						|
    cfg.database.port
 | 
						|
    cfg.database.username
 | 
						|
    cfg.database.password
 | 
						|
    cfg.database.name
 | 
						|
  ];
 | 
						|
 | 
						|
  envContent = ''
 | 
						|
    # Panoramax Configuration
 | 
						|
    FLASK_APP=geovisio
 | 
						|
    ${
 | 
						|
      if dbUrlConfigured
 | 
						|
      then "DB_URL=${cfg.database.url}"
 | 
						|
      else ''
 | 
						|
        DB_HOST=${cfg.database.host}
 | 
						|
        DB_PORT=${toString cfg.database.port}
 | 
						|
        DB_USERNAME=${cfg.database.username}
 | 
						|
        DB_PASSWORD=${cfg.database.password}
 | 
						|
        DB_NAME=${cfg.database.name}
 | 
						|
      ''
 | 
						|
    }
 | 
						|
    ${optionalString (cfg.storage.fsUrl != null) "FS_URL=${cfg.storage.fsUrl}"}
 | 
						|
    ${optionalString (cfg.infrastructure.nbProxies != null) "INFRA_NB_PROXIES=${toString cfg.infrastructure.nbProxies}"}
 | 
						|
    ${optionalString (cfg.flask.secretKey != null) "FLASK_SECRET_KEY=${cfg.flask.secretKey}"}
 | 
						|
    ${optionalString (cfg.flask.sessionCookieDomain != null) "FLASK_SESSION_COOKIE_DOMAIN=${cfg.flask.sessionCookieDomain}"}
 | 
						|
    ${optionalString (cfg.api.pictures.licenseSpdxId != null) "API_PICTURES_LICENSE_SPDX_ID=${cfg.api.pictures.licenseSpdxId}"}
 | 
						|
    ${optionalString (cfg.api.pictures.licenseUrl != null) "API_PICTURES_LICENSE_URL=${cfg.api.pictures.licenseUrl}"}
 | 
						|
    ${optionalString (cfg.port != null) "PORT=${toString cfg.port}"}
 | 
						|
    ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name}=${value}") cfg.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";
 | 
						|
    };
 | 
						|
 | 
						|
    # TODO: sgblur config
 | 
						|
    port = mkOption {
 | 
						|
      type = types.nullOr types.port;
 | 
						|
      default = 5000;
 | 
						|
      description = "Port for the Panoramax service";
 | 
						|
    };
 | 
						|
 | 
						|
    host = mkOption {
 | 
						|
      type = types.str;
 | 
						|
      default = "127.0.0.1";
 | 
						|
      description = "Host to bind the Panoramax service to";
 | 
						|
    };
 | 
						|
 | 
						|
    urlScheme = mkOption {
 | 
						|
      type = types.enum ["http" "https"];
 | 
						|
      default = "https";
 | 
						|
      description = "URL scheme for the application";
 | 
						|
    };
 | 
						|
 | 
						|
    database = {
 | 
						|
      url = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = null;
 | 
						|
        description = ''
 | 
						|
          Complete database URL connection string (e.g., "postgresql://user:password@host:port/dbname").
 | 
						|
          If provided, individual database options (host, port, username, password, name) are ignored.
 | 
						|
        '';
 | 
						|
      };
 | 
						|
 | 
						|
      port = mkOption {
 | 
						|
        type = types.nullOr types.port;
 | 
						|
        default = 5432;
 | 
						|
        description = "Database port (ignored if database.url is set)";
 | 
						|
      };
 | 
						|
 | 
						|
      host = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = "localhost";
 | 
						|
        description = "Database host (ignored if database.url is set)";
 | 
						|
      };
 | 
						|
 | 
						|
      username = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = "panoramax";
 | 
						|
        description = "Database username (ignored if database.url is set)";
 | 
						|
      };
 | 
						|
 | 
						|
      password = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = null;
 | 
						|
        description = "Database password (ignored if database.url is set)";
 | 
						|
      };
 | 
						|
 | 
						|
      name = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = "panoramax";
 | 
						|
        description = "Database name (ignored if database.url is set)";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    storage = {
 | 
						|
      fsUrl = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = "/var/lib/panoramax/storage";
 | 
						|
        description = "File system URL for storage";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    infrastructure = {
 | 
						|
      nbProxies = mkOption {
 | 
						|
        type = types.nullOr types.int;
 | 
						|
        default = 1;
 | 
						|
        description = "Number of proxies in front of the application";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    flask = {
 | 
						|
      secretKey = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = null;
 | 
						|
        description = "Flask secret key for session security";
 | 
						|
      };
 | 
						|
 | 
						|
      sessionCookieDomain = mkOption {
 | 
						|
        type = types.nullOr types.str;
 | 
						|
        default = null;
 | 
						|
        description = "Flask session cookie domain";
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    api = {
 | 
						|
      pictures = {
 | 
						|
        licenseSpdxId = mkOption {
 | 
						|
          type = types.nullOr types.str;
 | 
						|
          default = null;
 | 
						|
          description = "SPDX license identifier for API pictures";
 | 
						|
        };
 | 
						|
 | 
						|
        licenseUrl = mkOption {
 | 
						|
          type = types.nullOr types.str;
 | 
						|
          default = null;
 | 
						|
          description = "License URL for API pictures";
 | 
						|
        };
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    extraEnvironment = mkOption {
 | 
						|
      type = types.attrsOf types.str;
 | 
						|
      default = {};
 | 
						|
      description = "Additional environment variables";
 | 
						|
      example = {
 | 
						|
        CUSTOM_SETTING = "value";
 | 
						|
        DEBUG = "true";
 | 
						|
      };
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  config = lib.mkIf config.services.panoramax.enable (
 | 
						|
    lib.mkMerge [
 | 
						|
      {
 | 
						|
        environment.systemPackages = with pkgs; [
 | 
						|
          config.services.panoramax.package
 | 
						|
          python3Packages.waitress
 | 
						|
        ];
 | 
						|
 | 
						|
        systemd.services.panoramax = {
 | 
						|
          description = "Panoramax Service";
 | 
						|
          after = ["network.target"];
 | 
						|
          wantedBy = ["multi-user.target"];
 | 
						|
          serviceConfig = {
 | 
						|
            ExecStart = "${pkgs.python3Packages.waitress}/bin/waitress-serve --env-file=${envFile} --host=${config.services.panoramax.host} --port=${toString config.services.panoramax.port} --url-scheme=${config.services.panoramax.urlScheme} --call geovisio:create_app";
 | 
						|
            Restart = "always";
 | 
						|
            User = "panoramax";
 | 
						|
            Group = "panoramax";
 | 
						|
            WorkingDirectory = "/var/lib/panoramax";
 | 
						|
            Environment = "PYTHONPATH=${config.services.panoramax.package}/lib/python3.11/site-packages";
 | 
						|
          };
 | 
						|
        };
 | 
						|
 | 
						|
        users.users.panoramax = {
 | 
						|
          isSystemUser = true;
 | 
						|
          group = "panoramax";
 | 
						|
          home = "/var/lib/panoramax";
 | 
						|
          createHome = true;
 | 
						|
        };
 | 
						|
 | 
						|
        users.groups.panoramax = {};
 | 
						|
 | 
						|
        systemd.tmpfiles.rules = [
 | 
						|
          "d /var/lib/panoramax 0755 panoramax panoramax -"
 | 
						|
          "d ${config.services.panoramax.storage.fsUrl} 0755 panoramax panoramax -"
 | 
						|
        ];
 | 
						|
 | 
						|
        assertions = [
 | 
						|
          {
 | 
						|
            assertion = dbUrlConfigured || individualDbConfigured;
 | 
						|
            message = ''
 | 
						|
              Panoramax database configuration requires either:
 | 
						|
              - A complete database URL (services.panoramax.database.url), OR
 | 
						|
              - All individual database options (host, port, username, password, name)
 | 
						|
 | 
						|
              Currently configured:
 | 
						|
              - database.url: ${
 | 
						|
                if dbUrlConfigured
 | 
						|
                then "✓ configured"
 | 
						|
                else "✗ not configured"
 | 
						|
              }
 | 
						|
              - individual options: ${
 | 
						|
                if individualDbConfigured
 | 
						|
                then "✓ all configured"
 | 
						|
                else "✗ some missing"
 | 
						|
              }
 | 
						|
            '';
 | 
						|
          }
 | 
						|
        ];
 | 
						|
 | 
						|
        # TODO: auto config db
 | 
						|
      }
 | 
						|
      (
 | 
						|
        lib.mkIf config.host.reverse_proxy.enable {
 | 
						|
          # TODO: configure reverse proxy here
 | 
						|
        }
 | 
						|
      )
 | 
						|
      (
 | 
						|
        lib.mkIf config.services.fail2ban {
 | 
						|
          # TODO: configure options for fail2ban
 | 
						|
        }
 | 
						|
      )
 | 
						|
      (
 | 
						|
        lib.mkIf osConfig.host.impermanence.enable {
 | 
						|
          # TODO: configure impermanence for panoramax data
 | 
						|
        }
 | 
						|
      )
 | 
						|
    ]
 | 
						|
  );
 | 
						|
}
 |