From 705702abee0daa068ee19ef1797516a9acb3fd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20K=C3=B6nig?= Date: Mon, 17 Aug 2026 13:00:06 +0200 Subject: [PATCH] feat: AtroPIM image pipeline with secret-free build (AZ-NIX-ava.1) - flake input atrocore-docker rev-pinned (e1e9bed) - pkgs/atropim-image: atropim-build/atropim-push scripts + 2-stage podman build (vendor pdf target + entrypoint wrapper) - entrypoint writes ATRO_DB_* to data/config.php at runtime, guarded by isInstalled (idempotent); no DB credentials in layers - devShell documents build/push commands and ENV variables --- flake.lock | 18 ++++ flake.nix | 27 +++++- overlays/default.nix | 5 +- pkgs/atropim-image/default.nix | 95 +++++++++++++++++++ pkgs/atropim-image/entrypoint/Dockerfile | 13 +++ .../entrypoint/entrypoint-prepare-pim.php | 32 +++++++ pkgs/atropim-image/entrypoint/entrypoint.sh | 24 +++++ pkgs/default.nix | 7 +- 8 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 pkgs/atropim-image/default.nix create mode 100644 pkgs/atropim-image/entrypoint/Dockerfile create mode 100644 pkgs/atropim-image/entrypoint/entrypoint-prepare-pim.php create mode 100644 pkgs/atropim-image/entrypoint/entrypoint.sh diff --git a/flake.lock b/flake.lock index 7ac78d8..ca03d67 100644 --- a/flake.lock +++ b/flake.lock @@ -145,6 +145,23 @@ "url": "https://code.m3ta.dev/m3tam3re/AGENTS" } }, + "atrocore-docker": { + "flake": false, + "locked": { + "lastModified": 1786515722, + "narHash": "sha256-17KU/c6l4SEoqM13vdvplI0ENxCHJ65SyG0gpzbhyqg=", + "owner": "atrocore", + "repo": "docker", + "rev": "e1e9bed1f6ae983d1aac474657cbeba1c004e313", + "type": "github" + }, + "original": { + "owner": "atrocore", + "repo": "docker", + "rev": "e1e9bed1f6ae983d1aac474657cbeba1c004e313", + "type": "github" + } + }, "azess": { "inputs": { "nixpkgs": [ @@ -1480,6 +1497,7 @@ "inputs": { "agenix": "agenix", "agents": "agents", + "atrocore-docker": "atrocore-docker", "azess": "azess", "azion-scheduler": "azion-scheduler", "disko": "disko", diff --git a/flake.nix b/flake.nix index 78f3dbc..606c293 100644 --- a/flake.nix +++ b/flake.nix @@ -70,6 +70,12 @@ url = "git+https://git.az-gruppe.com/AZ-Intec-GmbH/phishboard.git"; inputs.nixpkgs.follows = "nixpkgs"; }; + + atrocore-docker = { + # Rev-pinned vendor Dockerfiles for the AtroPIM image pipeline (AZ-NIX-ava.1) + url = "github:atrocore/docker/e1e9bed1f6ae983d1aac474657cbeba1c004e313"; + flake = false; + }; }; outputs = { @@ -90,8 +96,11 @@ ]; forAllSystems = nixpkgs.lib.genAttrs systems; in { - packages = - forAllSystems (system: import ./pkgs nixpkgs.legacyPackages.${system}); + packages = forAllSystems (system: + import ./pkgs { + pkgs = nixpkgs.legacyPackages.${system}; + atrocore-docker = inputs.atrocore-docker; + }); overlays = let all = import ./overlays {inherit inputs;}; in @@ -116,6 +125,7 @@ opencode # pi-coding-agent inputs.agenix.packages.${system}.default + outputs.packages.${system}.atropim-tools ]; shellHook = '' @@ -140,6 +150,19 @@ echo " - ls .opencode-rules/ Browse available rules" echo " - nix develop Re-enter this shell" echo "" + echo "🐘 AtroPIM Image Pipeline (AZ-NIX-ava)" + echo " Commands:" + echo " atropim-build Build secret-free AtroPIM image (podman, 2 stages)" + echo " atropim-push [version] Tag + push to Gitea registry (default tag: YYYYMMDD)" + echo " Build parameters (pkgs/atropim-image/default.nix):" + echo " SKELETON_VARIANT=pim-no-demo BUILD_VARIANT=pdf PRODUCTION_STABILITY=stable" + echo " PRODUCTION_DOMAIN=pim.l.az-gruppe.com DB build args deliberately EMPTY" + echo " Runtime ENV (written to data/config.php at container start):" + echo " ATRO_DB_HOST ATRO_DB_NAME ATRO_DB_USER ATRO_DB_PASSWORD" + echo " Registry: git.az-gruppe.com/az-intec-gmbh/atrocore-web (podman login git.az-gruppe.com)" + echo " Quick test:" + echo " podman run -d --name atropim -p 127.0.0.1:8080:80 " + echo "" echo "Remember to add to .gitignore:" echo " .opencode-rules" echo " coding-rules.json" diff --git a/overlays/default.nix b/overlays/default.nix index 8dc3aef..946bf3d 100644 --- a/overlays/default.nix +++ b/overlays/default.nix @@ -1,7 +1,10 @@ {inputs, ...}: { # This one brings our custom packages from the 'pkgs' directory additions = final: prev: - (import ../pkgs {pkgs = final;}) + (import ../pkgs { + pkgs = final; + atrocore-docker = inputs.atrocore-docker; + }) // { zugferd-service = inputs.zugferd-service.packages.${prev.stdenv.hostPlatform.system}.default; }; diff --git a/pkgs/atropim-image/default.nix b/pkgs/atropim-image/default.nix new file mode 100644 index 0000000..be493ff --- /dev/null +++ b/pkgs/atropim-image/default.nix @@ -0,0 +1,95 @@ +{ + lib, + pkgs, + atrocore-docker, + registryHost ? "git.az-gruppe.com", + registryNamespace ? "az-intec-gmbh", + imageName ? "atrocore-web", + skeletonVariant ? "pim-no-demo", + buildVariant ? "pdf", + productionDomain ? "pim.l.az-gruppe.com", + productionStability ? "stable", +}: let + imageRef = "${registryHost}/${registryNamespace}/${imageName}"; + baseTag = "localhost/${imageName}-base:build"; + entrypointContext = ./entrypoint; + + # The vendor Dockerfile uses the unqualified FROM "php:8.4-apache-bookworm"; + # resolve it against docker.io without touching the host's registries.conf. + registriesConf = pkgs.writeText "atropim-registries.conf" '' + unqualified-search-registries = ["docker.io"] + ''; + + podman = lib.getExe pkgs.podman; + + atropim-build = pkgs.writeShellApplication { + name = "atropim-build"; + runtimeInputs = with pkgs; [coreutils]; + text = '' + # Two-stage build: stage 1 builds the untouched vendor image from the + # rev-pinned atrocore/docker flake input, stage 2 layers the secret-free + # entrypoint wrapper on top. All DB build args stay empty on purpose: + # no credentials are ever baked into any layer. + export CONTAINERS_REGISTRIES_CONF="${registriesConf}" + + echo "[atropim-build] stage 1: vendor image from ${atrocore-docker} (target ${buildVariant})" + ${podman} build \ + --target "${buildVariant}" \ + --build-arg "SKELETON_VARIANT=${skeletonVariant}" \ + --build-arg "PRODUCTION_DOMAIN=${productionDomain}" \ + --build-arg "PRODUCTION_STABILITY=${productionStability}" \ + --build-arg "PRODUCTION_DB=" \ + --build-arg "DB_USER=" \ + --build-arg "DB_PASSWORD=" \ + --tag "${baseTag}" \ + --file "${atrocore-docker}/.docker/php/Dockerfile" \ + "${atrocore-docker}/.docker" + + echo "[atropim-build] stage 2: entrypoint wrapper -> ${imageRef}:latest" + ${podman} build \ + --build-arg "BASE_IMAGE=${baseTag}" \ + --label "org.opencontainers.image.title=${imageName}" \ + --label "org.opencontainers.image.description=AtroPIM (${skeletonVariant}) web image, secret-free build with runtime DB config" \ + --tag "${imageRef}:latest" \ + --file "${entrypointContext}/Dockerfile" \ + "${entrypointContext}" + + echo "[atropim-build] done: ${imageRef}:latest" + echo "[atropim-build] verify the image is secret-free:" + echo " podman run --rm ${imageRef}:latest ls /var/www/${productionDomain}/data" + ''; + }; + + atropim-push = pkgs.writeShellApplication { + name = "atropim-push"; + runtimeInputs = with pkgs; [coreutils]; + text = '' + VERSION="''${1:-$(date +%Y%m%d)}" + + if ! ${podman} image exists "${imageRef}:latest"; then + echo "error: ${imageRef}:latest not found - run atropim-build first" >&2 + exit 1 + fi + + if ! ${podman} login --get-login "${registryHost}" >/dev/null 2>&1; then + echo "not logged in to ${registryHost} - run: podman login ${registryHost}" >&2 + exit 1 + fi + + echo "[atropim-push] pushing ${imageRef}:{latest,''${VERSION}}" + ${podman} tag "${imageRef}:latest" "${imageRef}:''${VERSION}" + ${podman} push "${imageRef}:''${VERSION}" + ${podman} push "${imageRef}:latest" + + echo "[atropim-push] done - package visible at https://${registryHost}/${registryNamespace}/-/packages" + ''; + }; +in + pkgs.symlinkJoin { + name = "atropim-tools"; + paths = [atropim-build atropim-push]; + meta = { + description = "Build/push tooling for the secret-free AtroPIM image (${imageRef})"; + platforms = lib.platforms.linux; + }; + } diff --git a/pkgs/atropim-image/entrypoint/Dockerfile b/pkgs/atropim-image/entrypoint/Dockerfile new file mode 100644 index 0000000..6524bf1 --- /dev/null +++ b/pkgs/atropim-image/entrypoint/Dockerfile @@ -0,0 +1,13 @@ +# Stage 2 of the AtroPIM image pipeline: takes the vendor-built base image +# (atrocore/docker, target "pdf") and layers the secret-free entrypoint +# wrapper on top. The base image reference is injected via BASE_IMAGE so the +# vendor Dockerfile stays untouched (pinned flake input). +ARG BASE_IMAGE +FROM ${BASE_IMAGE} + +COPY entrypoint.sh /entrypoint.sh +COPY entrypoint-prepare-pim.php /entrypoint-prepare-pim.php + +RUN chmod +x /entrypoint.sh + +CMD ["/entrypoint.sh"] diff --git a/pkgs/atropim-image/entrypoint/entrypoint-prepare-pim.php b/pkgs/atropim-image/entrypoint/entrypoint-prepare-pim.php new file mode 100644 index 0000000..6f7a538 --- /dev/null +++ b/pkgs/atropim-image/entrypoint/entrypoint-prepare-pim.php @@ -0,0 +1,32 @@ + \n"); +} + +$instanceDir = $argv[1]; + +chdir($instanceDir); +set_include_path($instanceDir); + +require_once 'vendor/autoload.php'; + +$app = new \Atro\Core\Application(); +$config = $app->getContainer()->get('config'); + +if ($config->get('isInstalled')) { + exit("[entrypoint] instance already installed - keeping existing data/config.php\n"); +} + +$config->set('database', [ + 'driver' => 'pdo_pgsql', + 'host' => $argv[2], + 'port' => '', + 'charset' => 'utf8', + 'dbname' => $argv[3], + 'user' => $argv[4], + 'password' => $argv[5], +]); +$config->set('useChromeNoSandbox', true); +$config->save(); diff --git a/pkgs/atropim-image/entrypoint/entrypoint.sh b/pkgs/atropim-image/entrypoint/entrypoint.sh new file mode 100644 index 0000000..549b366 --- /dev/null +++ b/pkgs/atropim-image/entrypoint/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# Secret-free AtroPIM image entrypoint. +# +# Before cron/apache start, the ATRO_DB_* environment variables are written +# into data/config.php of the instance directory (same pattern as the vendor's +# prepare-pim.php). The PHP side skips the write once the instance is +# installed (isInstalled), which makes the wrapper idempotent: a config +# completed by the web installer is never overwritten. + +set -e + +INSTANCE_DIR="/var/www/${ATRO_INSTANCE_DIR:-pim.l.az-gruppe.com}" + +if [ -n "${ATRO_DB_HOST:-}" ] && [ -n "${ATRO_DB_NAME:-}" ] && [ -n "${ATRO_DB_USER:-}" ] && [ -n "${ATRO_DB_PASSWORD:-}" ]; then + echo "[entrypoint] applying ATRO_DB_* variables to ${INSTANCE_DIR}/data/config.php" + mkdir -p "${INSTANCE_DIR}/data" + php /entrypoint-prepare-pim.php "${INSTANCE_DIR}" "${ATRO_DB_HOST}" "${ATRO_DB_NAME}" "${ATRO_DB_USER}" "${ATRO_DB_PASSWORD}" + # The web installer (running as www-data) must stay able to update the config later. + chown www-data:www-data "${INSTANCE_DIR}/data/config.php" +else + echo "[entrypoint] no ATRO_DB_* variables set - starting web installer" +fi + +exec /startup.sh diff --git a/pkgs/default.nix b/pkgs/default.nix index 0fbbbd6..8faaba0 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,4 +1,9 @@ -{pkgs, ...}: { +{ + pkgs, + atrocore-docker, + ... +}: { # Define your custom packages here online3dviewer = pkgs.callPackage ./online3dviewer {}; + atropim-tools = pkgs.callPackage ./atropim-image {inherit atrocore-docker;}; }