feat: AtroPIM deployment config for AZ-PRM-1 (AZ-NIX-ava.2)

- port registry: atrocore = 3058
- oci-container atrocore: Gitea-registry image, 127.0.0.1:3058:80,
  web network, static ip 10.89.0.16, db alias to host pg, env-file
  from agenix, named volume for instance data, registry login via
  token secret
- agenix secrets: atrocore-env (ATRO_DB_*) + atrocore-registry-token,
  age-encrypted to AZ-PRM-1 + user, non-interactive creation
- host postgres 17: idempotent atrocore-db-init oneshot (psql peer,
  secret only at runtime), pg_hba 10.89.0.0/24 scram-sha-256,
  atrocore in 03:10 backup list
- traefik: pim.l.az-gruppe.com -> localhost:3058 (ionos, websecure)
This commit is contained in:
m3ta-chiron
2026-08-17 13:45:21 +02:00
parent 705702abee
commit f1481dc256
8 changed files with 115 additions and 1 deletions
+6
View File
@@ -5,6 +5,12 @@ in {
age = { age = {
secrets = secrets =
{ {
atrocore-env = {
file = ../../secrets/atrocore-env.age;
};
atrocore-registry-token = {
file = ../../secrets/atrocore-registry-token.age;
};
azion-env = { azion-env = {
file = ../../secrets/azion-env.age; file = ../../secrets/azion-env.age;
}; };
@@ -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";
};
};
}
@@ -1,6 +1,7 @@
{lib, ...}: { {lib, ...}: {
imports = [ imports = [
#./baserow.nix #./baserow.nix
./atrocore.nix
./excalidraw.nix ./excalidraw.nix
./kestra.nix ./kestra.nix
./online3dviewer.nix ./online3dviewer.nix
+2 -1
View File
@@ -52,6 +52,7 @@
# Podman network connections # Podman network connections
host baserow baserow 10.89.0.0/24 scram-sha-256 host baserow baserow 10.89.0.0/24 scram-sha-256
host kestra kestra 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) # Grafana (local socket / localhost only)
host grafana grafana 127.0.0.1/32 scram-sha-256 host grafana grafana 127.0.0.1/32 scram-sha-256
@@ -65,7 +66,7 @@
services.postgresqlBackup = { services.postgresqlBackup = {
enable = true; enable = true;
startAt = "03:10:00"; startAt = "03:10:00";
databases = ["baserow" "kestra" "n8n" "netbox"]; databases = ["atrocore" "baserow" "kestra" "n8n" "netbox"];
}; };
networking.firewall = { networking.firewall = {
extraCommands = '' extraCommands = ''
+1
View File
@@ -21,6 +21,7 @@
azion-scheduler-proxy = 3049; azion-scheduler-proxy = 3049;
phishboard = 3055; phishboard = 3055;
homarr = 3057; homarr = 3057;
atrocore = 3058;
metabase = 3013; metabase = 3013;
baserow = 3050; baserow = 3050;
+2
View File
@@ -11,6 +11,8 @@ let
users = [sascha.koenig]; users = [sascha.koenig];
systems = [AZ-CLD-1 AZ-LT-NIX AZ-PRM-1]; systems = [AZ-CLD-1 AZ-LT-NIX AZ-PRM-1];
in { 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/azion-env.age".publicKeys = systems ++ users;
"secrets/server.crt.age".publicKeys = systems ++ users; "secrets/server.crt.age".publicKeys = systems ++ users;
"secrets/server.key.age".publicKeys = systems ++ users; "secrets/server.key.age".publicKeys = systems ++ users;
+8
View File
@@ -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›�
+8
View File
@@ -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ç‰�·