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}"
|
|
''
|
|
];
|
|
})
|
|
]);
|
|
}
|