completed local setup with compose
Some checks failed
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled

This commit is contained in:
harkon
2025-11-26 13:17:17 +00:00
parent 8fe5e62fee
commit fdba81809f
87 changed files with 5610 additions and 3376 deletions

View File

@@ -2,6 +2,8 @@
Multi-environment Docker Compose infrastructure for AI Tax Agent.
For local development use the dedicated self-signed stack in `infra/compose` (see `infra/compose/README.md`). For remote environments use the shared base files with `infra/scripts/deploy.sh` and the envs in `infra/environments`.
## Directory Structure
```
@@ -244,4 +246,3 @@ For issues or questions:
- Check logs: `docker compose logs -f <service>`
- Review documentation in `docs/`
- Check Traefik dashboard for routing issues

View File

@@ -0,0 +1,370 @@
# FILE: blueprints/ai-tax-agent-bootstrap.yaml
# Authentik Bootstrap (v2025.x): users, groups, scope mappings, OIDC providers, applications
version: 1
metadata:
name: AI Tax Agent — Bootstrap + OIDC Providers
entries:
# --- Groups first (so the admin user can reference them) -------------------
- model: authentik_core.group
state: present
identifiers:
name: "Administrators"
attrs:
is_superuser: true
- model: authentik_core.group
state: present
identifiers:
name: "Tax Reviewers"
attrs:
is_superuser: false
- model: authentik_core.group
state: present
identifiers:
name: "Accountants"
attrs:
is_superuser: false
- model: authentik_core.group
state: present
identifiers:
name: "Clients"
attrs:
is_superuser: false
# --- Admin user ------------------------------------------------------------
- model: authentik_core.user
state: present
identifiers:
username: admin
attrs:
name: "System Administrator"
email: admin@local.lan
is_active: true
is_staff: true
is_superuser: true
groups:
- !Find [authentik_core.group, [name, "Administrators"]]
# Helper finders
# ========= OIDC Providers + Applications ==================================
# --- UI Review (Proxy Provider for ForwardAuth) ---------------------------
- model: authentik_providers_proxy.proxyprovider
state: present
identifiers:
name: "UI Review Proxy"
attrs:
external_host: "https://review.local.lan"
internal_host: "http://ui-review:3030"
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
mode: "forward_single"
cookie_domain: "local.lan"
- model: authentik_core.application
state: present
identifiers:
slug: "ui-review"
attrs:
name: "UI Review"
provider:
!Find [
authentik_providers_proxy.proxyprovider,
[name, "UI Review Proxy"],
]
meta_launch_url: "https://review.local.lan"
meta_description: "Tax Agent Platform - Review UI"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- Vault OIDC Provider --------------------------------------------------
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "Vault OIDC"
attrs:
client_id: "vault"
client_secret: !Env [AUTHENTIK_VAULT_CLIENT_SECRET, "changeme"]
client_type: "confidential"
redirect_uris:
- matching_mode: strict
url: "https://vault.local.lan/ui/vault/auth/oidc/oidc/callback"
- matching_mode: strict
url: "https://vault.local.lan/oidc/callback"
- matching_mode: strict
url: "http://localhost:8250/oidc/callback"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
issuer_mode: "per_provider"
signing_key:
!Find [
authentik_crypto.certificatekeypair,
[name, "authentik Self-signed Certificate"],
]
property_mappings:
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "openid"],
]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, "email"]]
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "profile"],
]
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
- model: authentik_core.application
state: present
identifiers:
slug: "vault-oidc"
attrs:
name: "Vault OIDC"
provider:
!Find [authentik_providers_oauth2.oauth2provider, [name, "Vault OIDC"]]
meta_launch_url: "https://vault.local.lan"
meta_description: "Vault OIDC Authentication"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- MinIO OIDC Provider --------------------------------------------------
# Scope Mapping for MinIO Policy
- model: authentik_providers_oauth2.scopemapping
state: present
identifiers:
name: "MinIO Policy Mapping"
attrs:
name: "MinIO Policy Mapping"
description: "Maps Authentik users to MinIO policies"
scope_name: "minio"
expression: |
# Default to readwrite for all authenticated users
# You can customize this based on groups
return {
"policy": "readwrite"
}
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "MinIO OIDC"
attrs:
client_id: "minio"
client_secret: !Env [AUTHENTIK_MINIO_CLIENT_SECRET, "changeme"]
client_type: "confidential"
redirect_uris:
- matching_mode: strict
url: "https://minio.local.lan/oauth_callback"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
issuer_mode: "per_provider"
signing_key:
!Find [
authentik_crypto.certificatekeypair,
[name, "authentik Self-signed Certificate"],
]
property_mappings:
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "openid"],
]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, "email"]]
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "profile"],
]
- !Find [
authentik_providers_oauth2.scopemapping,
[name, "MinIO Policy Mapping"],
]
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
- model: authentik_core.application
state: present
identifiers:
slug: "minio-oidc"
attrs:
name: "MinIO OIDC"
provider:
!Find [authentik_providers_oauth2.oauth2provider, [name, "MinIO OIDC"]]
meta_launch_url: "https://minio.local.lan"
meta_description: "MinIO Object Storage OIDC"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- Grafana SSO Configuration -------------------------------------------
# Custom Role Mapping for Grafana
- model: authentik_providers_oauth2.scopemapping
state: present
identifiers:
name: "Grafana Role Mapping"
attrs:
name: "Grafana Role Mapping"
description: "Maps Authentik groups to Grafana roles"
scope_name: "role"
expression: |
# Map Authentik groups to Grafana roles
user_groups = [group.name for group in request.user.ak_groups.all()]
# Admin role mapping
if "authentik Admins" in user_groups or "Administrators" in user_groups:
return "Admin"
# Editor role mapping
if "Tax Reviewers" in user_groups or "Accountants" in user_groups:
return "Editor"
# Default to Viewer role
return "Viewer"
# Grafana OAuth2 Provider
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "Grafana"
attrs:
client_id: !Env [GRAFANA_OAUTH_CLIENT_ID, "grafana"]
client_secret: !Env [GRAFANA_OAUTH_CLIENT_SECRET, "changeme"]
client_type: "confidential"
redirect_uris:
- matching_mode: strict
url: "https://grafana.local.lan/login/generic_oauth"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
issuer_mode: "per_provider"
signing_key:
!Find [
authentik_crypto.certificatekeypair,
[name, "authentik Self-signed Certificate"],
]
property_mappings:
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "openid"],
]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, "email"]]
- !Find [
authentik_providers_oauth2.scopemapping,
[scope_name, "profile"],
]
- !Find [
authentik_providers_oauth2.scopemapping,
[name, "Grafana Role Mapping"],
]
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
# Grafana Application
- model: authentik_core.application
state: present
identifiers:
slug: "grafana"
attrs:
name: "Grafana"
provider:
!Find [authentik_providers_oauth2.oauth2provider, [name, "Grafana"]]
meta_launch_url: "https://grafana.local.lan"
meta_description: "Grafana monitoring and observability platform"
meta_publisher: "Grafana Labs"
policy_engine_mode: "any"
# --- Traefik Dashboard (Proxy Provider for ForwardAuth) -------------------
- model: authentik_providers_proxy.proxyprovider
state: present
identifiers:
name: "Traefik Dashboard Proxy"
attrs:
external_host: "https://traefik.local.lan"
internal_host: "http://apa-traefik:8080"
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
mode: "forward_single"
cookie_domain: "local.lan"
- model: authentik_core.application
state: present
identifiers:
slug: "traefik-dashboard"
attrs:
name: "Traefik Dashboard"
provider:
!Find [
authentik_providers_proxy.proxyprovider,
[name, "Traefik Dashboard Proxy"],
]
meta_launch_url: "https://traefik.local.lan"
meta_description: "Traefik Edge Router Dashboard"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- AI Tax Agent API (Proxy Provider for ForwardAuth) --------------------
- model: authentik_providers_proxy.proxyprovider
state: present
identifiers:
name: "AI Tax Agent API Proxy"
attrs:
external_host: "https://api.local.lan"
internal_host: "http://apa-traefik:8080"
authorization_flow:
!Find [authentik_flows.flow, [slug, "default-authentication-flow"]]
invalidation_flow:
!Find [authentik_flows.flow, [slug, "default-invalidation-flow"]]
mode: "forward_single"
cookie_domain: "local.lan"
- model: authentik_core.application
state: present
identifiers:
slug: "ai-tax-agent-api-gateway"
attrs:
name: "AI Tax Agent API Gateway"
provider:
!Find [
authentik_providers_proxy.proxyprovider,
[name, "AI Tax Agent API Proxy"],
]
meta_launch_url: "https://api.local.lan"
meta_description: "AI Tax Agent API Gateway"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- Outpost Configuration ------------------------------------------------
- model: authentik_outposts.outpost
state: present
identifiers:
name: "authentik Embedded Outpost"
attrs:
token: !Env [AUTHENTIK_OUTPOST_TOKEN, "changeme"]
providers:
- !Find [
authentik_providers_proxy.proxyprovider,
[name, "Traefik Dashboard Proxy"],
]
- !Find [
authentik_providers_proxy.proxyprovider,
[name, "UI Review Proxy"],
]
- !Find [
authentik_providers_proxy.proxyprovider,
[name, "AI Tax Agent API Proxy"],
]

View File

@@ -20,6 +20,7 @@ volumes:
vault_data:
redis_data:
nats_data:
authentik_data:
services:
# Edge Gateway & SSO
@@ -37,6 +38,14 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/config/:/etc/traefik/:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.${DOMAIN}`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls=true"
- "traefik.http.routers.dashboard.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=authentik-forwardauth@file"
# Identity & SSO (Authentik)
apa-authentik-db:
@@ -46,7 +55,7 @@ services:
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data
- authentik_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
@@ -94,7 +103,7 @@ services:
- "traefik.http.routers.authentik.rule=Host(`auth.${DOMAIN}`)"
- "traefik.http.routers.authentik.entrypoints=websecure"
- "traefik.http.routers.authentik.tls=true"
- "traefik.http.routers.authentik.tls.certresolver=godaddy"
- "traefik.http.routers.authentik.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
apa-authentik-worker:
@@ -149,18 +158,23 @@ services:
command: vault server -dev -dev-listen-address=0.0.0.0:8200
cap_add:
- IPC_LOCK
extra_hosts:
- "auth.local.lan:host-gateway"
- "vault.local.lan:host-gateway"
- "minio.local.lan:host-gateway"
- "api.local.lan:host-gateway"
- "traefik.local.lan:host-gateway"
labels:
- "traefik.enable=true"
- "traefik.http.routers.vault.rule=Host(`vault.${DOMAIN}`)"
- "traefik.http.routers.vault.entrypoints=websecure"
- "traefik.http.routers.vault.tls=true"
- "traefik.http.routers.vault.tls.certresolver=godaddy"
- "traefik.http.routers.vault.middlewares=authentik-forwardauth@file"
- "traefik.http.routers.vault.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.services.vault.loadbalancer.server.port=8200"
# Object Storage
apa-minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
container_name: apa-minio
restart: unless-stopped
networks:
@@ -172,26 +186,35 @@ services:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_BROWSER_REDIRECT_URL: https://minio.${DOMAIN}
MINIO_IDENTITY_OPENID_CONFIG_URL: "https://auth.${DOMAIN}/application/o/minio-oidc/.well-known/openid-configuration"
MINIO_IDENTITY_OPENID_CLIENT_ID: "minio"
MINIO_IDENTITY_OPENID_CLIENT_SECRET: ${AUTHENTIK_MINIO_CLIENT_SECRET}
MINIO_IDENTITY_OPENID_SCOPES: "openid,profile,email,minio"
MINIO_IDENTITY_OPENID_REDIRECT_URI: "https://minio.${DOMAIN}/oauth_callback"
MINIO_IDENTITY_OPENID_DISPLAY_NAME: "Login with Authentik"
command: server /data --address ":9092" --console-address ":9093"
healthcheck:
test: ["CMD", "mc", "--version"]
test: ["CMD", "curl", "-f", "http://localhost:9092/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
extra_hosts:
- "auth.local.lan:host-gateway"
- "minio.local.lan:host-gateway"
- "api.local.lan:host-gateway"
- "traefik.local.lan:host-gateway"
labels:
- "traefik.enable=true"
- "traefik.http.routers.minio-api.rule=Host(`minio-api.${DOMAIN}`)"
- "traefik.http.routers.minio-api.entrypoints=websecure"
- "traefik.http.routers.minio-api.tls=true"
- "traefik.http.routers.minio-api.tls.certresolver=godaddy"
- "traefik.http.routers.minio-api.middlewares=authentik-forwardauth@file"
- "traefik.http.routers.minio-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.minio-api.service=minio-api"
- "traefik.http.services.minio-api.loadbalancer.server.port=9092"
- "traefik.http.routers.minio-console.rule=Host(`minio.${DOMAIN}`)"
- "traefik.http.routers.minio-console.entrypoints=websecure"
- "traefik.http.routers.minio-console.tls=true"
- "traefik.http.routers.minio-console.tls.certresolver=godaddy"
- "traefik.http.routers.minio-console.middlewares=authentik-forwardauth@file"
- "traefik.http.routers.minio-console.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.minio-console.service=minio-console"
- "traefik.http.services.minio-console.loadbalancer.server.port=9093"
@@ -214,7 +237,7 @@ services:
- "traefik.http.routers.qdrant.rule=Host(`qdrant.${DOMAIN}`)"
- "traefik.http.routers.qdrant.entrypoints=websecure"
- "traefik.http.routers.qdrant.tls=true"
- "traefik.http.routers.qdrant.tls.certresolver=godaddy"
- "traefik.http.routers.qdrant.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.qdrant.middlewares=authentik-forwardauth@file"
- "traefik.http.services.qdrant.loadbalancer.server.port=6333"
@@ -242,7 +265,7 @@ services:
- "traefik.http.routers.neo4j.rule=Host(`neo4j.${DOMAIN}`)"
- "traefik.http.routers.neo4j.entrypoints=websecure"
- "traefik.http.routers.neo4j.tls=true"
- "traefik.http.routers.neo4j.tls.certresolver=godaddy"
- "traefik.http.routers.neo4j.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.neo4j.middlewares=authentik-forwardauth@file"
- "traefik.http.services.neo4j.loadbalancer.server.port=7474"
@@ -334,6 +357,6 @@ services:
- "traefik.http.routers.nats-monitor.rule=Host(`nats.${DOMAIN}`)"
- "traefik.http.routers.nats-monitor.entrypoints=websecure"
- "traefik.http.routers.nats-monitor.tls=true"
- "traefik.http.routers.nats-monitor.tls.certresolver=godaddy"
- "traefik.http.routers.nats-monitor.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.nats-monitor.middlewares=authentik-forwardauth@file"
- "traefik.http.services.nats-monitor.loadbalancer.server.port=8222"

30
infra/base/loki/loki.yml Normal file
View File

@@ -0,0 +1,30 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093

View File

@@ -0,0 +1,26 @@
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://apa-loki:3100/loki/api/v1/push
scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*log
- job_name: docker
static_configs:
- targets:
- localhost
labels:
job: docker
__path__: /var/lib/docker/containers/*/*-json.log

View File

@@ -39,7 +39,7 @@ services:
- "traefik.http.routers.prometheus.rule=Host(`prometheus.${DOMAIN}`)"
- "traefik.http.routers.prometheus.entrypoints=websecure"
- "traefik.http.routers.prometheus.tls=true"
- "traefik.http.routers.prometheus.tls.certresolver=godaddy"
- "traefik.http.routers.prometheus.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.prometheus.middlewares=authentik-forwardauth@file"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
@@ -80,12 +80,19 @@ services:
GF_SECURITY_COOKIE_SECURE: true
GF_SECURITY_COOKIE_SAMESITE: lax
GF_AUTH_GENERIC_OAUTH_USE_PKCE: true
GF_AUTH_GENERIC_OAUTH_TLS_SKIP_VERIFY_INSECURE: true
GF_AUTH_SIGNOUT_REDIRECT_URL: https://auth.${DOMAIN}/application/o/grafana/end-session/
extra_hosts:
- "auth.local.lan:host-gateway"
- "grafana.local.lan:host-gateway"
- "api.local.lan:host-gateway"
- "traefik.local.lan:host-gateway"
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true"
- "traefik.http.routers.grafana.tls.certresolver=godaddy"
- "traefik.http.routers.grafana.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
# Log Aggregation
@@ -105,7 +112,7 @@ services:
- "traefik.http.routers.loki.rule=Host(`loki.${DOMAIN}`)"
- "traefik.http.routers.loki.entrypoints=websecure"
- "traefik.http.routers.loki.tls=true"
- "traefik.http.routers.loki.tls.certresolver=godaddy"
- "traefik.http.routers.loki.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.loki.middlewares=authentik-forwardauth@file"
- "traefik.http.services.loki.loadbalancer.server.port=3100"

View File

@@ -0,0 +1,21 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "traefik"
static_configs:
- targets: ["apa-traefik:8080"]
- job_name: "services"
static_configs:
- targets:
- "apa-svc-ingestion:8000"
- "apa-svc-extract:8000"
- "apa-svc-kg:8000"
- "apa-svc-rag-retriever:8000"
- "apa-svc-rag-indexer:8000"

View File

@@ -40,8 +40,8 @@ services:
- "traefik.http.routers.svc-ingestion.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/ingestion`)"
- "traefik.http.routers.svc-ingestion.entrypoints=websecure"
- "traefik.http.routers.svc-ingestion.tls=true"
- "traefik.http.routers.svc-ingestion.tls.certresolver=godaddy"
- "traefik.http.routers.svc-ingestion.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-ingestion.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-ingestion.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-ingestion.loadbalancer.server.port=8000"
# Data Extraction Service
@@ -73,8 +73,8 @@ services:
- "traefik.http.routers.svc-extract.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/extract`)"
- "traefik.http.routers.svc-extract.entrypoints=websecure"
- "traefik.http.routers.svc-extract.tls=true"
- "traefik.http.routers.svc-extract.tls.certresolver=godaddy"
- "traefik.http.routers.svc-extract.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-extract.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-extract.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-extract.loadbalancer.server.port=8000"
# Knowledge Graph Service
@@ -100,8 +100,8 @@ services:
- "traefik.http.routers.svc-kg.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/kg`)"
- "traefik.http.routers.svc-kg.entrypoints=websecure"
- "traefik.http.routers.svc-kg.tls=true"
- "traefik.http.routers.svc-kg.tls.certresolver=godaddy"
- "traefik.http.routers.svc-kg.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-kg.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-kg.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-kg.loadbalancer.server.port=8000"
# RAG Retrieval Service
@@ -130,8 +130,8 @@ services:
- "traefik.http.routers.svc-rag-retriever.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/rag`)"
- "traefik.http.routers.svc-rag-retriever.entrypoints=websecure"
- "traefik.http.routers.svc-rag-retriever.tls=true"
- "traefik.http.routers.svc-rag-retriever.tls.certresolver=godaddy"
- "traefik.http.routers.svc-rag-retriever.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-rag-retriever.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-rag-retriever.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-rag-retriever.loadbalancer.server.port=8000"
# Forms Service
@@ -163,8 +163,8 @@ services:
- "traefik.http.routers.svc-forms.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/forms`)"
- "traefik.http.routers.svc-forms.entrypoints=websecure"
- "traefik.http.routers.svc-forms.tls=true"
- "traefik.http.routers.svc-forms.tls.certresolver=godaddy"
- "traefik.http.routers.svc-forms.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-forms.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-forms.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-forms.loadbalancer.server.port=8000"
# HMRC Integration Service
@@ -197,8 +197,8 @@ services:
- "traefik.http.routers.svc-hmrc.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/hmrc`)"
- "traefik.http.routers.svc-hmrc.entrypoints=websecure"
- "traefik.http.routers.svc-hmrc.tls=true"
- "traefik.http.routers.svc-hmrc.tls.certresolver=godaddy"
- "traefik.http.routers.svc-hmrc.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-hmrc.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-hmrc.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-hmrc.loadbalancer.server.port=8000"
# OCR Service
@@ -230,8 +230,8 @@ services:
- "traefik.http.routers.svc-ocr.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/ocr`)"
- "traefik.http.routers.svc-ocr.entrypoints=websecure"
- "traefik.http.routers.svc-ocr.tls=true"
- "traefik.http.routers.svc-ocr.tls.certresolver=godaddy"
- "traefik.http.routers.svc-ocr.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-ocr.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-ocr.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-ocr.loadbalancer.server.port=8000"
# RAG Indexer Service
@@ -263,8 +263,8 @@ services:
- "traefik.http.routers.svc-rag-indexer.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/rag-indexer`)"
- "traefik.http.routers.svc-rag-indexer.entrypoints=websecure"
- "traefik.http.routers.svc-rag-indexer.tls=true"
- "traefik.http.routers.svc-rag-indexer.tls.certresolver=godaddy"
- "traefik.http.routers.svc-rag-indexer.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-rag-indexer.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-rag-indexer.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-rag-indexer.loadbalancer.server.port=8000"
# Reasoning Service
@@ -296,8 +296,8 @@ services:
- "traefik.http.routers.svc-reason.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/reason`)"
- "traefik.http.routers.svc-reason.entrypoints=websecure"
- "traefik.http.routers.svc-reason.tls=true"
- "traefik.http.routers.svc-reason.tls.certresolver=godaddy"
- "traefik.http.routers.svc-reason.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-reason.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-reason.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-reason.loadbalancer.server.port=8000"
# RPA Service
@@ -329,8 +329,8 @@ services:
- "traefik.http.routers.svc-rpa.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/rpa`)"
- "traefik.http.routers.svc-rpa.entrypoints=websecure"
- "traefik.http.routers.svc-rpa.tls=true"
- "traefik.http.routers.svc-rpa.tls.certresolver=godaddy"
- "traefik.http.routers.svc-rpa.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-rpa.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-rpa.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-rpa.loadbalancer.server.port=8000"
# Normalize & Map Service
@@ -362,8 +362,8 @@ services:
- "traefik.http.routers.svc-normalize-map.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/normalize-map`)"
- "traefik.http.routers.svc-normalize-map.entrypoints=websecure"
- "traefik.http.routers.svc-normalize-map.tls=true"
- "traefik.http.routers.svc-normalize-map.tls.certresolver=godaddy"
- "traefik.http.routers.svc-normalize-map.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-normalize-map.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-normalize-map.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-normalize-map.loadbalancer.server.port=8000"
# Coverage Service
@@ -395,8 +395,8 @@ services:
- "traefik.http.routers.svc-coverage.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/coverage`)"
- "traefik.http.routers.svc-coverage.entrypoints=websecure"
- "traefik.http.routers.svc-coverage.tls=true"
- "traefik.http.routers.svc-coverage.tls.certresolver=godaddy"
- "traefik.http.routers.svc-coverage.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-coverage.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-coverage.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-coverage.loadbalancer.server.port=8000"
# Firm Connectors Service
@@ -428,8 +428,8 @@ services:
- "traefik.http.routers.svc-firm-connectors.rule=Host(`api.${DOMAIN}`) && PathPrefix(`/firm-connectors`)"
- "traefik.http.routers.svc-firm-connectors.entrypoints=websecure"
- "traefik.http.routers.svc-firm-connectors.tls=true"
- "traefik.http.routers.svc-firm-connectors.tls.certresolver=godaddy"
- "traefik.http.routers.svc-firm-connectors.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.routers.svc-firm-connectors.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.svc-firm-connectors.middlewares=authentik-forwardauth@file,rate-limit@file,strip-api-prefixes@file"
- "traefik.http.services.svc-firm-connectors.loadbalancer.server.port=8000"
# Review UI
@@ -448,6 +448,6 @@ services:
- "traefik.http.routers.ui-review.rule=Host(`app.${DOMAIN}`)"
- "traefik.http.routers.ui-review.entrypoints=websecure"
- "traefik.http.routers.ui-review.tls=true"
- "traefik.http.routers.ui-review.tls.certresolver=godaddy"
- "traefik.http.routers.ui-review.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
- "traefik.http.routers.ui-review.middlewares=authentik-forwardauth@file"
- "traefik.http.services.ui-review.loadbalancer.server.port=3030"

View File

@@ -1,133 +1,23 @@
# External Services
# Compose Stacks
This directory contains Docker Compose configurations for external services that run on the production server.
This folder is for the self-contained local stack (self-signed TLS) and Traefik assets. Remote environments use the shared compose files in `infra/base` together with `infra/scripts/deploy.sh`.
## Services
## Local development (self-signed TLS)
- Copy envs: `cp infra/compose/env.example infra/compose/.env` then set passwords/secrets and the dev domain (defaults to `local.lan`).
- Host aliases: add the domain to `/etc/hosts` (e.g. `127.0.0.1 auth.local.lan api.local.lan grafana.local.lan vault.local.lan minio.local.lan`).
- Networks: `./infra/scripts/setup-networks.sh` (creates `apa-frontend` and `apa-backend` used everywhere).
- Run: `cd infra/compose && docker compose --env-file .env -f docker-compose.local.yml up -d`.
- Stop: `docker compose --env-file .env -f docker-compose.local.yml down`.
- TLS: Traefik mounts `infra/compose/traefik/certs/local.{crt,key}`. Regenerate if needed with `openssl req -x509 -newkey rsa:2048 -nodes -keyout infra/compose/traefik/certs/local.key -out infra/compose/traefik/certs/local.crt -days 365 -subj "/CN=*.local.lan"`.
### Traefik
- **Location**: `traefik/`
- **Purpose**: Reverse proxy and load balancer for all services
- **Deploy**: `cd traefik && docker compose up -d`
- **Access**: https://traefik.harkon.co.uk
## Cloud / remote (Lets Encrypt)
- Config lives in `infra/base` with env files in `infra/environments/{development,production}/.env`.
- Create the same docker networks on the host (`./infra/scripts/setup-networks.sh`) so Traefik and services share `apa-frontend` / `apa-backend`.
- Deploy on the server: `./infra/scripts/deploy.sh <environment> all` (or `infrastructure`, `monitoring`, `services`).
- Certificates: Traefik uses DNS-01 via GoDaddy from the provider env in `infra/base/traefik/config` (make sure `DOMAIN`, ACME email, and provider creds are set in the env file).
### Authentik
- **Location**: `authentik/`
- **Purpose**: SSO and authentication provider
- **Deploy**: `cd authentik && docker compose up -d`
- **Access**: https://authentik.harkon.co.uk
### Gitea
- **Location**: `gitea/`
- **Purpose**: Git repository hosting and container registry
- **Deploy**: `cd gitea && docker compose up -d`
- **Access**: https://gitea.harkon.co.uk
### Nextcloud
- **Location**: `nextcloud/`
- **Purpose**: File storage and collaboration
- **Deploy**: `cd nextcloud && docker compose up -d`
- **Access**: https://nextcloud.harkon.co.uk
### Portainer
- **Location**: `portainer/`
- **Purpose**: Docker management UI
- **Deploy**: `cd portainer && docker compose up -d`
- **Access**: https://portainer.harkon.co.uk
## Deployment
### Production (Remote Server)
```bash
# SSH to server
ssh deploy@141.136.35.199
# Navigate to service directory
cd /opt/ai-tax-agent/infra/compose/<service>
# Deploy service
docker compose up -d
# Check logs
docker compose logs -f
# Check status
docker compose ps
```
### Local Development
For local development, use the all-in-one compose file:
```bash
cd infra/compose
docker compose -f docker-compose.local.yml up -d
```
## Configuration
Each service has its own `.env` file for environment-specific configuration:
- `traefik/.provider.env` - GoDaddy API credentials
- `authentik/.env` - Authentik secrets
- `gitea/.env` - Gitea database credentials
## Networks
All services use shared Docker networks:
- `frontend` - Public-facing services
- `backend` - Internal services
Create networks before deploying:
```bash
docker network create frontend
docker network create backend
```
## Maintenance
### Update Service
```bash
cd /opt/ai-tax-agent/infra/compose/<service>
docker compose pull
docker compose up -d
```
### Restart Service
```bash
cd /opt/ai-tax-agent/infra/compose/<service>
docker compose restart
```
### View Logs
```bash
cd /opt/ai-tax-agent/infra/compose/<service>
docker compose logs -f
```
### Backup Data
```bash
# Backup volumes
docker run --rm -v <service>_data:/data -v $(pwd):/backup alpine tar czf /backup/<service>-backup.tar.gz /data
```
## Integration with Application
These external services are used by the application infrastructure:
- **Traefik** - Routes traffic to application services
- **Authentik** - Provides SSO for application UIs
- **Gitea** - Hosts Docker images for application services
The application infrastructure is deployed separately using:
```bash
./infra/scripts/deploy.sh production infrastructure
./infra/scripts/deploy.sh production services
```
## Files of note
- `docker-compose.local.yml` full local stack.
- `traefik/traefik.local.yml` and `traefik/traefik-dynamic.local.yml` static/dynamic Traefik config for local.
- `traefik/certs/` self-signed certs used by the local proxy.
- `env.example` defaults for local `.env`.

View File

@@ -0,0 +1,156 @@
# FILE: infra/compose/compose.override.yaml
# Local development overrides
# Automatically loaded by docker compose when compose.yaml is present
services:
# --- Infrastructure Overrides ---
apa-traefik:
volumes:
- ./traefik/traefik.local.yml:/etc/traefik/traefik.yml:ro
- ./traefik/traefik-dynamic.local.yml:/etc/traefik/traefik-dynamic.yml:ro
- ./traefik/certs/:/var/traefik/certs/:ro
ports:
- "8080:8080" # Dashboard (admin entrypoint, insecure mode only for local)
apa-authentik-server:
environment:
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
DOMAIN: ${DOMAIN:-local.lan}
GRAFANA_OAUTH_CLIENT_ID: ${GRAFANA_OAUTH_CLIENT_ID}
GRAFANA_OAUTH_CLIENT_SECRET: ${GRAFANA_OAUTH_CLIENT_SECRET}
AUTHENTIK_MINIO_CLIENT_SECRET: ${AUTHENTIK_MINIO_CLIENT_SECRET}
AUTHENTIK_VAULT_CLIENT_SECRET: ${AUTHENTIK_VAULT_CLIENT_SECRET}
AUTHENTIK_OUTPOST_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
volumes:
- ../authentik/bootstrap.yaml:/blueprints/ai-tax-agent-bootstrap.yaml:ro
apa-authentik-worker:
environment:
DOMAIN: ${DOMAIN:-local.lan}
GRAFANA_OAUTH_CLIENT_ID: ${GRAFANA_OAUTH_CLIENT_ID}
GRAFANA_OAUTH_CLIENT_SECRET: ${GRAFANA_OAUTH_CLIENT_SECRET}
AUTHENTIK_MINIO_CLIENT_SECRET: ${AUTHENTIK_MINIO_CLIENT_SECRET}
AUTHENTIK_VAULT_CLIENT_SECRET: ${AUTHENTIK_VAULT_CLIENT_SECRET}
AUTHENTIK_OUTPOST_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN}
volumes:
- ../authentik/bootstrap.yaml:/blueprints/ai-tax-agent-bootstrap.yaml:ro
apa-vault:
volumes:
- ./traefik/certs/:/certs:ro
# --- Service Build Overrides ---
# Pointing to local source code for building
apa-svc-ingestion:
build:
context: ../../
dockerfile: apps/svc_ingestion/Dockerfile
image: ai-tax-agent/svc-ingestion:local
pull_policy: never
apa-svc-extract:
build:
context: ../../
dockerfile: apps/svc_extract/Dockerfile
image: ai-tax-agent/svc-extract:local
pull_policy: never
apa-svc-kg:
build:
context: ../../
dockerfile: apps/svc_kg/Dockerfile
image: ai-tax-agent/svc-kg:local
pull_policy: never
apa-svc-rag-retriever:
build:
context: ../../
dockerfile: apps/svc_rag_retriever/Dockerfile
image: ai-tax-agent/svc-rag-retriever:local
pull_policy: never
apa-svc-forms:
build:
context: ../../
dockerfile: apps/svc_forms/Dockerfile
image: ai-tax-agent/svc-forms:local
pull_policy: never
apa-svc-hmrc:
build:
context: ../../
dockerfile: apps/svc_hmrc/Dockerfile
image: ai-tax-agent/svc-hmrc:local
pull_policy: never
apa-svc-ocr:
build:
context: ../../
dockerfile: apps/svc_ocr/Dockerfile
image: ai-tax-agent/svc-ocr:local
pull_policy: never
restart: on-failure
apa-svc-rag-indexer:
build:
context: ../../
dockerfile: apps/svc_rag_indexer/Dockerfile
image: ai-tax-agent/svc-rag-indexer:local
pull_policy: never
apa-svc-reason:
build:
context: ../../
dockerfile: apps/svc_reason/Dockerfile
image: ai-tax-agent/svc-reason:local
pull_policy: never
apa-svc-rpa:
build:
context: ../../
dockerfile: apps/svc_rpa/Dockerfile
image: ai-tax-agent/svc-rpa:local
pull_policy: never
apa-svc-normalize-map:
build:
context: ../../
dockerfile: apps/svc_normalize_map/Dockerfile
image: ai-tax-agent/svc-normalize-map:local
pull_policy: never
apa-svc-coverage:
build:
context: ../../
dockerfile: apps/svc_coverage/Dockerfile
image: ai-tax-agent/svc-coverage:local
pull_policy: never
apa-svc-firm-connectors:
build:
context: ../../
dockerfile: apps/svc_firm_connectors/Dockerfile
image: ai-tax-agent/svc-firm-connectors:local
pull_policy: never
apa-ui-review:
# UI might not have a Dockerfile in root/ui-review/Dockerfile based on previous file view
# Assuming standard build context if it exists, otherwise comment out build
# build:
# context: ../../ui-review
# dockerfile: Dockerfile
image: alpine:latest
profiles: ["disabled"]
environment:
- NEXTAUTH_URL=https://app.local.lan
- API_BASE_URL=https://api.local.lan
apa-minio:
volumes:
- ./traefik/certs/local.crt:/root/.minio/certs/CAs/local.crt:ro
# --- Local Development Specific Services ---
# Services that only exist in local dev (e.g. mailhog if used, or specific tools)
# None identified from docker-compose.local.yml that aren't in base

View File

@@ -0,0 +1,14 @@
# FILE: infra/compose/compose.yaml
# Main entry point for Docker Compose
# Includes base configurations from infra/base/
include:
- ../base/infrastructure.yaml
- ../base/services.yaml
# Monitoring stack is optional for local dev but included for completeness
# Can be disabled via profiles if needed, but keeping simple for now
- ../base/monitoring.yaml
# Define project name to match existing convention if needed,
# though 'compose' directory name usually defaults to 'compose'
name: ai-tax-agent

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
# FILE: infra/compose/env.example
# Domain Configuration
DOMAIN=local
DOMAIN=local.lan
EMAIL=admin@local.lan
# Database Passwords
@@ -26,6 +26,7 @@ AUTHENTIK_SECRET_KEY=changeme
AUTHENTIK_OUTPOST_TOKEN=changeme
AUTHENTIK_BOOTSTRAP_EMAIL=admin@local.lan
AUTHENTIK_BOOTSTRAP_PASSWORD=admin123
# AUTHENTIK_BOOTSTRAP_TOKEN: This value will be automatically updated after the initial setup.
AUTHENTIK_BOOTSTRAP_TOKEN=
# Monitoring
@@ -80,7 +81,7 @@ PII_LOG_RETENTION_DAYS=30
# Backup & DR
BACKUP_ENABLED=true
BACKUP_SCHEDULE=0 2 * * *
BACKUP_SCHEDULE="0 2 * * *"
BACKUP_RETENTION_DAYS=30
# Performance Tuning

View File

@@ -0,0 +1,89 @@
http:
middlewares:
authentik-forwardauth:
forwardAuth:
address: "http://apa-authentik-outpost:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
# Large upload middleware for Gitea registry
gitea-large-upload:
buffering:
maxRequestBodyBytes: 5368709120 # 5GB
memRequestBodyBytes: 104857600 # 100MB
maxResponseBodyBytes: 5368709120 # 5GB
memResponseBodyBytes: 104857600 # 100MB
retryExpression: "IsNetworkError() && Attempts() < 3"
# Rate limiting for public APIs
rate-limit:
rateLimit:
average: 100
burst: 50
period: 1s
# Security headers
security-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
# CORS headers
api-cors:
headers:
accessControlAllowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
accessControlAllowOriginList:
- "https://app.harkon.co.uk"
accessControlAllowHeaders:
- "Content-Type"
- "Authorization"
accessControlMaxAge: 100
addVaryHeader: true
# Strip API prefixes
strip-api-prefixes:
stripPrefix:
prefixes:
- "/rag-indexer"
- "/firm-connectors"
- "/normalize-map"
- "/ingestion"
- "/extract"
- "/forms"
- "/hmrc"
- "/ocr"
- "/reason"
- "/rpa"
- "/coverage"
- "/kg"
- "/rag"
tls:
certificates:
- certFile: /var/traefik/certs/local.crt
keyFile: /var/traefik/certs/local.key
options:
default:
minVersion: VersionTLS12
sniStrict: false

View File

@@ -0,0 +1,35 @@
# Traefik static configuration for local development (self-signed TLS)
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
tls:
options: default
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: "apa-frontend"
file:
filename: "/etc/traefik/traefik-dynamic.yml"
watch: true
api:
dashboard: true
insecure: true
serversTransport:
insecureSkipVerify: true
log:
level: INFO
accessLog: {}

8
infra/postgres/init/unleash.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER unleash WITH PASSWORD '${UNLEASH_DB_PASSWORD:-unleash}';
CREATE DATABASE unleash;
GRANT ALL PRIVILEGES ON DATABASE unleash TO unleash;
EOSQL

View File

@@ -112,6 +112,18 @@ echo ""
compose_cmd() {
local file=$1
shift
# For local environment, use the new unified compose.yaml
if [ "$ENVIRONMENT" = "local" ] && [ "$file" = "all" ]; then
docker compose -f "$INFRA_DIR/compose/compose.yaml" -f "$INFRA_DIR/compose/compose.override.yaml" --env-file "$ENV_FILE" --project-name "ai-tax-agent" "$@"
return
fi
# For other environments or specific stacks, keep existing behavior for now
# or adapt as needed. The goal is to eventually unify everything.
# If file is 'infrastructure.yaml', etc., we might still want to use base/
# directly for production to avoid local overrides.
docker compose -f "$BASE_DIR/$file" --env-file "$ENV_FILE" --project-name "ai-tax-agent-$ENVIRONMENT" "$@"
}
@@ -139,7 +151,7 @@ deploy_services() {
# Deploy external services stack
deploy_external() {
log_info "Deploying external services stack..."
if [ "$ENVIRONMENT" = "production" ] || [ "$ENVIRONMENT" = "development" ]; then
log_warning "External services (Traefik, Authentik, Gitea) may already exist on this server"
read -p "Do you want to deploy external services? (y/N) " -n 1 -r
@@ -149,7 +161,7 @@ deploy_external() {
return
fi
fi
compose_cmd "external.yaml" up -d "$@"
log_success "External services stack deployed"
}
@@ -157,50 +169,55 @@ deploy_external() {
# Stop all stacks
stop_all() {
log_info "Stopping all stacks..."
if [ -f "$BASE_DIR/services.yaml" ]; then
compose_cmd "services.yaml" down
fi
if [ -f "$BASE_DIR/monitoring.yaml" ]; then
compose_cmd "monitoring.yaml" down
fi
if [ -f "$BASE_DIR/infrastructure.yaml" ]; then
compose_cmd "infrastructure.yaml" down
fi
if [ -f "$BASE_DIR/external.yaml" ]; then
log_warning "External services not stopped (may be shared)"
fi
log_success "All stacks stopped"
}
# Deploy all stacks
deploy_all() {
log_info "Deploying all stacks..."
# Check if networks exist
if ! docker network inspect apa-frontend >/dev/null 2>&1; then
log_warning "Network 'apa-frontend' does not exist. Creating..."
docker network create apa-frontend
fi
if ! docker network inspect apa-backend >/dev/null 2>&1; then
log_warning "Network 'apa-backend' does not exist. Creating..."
docker network create apa-backend
fi
# Deploy in order
deploy_infrastructure "$@"
sleep 5
deploy_monitoring "$@"
sleep 5
deploy_services "$@"
if [ "$ENVIRONMENT" = "local" ]; then
log_info "Deploying unified stack for local environment..."
compose_cmd "all" up -d "$@"
else
deploy_infrastructure "$@"
sleep 5
deploy_monitoring "$@"
sleep 5
deploy_services "$@"
fi
log_success "All stacks deployed successfully!"
echo ""
log_info "Access your services:"

View File

@@ -0,0 +1,16 @@
{
"godaddy": {
"Account": {
"Email": "info@harkon.co.uk",
"Registration": {
"body": {
"status": "valid"
},
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/2826907666"
},
"PrivateKey": "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",
"KeyType": "4096"
},
"Certificates": null
}
}

View File

@@ -0,0 +1,64 @@
http:
middlewares:
authentik-forwardauth:
forwardAuth:
address: "http://apa-authentik-outpost:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
# Large upload middleware for Gitea registry
gitea-large-upload:
buffering:
maxRequestBodyBytes: 5368709120 # 5GB
memRequestBodyBytes: 104857600 # 100MB
maxResponseBodyBytes: 5368709120 # 5GB
memResponseBodyBytes: 104857600 # 100MB
retryExpression: "IsNetworkError() && Attempts() < 3"
# Rate limiting for public APIs
api-ratelimit:
rateLimit:
average: 100
burst: 50
period: 1s
# Security headers
security-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
# CORS headers
api-cors:
headers:
accessControlAllowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
accessControlAllowOriginList:
- "https://app.harkon.co.uk"
accessControlAllowHeaders:
- "Content-Type"
- "Authorization"
accessControlMaxAge: 100
addVaryHeader: true
# Security headers

View File

@@ -0,0 +1,35 @@
# Static Traefik configuration (production)
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: 30m
api:
dashboard: true
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: "apa-frontend"
file:
filename: "/etc/traefik/traefik-dynamic.yml"
watch: true
# -- Configure your CertificateResolver here...
certificatesResolvers:
godaddy:
acme:
email: info@harkon.co.uk
storage: /var/traefik/certs/godaddy-acme.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
dnsChallenge:
provider: godaddy
resolvers:
- 1.1.1.1:53
- 8.8.8.8:53
- 97.74.103.44:53
- 173.201.71.44:53