diff --git a/hosts/AZ-PRM-1/secrets.nix b/hosts/AZ-PRM-1/secrets.nix index e53441a..4966208 100644 --- a/hosts/AZ-PRM-1/secrets.nix +++ b/hosts/AZ-PRM-1/secrets.nix @@ -5,6 +5,12 @@ in { age = { secrets = { + atrocore-env = { + file = ../../secrets/atrocore-env.age; + }; + atrocore-registry-token = { + file = ../../secrets/atrocore-registry-token.age; + }; azion-env = { file = ../../secrets/azion-env.age; }; diff --git a/hosts/AZ-PRM-1/services/containers/atrocore.nix b/hosts/AZ-PRM-1/services/containers/atrocore.nix new file mode 100644 index 0000000..2787bfc --- /dev/null +++ b/hosts/AZ-PRM-1/services/containers/atrocore.nix @@ -0,0 +1,87 @@ +{config, ...}: let + serviceName = "atrocore"; + servicePort = config.m3ta.ports.get serviceName; + instanceDir = "/var/www/pim.l.az-gruppe.com"; +in { + virtualisation.oci-containers.containers."${serviceName}" = { + # Secret-free image from the AZ-NIX-ava.1 pipeline (Gitea registry). + image = "git.az-gruppe.com/az-intec-gmbh/atrocore-web:latest"; + # DB host is the alias for the host PostgreSQL on the podman web network; + # ATRO_DB_NAME/ATRO_DB_USER/ATRO_DB_PASSWORD come from the agenix secret + # (podman env-file) and are written to data/config.php by the entrypoint. + environment = { + ATRO_DB_HOST = "db"; + ATRO_INSTANCE_DIR = "pim.l.az-gruppe.com"; + }; + environmentFiles = [config.age.secrets.atrocore-env.path]; + # Registry pull credentials for the Gitea package registry (token from + # agenix secret, so nothing lands in the Nix store). + login = { + registry = "git.az-gruppe.com"; + username = "sascha.koenig"; + passwordFile = config.age.secrets.atrocore-registry-token.path; + }; + ports = ["127.0.0.1:${toString servicePort}:80"]; + volumes = [ + "atrocore_data:${instanceDir}/data" + ]; + extraOptions = ["--network=web" "--ip=10.89.0.16" "--add-host=db:10.89.0.1"]; + }; + + # Idempotent provisioning of the atrocore role/database on the host + # PostgreSQL 17. initialScript cannot be used here (cluster is already + # initialized and the password must never land in the Nix store), so the + # secret is read at runtime and applied via psql peer auth. + systemd.services.atrocore-db-init = { + description = "Provision atrocore role/database from agenix secret"; + after = ["postgresql.service"]; + before = ["podman-${serviceName}.service"]; + wants = ["postgresql.service"]; + wantedBy = ["multi-user.target"]; + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + Group = "postgres"; + RemainAfterExit = true; + }; + path = [config.services.postgresql.package]; + # Env-file format: plain KEY=value lines (no quotes), same file the + # container consumes. + script = '' + set -euo pipefail + ENV_FILE="${config.age.secrets.atrocore-env.path}" + get_val() { + grep -m1 "^$1=" "$ENV_FILE" | head -n1 | cut -d= -f2- | tr -d '\r' + } + db_name="$(get_val ATRO_DB_NAME)" + db_user="$(get_val ATRO_DB_USER)" + db_pass="$(get_val ATRO_DB_PASSWORD)" + if [ -z "$db_name" ] || [ -z "$db_user" ] || [ -z "$db_pass" ]; then + echo "atrocore-db-init: ATRO_DB_NAME/ATRO_DB_USER/ATRO_DB_PASSWORD missing in $ENV_FILE" >&2 + exit 1 + fi + psql -v ON_ERROR_STOP=1 -d postgres \ + -v db_name="$db_name" -v db_user="$db_user" -v db_pass="$db_pass" <<'SQL' + SELECT format('CREATE ROLE %I LOGIN', :'db_user') + WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = :'db_user') \gexec + ALTER ROLE :"db_user" WITH LOGIN PASSWORD :'db_pass'; + SELECT format('CREATE DATABASE %I OWNER %I', :'db_name', :'db_user') + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = :'db_name') \gexec + ALTER DATABASE :"db_name" OWNER TO :"db_user"; + SQL + echo "atrocore-db-init: role/database '$db_name' ready" + ''; + }; + + # Traefik configuration specific to atrocore (AtroPIM) + services.traefik.dynamicConfigOptions.http = { + services.${serviceName}.loadBalancer.servers = [{url = "http://localhost:${toString servicePort}/";}]; + + routers.${serviceName} = { + rule = "Host(`pim.l.az-gruppe.com`)"; + tls = {certResolver = "ionos";}; + service = serviceName; + entrypoints = "websecure"; + }; + }; +} diff --git a/hosts/AZ-PRM-1/services/containers/default.nix b/hosts/AZ-PRM-1/services/containers/default.nix index d10b431..687c076 100644 --- a/hosts/AZ-PRM-1/services/containers/default.nix +++ b/hosts/AZ-PRM-1/services/containers/default.nix @@ -1,6 +1,7 @@ {lib, ...}: { imports = [ #./baserow.nix + ./atrocore.nix ./excalidraw.nix ./kestra.nix ./online3dviewer.nix diff --git a/hosts/AZ-PRM-1/services/postgres.nix b/hosts/AZ-PRM-1/services/postgres.nix index d99175e..9cd1c97 100644 --- a/hosts/AZ-PRM-1/services/postgres.nix +++ b/hosts/AZ-PRM-1/services/postgres.nix @@ -52,6 +52,7 @@ # Podman network connections host baserow baserow 10.89.0.0/24 scram-sha-256 host kestra kestra 10.89.0.0/24 scram-sha-256 + host atrocore atrocore 10.89.0.0/24 scram-sha-256 # Grafana (local socket / localhost only) host grafana grafana 127.0.0.1/32 scram-sha-256 @@ -65,7 +66,7 @@ services.postgresqlBackup = { enable = true; startAt = "03:10:00"; - databases = ["baserow" "kestra" "n8n" "netbox"]; + databases = ["atrocore" "baserow" "kestra" "n8n" "netbox"]; }; networking.firewall = { extraCommands = '' diff --git a/hosts/common/ports.nix b/hosts/common/ports.nix index be0b688..e28106e 100644 --- a/hosts/common/ports.nix +++ b/hosts/common/ports.nix @@ -21,6 +21,7 @@ azion-scheduler-proxy = 3049; phishboard = 3055; homarr = 3057; + atrocore = 3058; metabase = 3013; baserow = 3050; diff --git a/secrets.nix b/secrets.nix index e324003..e0b39dd 100644 --- a/secrets.nix +++ b/secrets.nix @@ -11,6 +11,8 @@ let users = [sascha.koenig]; systems = [AZ-CLD-1 AZ-LT-NIX AZ-PRM-1]; in { + "secrets/atrocore-env.age".publicKeys = [AZ-PRM-1] ++ users; + "secrets/atrocore-registry-token.age".publicKeys = [AZ-PRM-1] ++ users; "secrets/azion-env.age".publicKeys = systems ++ users; "secrets/server.crt.age".publicKeys = systems ++ users; "secrets/server.key.age".publicKeys = systems ++ users; diff --git a/secrets/atrocore-env.age b/secrets/atrocore-env.age new file mode 100644 index 0000000..61d8b3e --- /dev/null +++ b/secrets/atrocore-env.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 lGqVZg u2jO08V1k+GAeuVxEfjx1X6kztQQ5UGWYrikR8TG1UM +3eZXQx5S33KYsqBSem1t11EZ6RuxlENm4f4uRpvG8ZA +-> ssh-ed25519 CSMyhg gOz1bLWGIN4Q63YXAKjjZo11tcN77U1gG8Isj0EE0DA +DbV8RnBf7VQ24LKXDiKUxqXh8jrpz+RPajKkzjlzjjU +--- HgpaS/Vnh/Cv4BzJPUqhqWB+WPPMkJzXN1EpjYa3W4Q +IÀ5ÛÞ­Ø»v**}TÉ�Àà +=X Lçß²7í^§Ç„ŒËÁ^¾7¸Á/®0ÕL‘,<ÕÏKߨ,“mâu‹œê#Ê�½KcJ©ª`ÆŠÅ&H�éï Òvl¹*"­�e­I+4 ƒ�,Ðá´•r>ôðrw &i€öd›� \ No newline at end of file diff --git a/secrets/atrocore-registry-token.age b/secrets/atrocore-registry-token.age new file mode 100644 index 0000000..8e0f036 --- /dev/null +++ b/secrets/atrocore-registry-token.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 lGqVZg Nw/1hxgS6OXDgrggZJ9OM3Zhh/CZQCGA6HFZU/ttlD8 +noQd0Q3YpZFuZrvMUj/Qq6/U5OGrTYqeysKRiSnO9c8 +-> ssh-ed25519 CSMyhg HVJKkw9qSoik1lCML+jl0cFGpjRw+8enFp3JeabVnyw +R27JjwWmQadjJEGeEzej9YR6xlvOcok3GVbIvbFH/9A +--- rEC1x5+HR0Uro49upploOR7kmMeFQ4jC93+fs9NY9h8 +÷ýƒ% +Íܯ1Ï\üäXd.šZ3~u‹£¿'šûG º^$¨Ììëön‹5/SÑÅìã�9ž€ØŠ%F ÉÿóÑ5ç‰�· \ No newline at end of file