From 3a2f03d02e1cc0c5375613b36bbeda9ec86ffb34 Mon Sep 17 00:00:00 2001 From: rabenNest7$ Date: Fri, 26 Jun 2026 08:37:19 +0000 Subject: [PATCH] Add FeWo Docker deployment and expand report themes --- deploy/docker-fewo/.env.example | 12 +++ deploy/docker-fewo/compose.yaml | 86 +++++++++++++++ deploy/docker-fewo/scripts/test.sh | 67 ++++++++++++ deploy/docker-fewo/scripts/update.sh | 40 +++++++ deploy/docker-fewo/sql/reporting-schema.sql | 109 ++++++++++++++++++++ n8n/migrations/001-expand-theme.sql | 27 +++++ n8n/postgres-schema.sql | 3 +- 7 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 deploy/docker-fewo/.env.example create mode 100644 deploy/docker-fewo/compose.yaml create mode 100755 deploy/docker-fewo/scripts/test.sh create mode 100755 deploy/docker-fewo/scripts/update.sh create mode 100644 deploy/docker-fewo/sql/reporting-schema.sql create mode 100644 n8n/migrations/001-expand-theme.sql diff --git a/deploy/docker-fewo/.env.example b/deploy/docker-fewo/.env.example new file mode 100644 index 0000000..0cddfeb --- /dev/null +++ b/deploy/docker-fewo/.env.example @@ -0,0 +1,12 @@ +TZ=Europe/Berlin + +REPORT_RENDERER_BIND_IP=127.0.0.1 +REPORT_RENDERER_PORT=8090 + +RENDER_TIMEOUT_SECONDS=60 +MAX_PAYLOAD_BYTES=2097152 + +GOTENBERG_IMAGE=gotenberg/gotenberg:8 +GOTENBERG_PLATFORM=linux/amd64 + +N8N_NETWORK=docker-postgres-backend diff --git a/deploy/docker-fewo/compose.yaml b/deploy/docker-fewo/compose.yaml new file mode 100644 index 0000000..f8078ae --- /dev/null +++ b/deploy/docker-fewo/compose.yaml @@ -0,0 +1,86 @@ +name: monthly-report-kit + +services: + report-renderer: + build: + context: ./source/renderer + container_name: monthly-report-renderer + restart: unless-stopped + + environment: + TZ: ${TZ} + GOTENBERG_URL: http://gotenberg:3000 + RENDER_TIMEOUT_SECONDS: ${RENDER_TIMEOUT_SECONDS} + MAX_PAYLOAD_BYTES: ${MAX_PAYLOAD_BYTES} + + ports: + - "${REPORT_RENDERER_BIND_IP}:${REPORT_RENDERER_PORT}:8080" + + depends_on: + gotenberg: + condition: service_healthy + + healthcheck: + test: + - CMD + - python + - -c + - >- + import urllib.request; + urllib.request.urlopen( + 'http://127.0.0.1:8080/health', + timeout=3 + ).read() + interval: 15s + timeout: 5s + retries: 10 + start_period: 20s + + networks: + report_internal: + n8n_backend: + aliases: + - report-renderer + + gotenberg: + image: ${GOTENBERG_IMAGE} + platform: ${GOTENBERG_PLATFORM} + container_name: monthly-report-gotenberg + restart: unless-stopped + + init: true + + entrypoint: + - /usr/bin/gotenberg + + command: + - --chromium-disable-javascript=true + - --api-timeout=90s + + healthcheck: + test: + - CMD-SHELL + - >- + bash -c 'exec 3<>/dev/tcp/127.0.0.1/3000; + printf "HEAD /health HTTP/1.0\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n" >&3; + IFS= read -r status <&3; + case "$$status" in + *" 200 "*) exit 0 ;; + *) exit 1 ;; + esac' + interval: 5s + timeout: 5s + retries: 20 + start_period: 20s + + networks: + report_internal: + +networks: + report_internal: + name: monthly-report-internal + internal: true + + n8n_backend: + external: true + name: ${N8N_NETWORK} diff --git a/deploy/docker-fewo/scripts/test.sh b/deploy/docker-fewo/scripts/test.sh new file mode 100755 index 0000000..d49a23f --- /dev/null +++ b/deploy/docker-fewo/scripts/test.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +TARGET_DIR="${TARGET_DIR:-/srv/docker/monthly-report-kit}" +N8N_CONTAINER="${N8N_CONTAINER:-n8n}" +POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-infra-postgres}" +POSTGRES_DATABASE="${POSTGRES_DATABASE:-fewomonitor}" +POSTGRES_ADMIN_USER="${POSTGRES_ADMIN_USER:-postgres}" + +[[ -f "$TARGET_DIR/.env" ]] || { + echo "FEHLER: $TARGET_DIR/.env fehlt" >&2 + exit 1 +} + +set -a +# shellcheck disable=SC1090 +source "$TARGET_DIR/.env" +set +a + +TEST_HOST="$REPORT_RENDERER_BIND_IP" +case "$TEST_HOST" in + 0.0.0.0|"::"|"[::]") + TEST_HOST="127.0.0.1" + ;; +esac + +echo "1/5 Compose-Status" +( + cd "$TARGET_DIR" + docker compose ps +) + +echo +echo "2/5 Gotenberg-Plattform" +docker image inspect "$GOTENBERG_IMAGE" \ + --format 'Image: {{.Os}}/{{.Architecture}}' + +echo +echo "3/5 Lokaler Renderer-Healthcheck" +curl -fsS \ + "http://${TEST_HOST}:${REPORT_RENDERER_PORT}/health" +echo + +echo +echo "4/5 Zugriff aus dem n8n-Container" +docker exec "$N8N_CONTAINER" node -e " +fetch('http://report-renderer:8080/health') + .then(async response => { + if (!response.ok) { + throw new Error('HTTP ' + response.status); + } + console.log(await response.text()); + }) + .catch(error => { + console.error(error); + process.exit(1); + }); +" + +echo +echo "5/5 Reporting-Tabellen" +docker exec "$POSTGRES_CONTAINER" \ + psql \ + -U "$POSTGRES_ADMIN_USER" \ + -d "$POSTGRES_DATABASE" \ + -c '\dt public.report_customer_config' \ + -c '\dt public.monthly_report_runs' diff --git a/deploy/docker-fewo/scripts/update.sh b/deploy/docker-fewo/scripts/update.sh new file mode 100755 index 0000000..ee999d5 --- /dev/null +++ b/deploy/docker-fewo/scripts/update.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +TARGET_DIR="${TARGET_DIR:-/srv/docker/monthly-report-kit}" +SOURCE_DIR="$TARGET_DIR/source" +REPO_REF="${REPO_REF:-main}" + +[[ -d "$SOURCE_DIR/.git" ]] || { + echo "FEHLER: Kein Git-Repository unter $SOURCE_DIR" >&2 + exit 1 +} + +[[ -f "$TARGET_DIR/.env" ]] || { + echo "FEHLER: $TARGET_DIR/.env fehlt" >&2 + exit 1 +} + +set -a +# shellcheck disable=SC1090 +source "$TARGET_DIR/.env" +set +a + +: "${GOTENBERG_IMAGE:?GOTENBERG_IMAGE fehlt in .env}" +: "${GOTENBERG_PLATFORM:?GOTENBERG_PLATFORM fehlt in .env}" + +git -C "$SOURCE_DIR" fetch --prune origin +git -C "$SOURCE_DIR" checkout "$REPO_REF" +git -C "$SOURCE_DIR" pull --ff-only origin "$REPO_REF" + +cd "$TARGET_DIR" +docker compose config >/dev/null + +docker pull \ + --platform "$GOTENBERG_PLATFORM" \ + "$GOTENBERG_IMAGE" + +docker compose build --pull report-renderer +docker compose up -d --force-recreate + +"$TARGET_DIR/scripts/test-monthly-report-kit.sh" diff --git a/deploy/docker-fewo/sql/reporting-schema.sql b/deploy/docker-fewo/sql/reporting-schema.sql new file mode 100644 index 0000000..675d331 --- /dev/null +++ b/deploy/docker-fewo/sql/reporting-schema.sql @@ -0,0 +1,109 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS public.report_customer_config ( + customer_id VARCHAR(64) PRIMARY KEY, + customer_name VARCHAR(160) NOT NULL, + property_name VARCHAR(160) NOT NULL, + location VARCHAR(160) NOT NULL, + contact_line VARCHAR(180) NOT NULL DEFAULT '', + logo_data_uri TEXT, + logo_text VARCHAR(5) NOT NULL, + theme VARCHAR(40) NOT NULL DEFAULT 'modern', + primary_color CHAR(7) NOT NULL DEFAULT '#1F5D66', + secondary_color CHAR(7) NOT NULL DEFAULT '#2C7C82', + accent_color CHAR(7) NOT NULL DEFAULT '#DBA25D', + text_color CHAR(7) NOT NULL DEFAULT '#24343A', + muted_color CHAR(7) NOT NULL DEFAULT '#718087', + surface_color CHAR(7) NOT NULL DEFAULT '#F2F6F5', + font_family VARCHAR(100) NOT NULL DEFAULT 'Arial', + active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Upgrade-Pfad für die ursprüngliche Repo-Struktur. +ALTER TABLE public.report_customer_config + ALTER COLUMN theme TYPE VARCHAR(40); + +ALTER TABLE public.report_customer_config + DROP CONSTRAINT IF EXISTS report_customer_config_theme_check; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'report_customer_config_theme_not_empty' + AND conrelid = 'public.report_customer_config'::regclass + ) THEN + ALTER TABLE public.report_customer_config + ADD CONSTRAINT report_customer_config_theme_not_empty + CHECK (length(trim(theme)) > 0) NOT VALID; + END IF; +END; +$$; + +ALTER TABLE public.report_customer_config + VALIDATE CONSTRAINT report_customer_config_theme_not_empty; + +CREATE TABLE IF NOT EXISTS public.monthly_report_runs ( + id BIGSERIAL PRIMARY KEY, + customer_id VARCHAR(64) NOT NULL + REFERENCES public.report_customer_config(customer_id), + report_year INTEGER NOT NULL + CHECK (report_year BETWEEN 2020 AND 2100), + report_month INTEGER NOT NULL + CHECK (report_month BETWEEN 1 AND 12), + status VARCHAR(20) NOT NULL + CHECK (status IN ('queued', 'processing', 'completed', 'failed')), + report_version VARCHAR(20) NOT NULL, + prompt_version VARCHAR(20) NOT NULL, + template_version VARCHAR(20) NOT NULL, + claude_model VARCHAR(80), + storage_path TEXT, + checksum_sha256 CHAR(64), + error_message TEXT, + started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (customer_id, report_year, report_month, report_version) +); + +CREATE INDEX IF NOT EXISTS idx_monthly_report_runs_status + ON public.monthly_report_runs(status, report_year, report_month); + +CREATE INDEX IF NOT EXISTS idx_monthly_report_runs_customer_period + ON public.monthly_report_runs(customer_id, report_year, report_month); + +CREATE OR REPLACE FUNCTION public.set_report_customer_config_updated_at() +RETURNS trigger +LANGUAGE plpgsql +AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + +DROP TRIGGER IF EXISTS trg_report_customer_config_updated_at + ON public.report_customer_config; + +CREATE TRIGGER trg_report_customer_config_updated_at +BEFORE UPDATE ON public.report_customer_config +FOR EACH ROW +EXECUTE FUNCTION public.set_report_customer_config_updated_at(); + +ALTER TABLE public.report_customer_config OWNER TO fewo; +ALTER TABLE public.monthly_report_runs OWNER TO fewo; +ALTER SEQUENCE IF EXISTS public.monthly_report_runs_id_seq OWNER TO fewo; +ALTER FUNCTION public.set_report_customer_config_updated_at() OWNER TO fewo; + +GRANT SELECT, INSERT, UPDATE, DELETE + ON public.report_customer_config, public.monthly_report_runs + TO fewo; + +GRANT USAGE, SELECT, UPDATE + ON SEQUENCE public.monthly_report_runs_id_seq + TO fewo; + +COMMIT; diff --git a/n8n/migrations/001-expand-theme.sql b/n8n/migrations/001-expand-theme.sql new file mode 100644 index 0000000..8c89f33 --- /dev/null +++ b/n8n/migrations/001-expand-theme.sql @@ -0,0 +1,27 @@ +BEGIN; + +ALTER TABLE public.report_customer_config + ALTER COLUMN theme TYPE VARCHAR(40); + +ALTER TABLE public.report_customer_config + DROP CONSTRAINT IF EXISTS report_customer_config_theme_check; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'report_customer_config_theme_not_empty' + AND conrelid = 'public.report_customer_config'::regclass + ) THEN + ALTER TABLE public.report_customer_config + ADD CONSTRAINT report_customer_config_theme_not_empty + CHECK (length(trim(theme)) > 0) NOT VALID; + END IF; +END; +$$; + +ALTER TABLE public.report_customer_config + VALIDATE CONSTRAINT report_customer_config_theme_not_empty; + +COMMIT; diff --git a/n8n/postgres-schema.sql b/n8n/postgres-schema.sql index 8017b2c..c359dbb 100644 --- a/n8n/postgres-schema.sql +++ b/n8n/postgres-schema.sql @@ -6,7 +6,8 @@ CREATE TABLE IF NOT EXISTS report_customer_config ( contact_line VARCHAR(180) NOT NULL DEFAULT '', logo_data_uri TEXT, logo_text VARCHAR(5) NOT NULL, - theme VARCHAR(20) NOT NULL DEFAULT 'modern' CHECK (theme IN ('modern', 'classic')), + theme VARCHAR(40) NOT NULL DEFAULT 'modern' + CHECK (length(trim(theme)) > 0), primary_color CHAR(7) NOT NULL DEFAULT '#1F5D66', secondary_color CHAR(7) NOT NULL DEFAULT '#2C7C82', accent_color CHAR(7) NOT NULL DEFAULT '#DBA25D',