Initial commit
Some checks failed
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 / 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 / Notifications (push) Has been cancelled

This commit is contained in:
harkon
2025-10-11 08:41:36 +01:00
commit b324ff09ef
276 changed files with 55220 additions and 0 deletions

37
infra/.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
# Environment files (contain secrets)
environments/*/.env
!environments/*/.env.example
compose/*/.env
!compose/env.example
# Certificates
certs/*/
!certs/.gitkeep
compose/*/certs/
!compose/*/certs/.gitkeep
# Provider credentials
compose/traefik/.provider.env
configs/traefik/.provider.env
# Data directories
compose/*/data/
compose/*/media/
compose/authentik/media/
compose/authentik/custom-templates/
compose/portainer/portainer/
# Backup files
*.backup
*.tmp
*-backup-*/
# Docker volumes (if mounted locally)
volumes/
# Logs
*.log
logs/
# Moved markers
**/.moved

541
infra/DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,541 @@
# AI Tax Agent Infrastructure Deployment Guide
Complete guide for deploying AI Tax Agent infrastructure across all environments.
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Quick Start](#quick-start)
3. [Local Development](#local-development)
4. [Development Server](#development-server)
5. [Production Server](#production-server)
6. [Troubleshooting](#troubleshooting)
---
## Prerequisites
### Required Software
- Docker 24.0+ with Compose V2
- Git
- SSH access (for remote deployments)
- Domain with DNS access (for dev/prod)
### Required Accounts
- GoDaddy account (for DNS-01 challenge)
- Gitea account (for container registry)
- OpenAI/Anthropic API keys (optional)
### Network Requirements
- Ports 80, 443 open (for Traefik)
- Docker networks: `frontend`, `backend`
---
## Quick Start
### 1. Clone Repository
```bash
git clone <repository-url>
cd ai-tax-agent
```
### 2. Choose Environment
```bash
# Local development
export ENV=local
# Development server
export ENV=development
# Production server
export ENV=production
```
### 3. Setup Environment File
```bash
# Copy template
cp infra/environments/$ENV/.env.example infra/environments/$ENV/.env
# Edit configuration
vim infra/environments/$ENV/.env
```
### 4. Generate Secrets (Dev/Prod only)
```bash
./scripts/generate-production-secrets.sh
```
### 5. Deploy
```bash
# Setup networks
./infra/scripts/setup-networks.sh
# Deploy all services
./infra/scripts/deploy.sh $ENV all
```
---
## Local Development
### Setup
1. **Create environment file**:
```bash
cp infra/environments/local/.env.example infra/environments/local/.env
```
2. **Edit configuration**:
```bash
vim infra/environments/local/.env
```
Key settings for local:
```env
DOMAIN=localhost
POSTGRES_PASSWORD=postgres
MINIO_ROOT_PASSWORD=minioadmin
GRAFANA_PASSWORD=admin
```
3. **Generate self-signed certificates** (optional):
```bash
./scripts/generate-dev-certs.sh
```
### Deploy
```bash
# Setup networks
./infra/scripts/setup-networks.sh
# Deploy infrastructure
./infra/scripts/deploy.sh local infrastructure
# Deploy monitoring
./infra/scripts/deploy.sh local monitoring
# Deploy services
./infra/scripts/deploy.sh local services
```
### Access Services
- **Grafana**: http://localhost:3000 (admin/admin)
- **MinIO Console**: http://localhost:9093 (minioadmin/minioadmin)
- **Vault**: http://localhost:8200 (token: dev-root-token)
- **Traefik Dashboard**: http://localhost:8080
### Development Workflow
1. Make code changes
2. Build images: `./scripts/build-and-push-images.sh localhost:5000 latest local`
3. Restart services: `./infra/scripts/deploy.sh local services`
4. Test changes
5. Check logs: `docker compose -f infra/base/services.yaml --env-file infra/environments/local/.env logs -f`
---
## Development Server
### Prerequisites
- Server with Docker installed
- Domain: `dev.harkon.co.uk`
- GoDaddy API credentials
- SSH access to server
### Setup
1. **SSH to development server**:
```bash
ssh deploy@dev-server.harkon.co.uk
```
2. **Clone repository**:
```bash
cd /opt
git clone <repository-url> ai-tax-agent
cd ai-tax-agent
```
3. **Create environment file**:
```bash
cp infra/environments/development/.env.example infra/environments/development/.env
```
4. **Generate secrets**:
```bash
./scripts/generate-production-secrets.sh
```
5. **Edit environment file**:
```bash
vim infra/environments/development/.env
```
Update:
- `DOMAIN=dev.harkon.co.uk`
- `EMAIL=dev@harkon.co.uk`
- API keys
- Registry credentials
6. **Setup GoDaddy DNS**:
```bash
# Create Traefik provider file
vim infra/configs/traefik/.provider.env
```
Add:
```env
GODADDY_API_KEY=your-api-key
GODADDY_API_SECRET=your-api-secret
```
### Deploy
```bash
# Setup networks
./infra/scripts/setup-networks.sh
# Deploy infrastructure
./infra/scripts/deploy.sh development infrastructure
# Wait for services to be healthy
sleep 30
# Deploy monitoring
./infra/scripts/deploy.sh development monitoring
# Deploy services
./infra/scripts/deploy.sh development services
```
### Verify Deployment
```bash
# Check services
docker ps
# Check logs
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/development/.env logs -f
# Test endpoints
curl https://vault.dev.harkon.co.uk
curl https://grafana.dev.harkon.co.uk
```
### Access Services
- **Grafana**: https://grafana.dev.harkon.co.uk
- **MinIO**: https://minio.dev.harkon.co.uk
- **Vault**: https://vault.dev.harkon.co.uk
- **UI Review**: https://ui-review.dev.harkon.co.uk
---
## Production Server
### Prerequisites
- Production server (141.136.35.199)
- Domain: `harkon.co.uk`
- Existing Traefik, Authentik, Gitea
- SSH access as `deploy` user
### Pre-Deployment Checklist
- [ ] Backup existing data
- [ ] Test in development first
- [ ] Generate production secrets
- [ ] Update DNS records
- [ ] Configure Authentik OAuth providers
- [ ] Setup Gitea container registry
- [ ] Build and push Docker images
### Setup
1. **SSH to production server**:
```bash
ssh deploy@141.136.35.199
```
2. **Navigate to project**:
```bash
cd /opt/ai-tax-agent
git pull origin main
```
3. **Verify environment file**:
```bash
cat infra/environments/production/.env | grep DOMAIN
```
Should show:
```env
DOMAIN=harkon.co.uk
```
4. **Verify secrets are set**:
```bash
# Check all secrets are not CHANGE_ME
grep -i "CHANGE_ME" infra/environments/production/.env
```
Should return nothing.
### Deploy Infrastructure
```bash
# Setup networks (if not already created)
./infra/scripts/setup-networks.sh
# Deploy infrastructure services
./infra/scripts/deploy.sh production infrastructure
```
This deploys:
- Vault (secrets management)
- MinIO (object storage)
- PostgreSQL (relational database)
- Neo4j (graph database)
- Qdrant (vector database)
- Redis (cache)
- NATS (message queue)
### Deploy Monitoring
```bash
./infra/scripts/deploy.sh production monitoring
```
This deploys:
- Prometheus (metrics)
- Grafana (dashboards)
- Loki (logs)
- Promtail (log collector)
### Deploy Services
```bash
./infra/scripts/deploy.sh production services
```
This deploys all 14 microservices.
### Post-Deployment
1. **Verify all services are running**:
```bash
docker ps | grep ai-tax-agent
```
2. **Check health**:
```bash
curl https://vault.harkon.co.uk/v1/sys/health
curl https://minio-api.harkon.co.uk/minio/health/live
```
3. **Configure Authentik OAuth**:
- Create OAuth providers for each service
- Update environment variables with client secrets
- Restart services
4. **Initialize Vault**:
```bash
# Access Vault
docker exec -it vault sh
# Initialize (if first time)
vault operator init
# Unseal (if needed)
vault operator unseal
```
5. **Setup MinIO buckets**:
```bash
# Access MinIO console
# https://minio.harkon.co.uk
# Create buckets:
# - documents
# - embeddings
# - models
# - backups
```
### Access Services
All services available at `https://<service>.harkon.co.uk`:
- **UI Review**: https://ui-review.harkon.co.uk
- **Grafana**: https://grafana.harkon.co.uk
- **Prometheus**: https://prometheus.harkon.co.uk
- **Vault**: https://vault.harkon.co.uk
- **MinIO**: https://minio.harkon.co.uk
---
## Troubleshooting
### Services Not Starting
```bash
# Check logs
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env logs -f
# Check specific service
docker logs vault
# Check Docker daemon
sudo systemctl status docker
```
### Network Issues
```bash
# Check networks exist
docker network ls | grep -E "frontend|backend"
# Inspect network
docker network inspect frontend
# Recreate networks
docker network rm frontend backend
./infra/scripts/setup-networks.sh
```
### Traefik Routing Issues
```bash
# Check Traefik logs
docker logs traefik | grep -i error
# Check container labels
docker inspect vault | grep -A 20 Labels
# Check Traefik dashboard
https://traefik.harkon.co.uk/dashboard/
```
### Database Connection Issues
```bash
# Check PostgreSQL
docker exec -it postgres psql -U postgres -c "\l"
# Check Neo4j
docker exec -it neo4j cypher-shell -u neo4j -p $NEO4J_PASSWORD
# Check Redis
docker exec -it redis redis-cli ping
```
### Volume/Data Issues
```bash
# List volumes
docker volume ls
# Inspect volume
docker volume inspect postgres_data
# Backup volume
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data
```
### SSL Certificate Issues
```bash
# Check Traefik logs for ACME errors
docker logs traefik | grep -i acme
# Check GoDaddy credentials
cat infra/configs/traefik/.provider.env
# Force certificate renewal
docker exec traefik rm -rf /var/traefik/certs/acme.json
docker restart traefik
```
---
## Maintenance
### Update Services
```bash
# Pull latest code
git pull origin main
# Rebuild images
./scripts/build-and-push-images.sh gitea.harkon.co.uk v1.0.2 harkon
# Deploy updates
./infra/scripts/deploy.sh production services --pull
```
### Backup Data
```bash
# Backup all volumes
./scripts/backup-volumes.sh production
# Backup specific service
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data
```
### Scale Services
```bash
# Scale a service
docker compose -f infra/base/services.yaml --env-file infra/environments/production/.env up -d --scale svc-ingestion=3
```
### View Logs
```bash
# All services
docker compose -f infra/base/services.yaml --env-file infra/environments/production/.env logs -f
# Specific service
docker logs -f svc-ingestion
# With Loki (via Grafana)
https://grafana.harkon.co.uk/explore
```
---
## Security Best Practices
1. **Rotate secrets regularly** - Use `generate-production-secrets.sh`
2. **Use Authentik SSO** - Enable for all services
3. **Keep images updated** - Regular security patches
4. **Monitor logs** - Check for suspicious activity
5. **Backup regularly** - Automated daily backups
6. **Use strong passwords** - Minimum 32 characters
7. **Limit network exposure** - Only expose necessary ports
8. **Enable audit logging** - Track all access
---
## Support
For issues:
1. Check logs
2. Review documentation
3. Check Traefik dashboard
4. Verify environment variables
5. Test in development first

415
infra/FINAL_STRUCTURE.md Normal file
View File

@@ -0,0 +1,415 @@
# AI Tax Agent Infrastructure - Final Structure
## Overview
The infrastructure is organized into two main categories:
1. **External Services** - Production-only services deployed individually
2. **Application Infrastructure** - Multi-environment services for the application
---
## Directory Structure
```
ai-tax-agent/
├── infra/
│ ├── compose/ # External services (production)
│ │ ├── traefik/ # Reverse proxy
│ │ │ ├── compose.yaml
│ │ │ ├── config/ # Traefik configuration (source of truth)
│ │ │ ├── certs/
│ │ │ └── .provider.env
│ │ ├── authentik/ # SSO provider
│ │ │ ├── compose.yaml
│ │ │ ├── .env
│ │ │ ├── media/
│ │ │ └── custom-templates/
│ │ ├── gitea/ # Git + Container Registry
│ │ │ ├── compose.yaml
│ │ │ └── .env
│ │ ├── nextcloud/ # File storage
│ │ │ └── compose.yaml
│ │ ├── portainer/ # Docker management
│ │ │ └── docker-compose.yaml
│ │ ├── docker-compose.local.yml # Local dev (all-in-one)
│ │ ├── docker-compose.backend.yml # Backend services
│ │ └── README.md
│ │
│ ├── base/ # Application infrastructure (multi-env)
│ │ ├── infrastructure.yaml # Core services (Vault, MinIO, DBs, etc.)
│ │ ├── services.yaml # Application microservices (14 services)
│ │ └── monitoring.yaml # Monitoring stack (Prometheus, Grafana, Loki)
│ │
│ ├── environments/ # Environment-specific configs
│ │ ├── local/
│ │ │ ├── .env.example
│ │ │ └── .env # Local development config
│ │ ├── development/
│ │ │ ├── .env.example
│ │ │ └── .env # Development server config
│ │ └── production/
│ │ ├── .env.example
│ │ └── .env # Production server config
│ │
│ ├── configs/ # Application service configs
│ │ ├── traefik/
│ │ │ └── app-middlewares.yml # App-specific Traefik middlewares
│ │ ├── authentik/
│ │ │ └── bootstrap.yaml # App-specific Authentik bootstrap
│ │ ├── grafana/
│ │ │ ├── dashboards/
│ │ │ └── provisioning/
│ │ ├── prometheus/
│ │ │ └── prometheus.yml
│ │ ├── loki/
│ │ │ └── loki-config.yml
│ │ └── vault/
│ │ └── config/
│ │
│ ├── docker/ # Dockerfile templates
│ │ ├── base-runtime.Dockerfile
│ │ ├── base-ml.Dockerfile
│ │ └── Dockerfile.ml-service.template
│ │
│ ├── certs/ # SSL certificates
│ │ ├── local/
│ │ ├── development/
│ │ └── production/
│ │
│ ├── scripts/ # Infrastructure deployment scripts
│ │ ├── deploy.sh # Deploy application infrastructure
│ │ ├── setup-networks.sh # Create Docker networks
│ │ └── reorganize-structure.sh
│ │
│ ├── README.md # Main infrastructure docs
│ ├── QUICK_START.md # Quick start guide
│ ├── DEPLOYMENT_GUIDE.md # Complete deployment guide
│ ├── MIGRATION_GUIDE.md # Migration from old structure
│ ├── STRUCTURE_OVERVIEW.md # Architecture overview
│ ├── STRUCTURE_CLEANUP.md # Cleanup plan
│ └── FINAL_STRUCTURE.md # This file
├── scripts/ # Project-wide scripts
│ ├── deploy-external.sh # Deploy external services
│ ├── cleanup-infra-structure.sh # Cleanup and align structure
│ ├── build-and-push-images.sh # Build and push Docker images
│ ├── generate-secrets.sh # Generate secrets
│ └── ...
└── Makefile # Project commands
```
---
## Deployment Workflows
### 1. Local Development
```bash
# Option A: Use Makefile (recommended)
make bootstrap
make run
# Option B: Use compose directly
cd infra/compose
docker compose -f docker-compose.local.yml up -d
# Option C: Use new multi-env structure
cp infra/environments/local/.env.example infra/environments/local/.env
./infra/scripts/setup-networks.sh
./infra/scripts/deploy.sh local all
```
### 2. Production - External Services
Deploy individually on remote server:
```bash
# SSH to server
ssh deploy@141.136.35.199
# Deploy all external services
cd /opt/ai-tax-agent
./scripts/deploy-external.sh all
# Or deploy individually
cd /opt/ai-tax-agent/infra/compose/traefik
docker compose up -d
cd /opt/ai-tax-agent/infra/compose/authentik
docker compose up -d
cd /opt/ai-tax-agent/infra/compose/gitea
docker compose up -d
```
### 3. Production - Application Infrastructure
```bash
# SSH to server
ssh deploy@141.136.35.199
cd /opt/ai-tax-agent
# Deploy infrastructure
./infra/scripts/deploy.sh production infrastructure
# Deploy monitoring
./infra/scripts/deploy.sh production monitoring
# Deploy services
./infra/scripts/deploy.sh production services
# Or use Makefile
make deploy-infra-prod
make deploy-monitoring-prod
make deploy-services-prod
```
---
## Makefile Commands
### Local Development
```bash
make bootstrap # Setup development environment
make run # Start all services (local)
make stop # Stop all services
make restart # Restart all services
make logs # Show logs from all services
make status # Show status of all services
make health # Check health of all services
```
### External Services (Production)
```bash
make deploy-external # Deploy all external services
make deploy-traefik # Deploy Traefik only
make deploy-authentik # Deploy Authentik only
make deploy-gitea # Deploy Gitea only
make deploy-nextcloud # Deploy Nextcloud only
make deploy-portainer # Deploy Portainer only
```
### Application Infrastructure (Multi-Environment)
```bash
# Local
make deploy-infra-local
make deploy-services-local
make deploy-monitoring-local
# Development
make deploy-infra-dev
make deploy-services-dev
make deploy-monitoring-dev
# Production
make deploy-infra-prod
make deploy-services-prod
make deploy-monitoring-prod
```
### Development Tools
```bash
make test # Run all tests
make lint # Run linting
make format # Format code
make build # Build Docker images
make clean # Clean up containers and volumes
```
---
## Configuration Management
### External Services
Each external service has its own configuration:
- **Traefik**: `infra/compose/traefik/config/` (source of truth)
- **Authentik**: `infra/compose/authentik/.env`
- **Gitea**: `infra/compose/gitea/.env`
### Application Infrastructure
Application-specific configurations:
- **Environment Variables**: `infra/environments/<env>/.env`
- **Traefik Middlewares**: `infra/configs/traefik/app-middlewares.yml`
- **Authentik Bootstrap**: `infra/configs/authentik/bootstrap.yaml`
- **Grafana Dashboards**: `infra/configs/grafana/dashboards/`
- **Prometheus Config**: `infra/configs/prometheus/prometheus.yml`
---
## Key Differences
### External Services vs Application Infrastructure
| Aspect | External Services | Application Infrastructure |
|--------|------------------|---------------------------|
| **Location** | `infra/compose/` | `infra/base/` + `infra/environments/` |
| **Deployment** | Individual compose files | Unified deployment script |
| **Environment** | Production only | Local, Dev, Prod |
| **Purpose** | Shared company services | AI Tax Agent application |
| **Examples** | Traefik, Authentik, Gitea | Vault, MinIO, Microservices |
---
## Networks
All services use two shared Docker networks:
- **frontend**: Public-facing services (connected to Traefik)
- **backend**: Internal services (databases, message queues)
Create networks:
```bash
docker network create frontend
docker network create backend
# Or use script
./infra/scripts/setup-networks.sh
# Or use Makefile
make networks
```
---
## Service Access
### Local Development
- **Grafana**: http://localhost:3000
- **MinIO**: http://localhost:9093
- **Vault**: http://localhost:8200
- **Traefik Dashboard**: http://localhost:8080
### Production
- **Traefik**: https://traefik.harkon.co.uk
- **Authentik**: https://authentik.harkon.co.uk
- **Gitea**: https://gitea.harkon.co.uk
- **Grafana**: https://grafana.harkon.co.uk
- **MinIO**: https://minio.harkon.co.uk
- **Vault**: https://vault.harkon.co.uk
- **UI Review**: https://ui-review.harkon.co.uk
---
## Best Practices
### 1. Configuration Management
- ✅ External service configs live with their compose files
- ✅ Application configs live in `infra/configs/`
- ✅ Environment-specific settings in `.env` files
- ✅ Never commit `.env` files (use `.env.example`)
### 2. Deployment
- ✅ Test in local first
- ✅ Deploy to development before production
- ✅ Deploy external services before application infrastructure
- ✅ Deploy infrastructure before services
### 3. Secrets Management
- ✅ Use `./scripts/generate-secrets.sh` for production
- ✅ Store secrets in `.env` files (gitignored)
- ✅ Use Vault for runtime secrets
- ✅ Rotate secrets regularly
### 4. Monitoring
- ✅ Check logs after deployment
- ✅ Verify health endpoints
- ✅ Monitor Grafana dashboards
- ✅ Set up alerts for production
---
## Troubleshooting
### Services Not Starting
```bash
# Check logs
docker compose logs -f <service>
# Check status
docker ps -a
# Check networks
docker network ls
docker network inspect frontend
```
### Configuration Issues
```bash
# Verify environment file
cat infra/environments/production/.env | grep DOMAIN
# Check compose file syntax
docker compose -f infra/base/infrastructure.yaml config
# Validate Traefik config
docker exec traefik traefik version
```
### Network Issues
```bash
# Recreate networks
docker network rm frontend backend
./infra/scripts/setup-networks.sh
# Check network connectivity
docker exec <service> ping <other-service>
```
---
## Migration from Old Structure
If you have the old structure, run:
```bash
./scripts/cleanup-infra-structure.sh
```
This will:
- Remove duplicate configurations
- Align Traefik configs
- Create app-specific middlewares
- Update .gitignore
- Create documentation
---
## Next Steps
1. ✅ Structure cleaned up and aligned
2. 📖 Read [QUICK_START.md](QUICK_START.md) for quick deployment
3. 📚 Read [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) for detailed instructions
4. 🧪 Test local deployment: `make run`
5. 🚀 Deploy to production: `make deploy-infra-prod`
---
## Support
For issues or questions:
1. Check logs: `make logs`
2. Check health: `make health`
3. Review documentation in `infra/`
4. Check Traefik dashboard for routing issues

312
infra/MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,312 @@
# Infrastructure Migration Guide
This guide helps you migrate from the old infrastructure structure to the new organized multi-environment setup.
## Old Structure vs New Structure
### Old Structure
```
infra/
├── compose/
│ ├── docker-compose.local.yml (1013 lines - everything)
│ ├── docker-compose.backend.yml (1014 lines - everything)
│ ├── authentik/compose.yaml
│ ├── gitea/compose.yaml
│ ├── nextcloud/compose.yaml
│ ├── portainer/docker-compose.yaml
│ └── traefik/compose.yaml
├── production/
│ ├── infrastructure.yaml
│ ├── services.yaml
│ └── monitoring.yaml
├── .env.production
└── various config folders
```
### New Structure
```
infra/
├── base/ # Shared compose files
│ ├── infrastructure.yaml
│ ├── services.yaml
│ ├── monitoring.yaml
│ └── external.yaml
├── environments/ # Environment-specific configs
│ ├── local/.env
│ ├── development/.env
│ └── production/.env
├── configs/ # Service configurations
│ ├── traefik/
│ ├── grafana/
│ ├── prometheus/
│ └── ...
└── scripts/
└── deploy.sh # Unified deployment script
```
## Migration Steps
### Step 1: Backup Current Setup
```bash
# Backup current environment files
cp infra/.env.production infra/.env.production.backup
cp infra/compose/.env infra/compose/.env.backup
# Backup compose files
tar -czf infra-backup-$(date +%Y%m%d).tar.gz infra/
```
### Step 2: Stop Current Services (if migrating live)
```bash
# Stop services (if running)
cd infra/compose
docker compose -f docker-compose.local.yml down
# Or for production
cd infra/production
docker compose -f infrastructure.yaml down
docker compose -f services.yaml down
docker compose -f monitoring.yaml down
```
### Step 3: Create Environment Files
```bash
# For local development
cp infra/environments/local/.env.example infra/environments/local/.env
vim infra/environments/local/.env
# For development server
cp infra/environments/development/.env.example infra/environments/development/.env
vim infra/environments/development/.env
# For production (copy from existing)
cp infra/.env.production infra/environments/production/.env
```
### Step 4: Move Configuration Files
```bash
# Move Traefik configs
cp -r infra/traefik/* infra/configs/traefik/
# Move Grafana configs
cp -r infra/grafana/* infra/configs/grafana/
# Move Prometheus configs
cp -r infra/prometheus/* infra/configs/prometheus/
# Move Loki configs
cp -r infra/loki/* infra/configs/loki/
# Move Vault configs
cp -r infra/vault/* infra/configs/vault/
# Move Authentik configs
cp -r infra/authentik/* infra/configs/authentik/
```
### Step 5: Update Volume Names (if needed)
If you want to preserve existing data, you have two options:
#### Option A: Keep Existing Volumes (Recommended)
The new compose files use the same volume names, so your data will be preserved automatically.
#### Option B: Rename Volumes
If you want environment-specific volume names:
```bash
# List current volumes
docker volume ls
# Rename volumes (example for production)
docker volume create prod_postgres_data
docker run --rm -v postgres_data:/from -v prod_postgres_data:/to alpine sh -c "cd /from && cp -av . /to"
# Repeat for each volume
```
### Step 6: Setup Networks
```bash
# Create Docker networks
./infra/scripts/setup-networks.sh
```
### Step 7: Deploy New Structure
```bash
# For local
./infra/scripts/deploy.sh local all
# For development
./infra/scripts/deploy.sh development all
# For production
./infra/scripts/deploy.sh production all
```
### Step 8: Verify Services
```bash
# Check running services
docker ps
# Check logs
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env logs -f
# Test endpoints
curl https://vault.harkon.co.uk
curl https://minio.harkon.co.uk
curl https://grafana.harkon.co.uk
```
## Handling External Services
If you have existing Traefik, Authentik, Gitea, Nextcloud, or Portainer:
### Option 1: Keep Existing (Recommended for Production)
Don't deploy `external.yaml`. Just ensure:
1. Networks are shared:
```yaml
networks:
frontend:
external: true
backend:
external: true
```
2. Services can discover each other via network
### Option 2: Migrate to New Structure
1. Stop existing services
2. Update their compose files to use new structure
3. Deploy via `external.yaml`
## Environment-Specific Differences
### Local Development
- Uses `localhost` or `*.local.harkon.co.uk`
- Self-signed SSL certificates
- Simple passwords
- Optional Authentik
- Traefik dashboard exposed on port 8080
### Development Server
- Uses `*.dev.harkon.co.uk`
- Let's Encrypt SSL via DNS-01 challenge
- Strong passwords (generated)
- Authentik SSO enabled
- Gitea container registry
### Production Server
- Uses `*.harkon.co.uk`
- Let's Encrypt SSL via DNS-01 challenge
- Strong passwords (generated)
- Authentik SSO enabled
- Gitea container registry
- No debug ports exposed
## Troubleshooting
### Issue: Services can't find each other
**Solution**: Ensure networks are created and services are on the correct networks
```bash
docker network ls
docker network inspect frontend
docker network inspect backend
```
### Issue: Volumes not found
**Solution**: Check volume names match
```bash
docker volume ls
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env config
```
### Issue: Environment variables not loaded
**Solution**: Check .env file exists and is in correct location
```bash
ls -la infra/environments/production/.env
cat infra/environments/production/.env | grep DOMAIN
```
### Issue: Traefik routing not working
**Solution**: Check labels and ensure Traefik can see containers
```bash
docker logs traefik | grep -i error
docker inspect <container> | grep -A 20 Labels
```
## Rollback Plan
If migration fails:
```bash
# Stop new services
./infra/scripts/deploy.sh production down
# Restore old structure
cd infra/compose
docker compose -f docker-compose.backend.yml up -d
# Or for production
cd infra/production
docker compose -f infrastructure.yaml up -d
docker compose -f services.yaml up -d
docker compose -f monitoring.yaml up -d
```
## Post-Migration Cleanup
After successful migration and verification:
```bash
# Remove old compose files (optional)
rm -rf infra/compose/docker-compose.*.yml
# Remove old production folder (optional)
rm -rf infra/production.old
# Remove backup files
rm infra/.env.production.backup
rm infra-backup-*.tar.gz
```
## Benefits of New Structure
**Multi-environment support** - Easy to deploy to local, dev, prod
**Cleaner organization** - Configs separated by purpose
**Unified deployment** - Single script for all environments
**Better security** - Environment-specific secrets
**Easier maintenance** - Clear separation of concerns
**Scalable** - Easy to add new environments or services
## Next Steps
1. Test in local environment first
2. Deploy to development server
3. Verify all services work
4. Deploy to production
5. Update documentation
6. Train team on new structure

349
infra/QUICK_START.md Normal file
View File

@@ -0,0 +1,349 @@
# Quick Start Guide
Get AI Tax Agent infrastructure running in 5 minutes!
## Prerequisites
- Docker 24.0+ with Compose V2
- Git
- 10GB free disk space
## Local Development (Fastest)
### 1. Create Environment File
```bash
cp infra/environments/local/.env.example infra/environments/local/.env
```
### 2. Setup Networks
```bash
./infra/scripts/setup-networks.sh
```
### 3. Deploy
```bash
./infra/scripts/deploy.sh local all
```
### 4. Access Services
- **Grafana**: http://localhost:3000 (admin/admin)
- **MinIO**: http://localhost:9093 (minioadmin/minioadmin)
- **Vault**: http://localhost:8200 (token: dev-root-token)
- **Traefik Dashboard**: http://localhost:8080
### 5. Build and Run Services
```bash
# Build images
./scripts/build-and-push-images.sh localhost:5000 latest local
# Services will auto-start via deploy script
```
---
## Development Server
### 1. SSH to Server
```bash
ssh deploy@dev-server.harkon.co.uk
cd /opt/ai-tax-agent
```
### 2. Create Environment File
```bash
cp infra/environments/development/.env.example infra/environments/development/.env
```
### 3. Generate Secrets
```bash
./scripts/generate-production-secrets.sh
```
### 4. Edit Environment
```bash
vim infra/environments/development/.env
```
Update:
- `DOMAIN=dev.harkon.co.uk`
- API keys
- Registry credentials
### 5. Deploy
```bash
./infra/scripts/setup-networks.sh
./infra/scripts/deploy.sh development all
```
### 6. Access
- https://grafana.dev.harkon.co.uk
- https://minio.dev.harkon.co.uk
- https://vault.dev.harkon.co.uk
---
## Production Server
### 1. SSH to Server
```bash
ssh deploy@141.136.35.199
cd /opt/ai-tax-agent
```
### 2. Verify Environment File
```bash
# Should already exist from previous setup
cat infra/environments/production/.env | grep DOMAIN
```
### 3. Deploy Infrastructure
```bash
./infra/scripts/setup-networks.sh
./infra/scripts/deploy.sh production infrastructure
```
### 4. Deploy Monitoring
```bash
./infra/scripts/deploy.sh production monitoring
```
### 5. Deploy Services
```bash
./infra/scripts/deploy.sh production services
```
### 6. Access
- https://grafana.harkon.co.uk
- https://minio.harkon.co.uk
- https://vault.harkon.co.uk
- https://ui-review.harkon.co.uk
---
## Common Commands
### Deploy Specific Stack
```bash
# Infrastructure only
./infra/scripts/deploy.sh production infrastructure
# Monitoring only
./infra/scripts/deploy.sh production monitoring
# Services only
./infra/scripts/deploy.sh production services
```
### Stop Services
```bash
./infra/scripts/deploy.sh production down
```
### View Logs
```bash
# All services
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env logs -f
# Specific service
docker logs -f vault
```
### Restart Service
```bash
docker restart vault
```
### Check Status
```bash
docker ps
```
---
## Troubleshooting
### Services Not Starting
```bash
# Check logs
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env logs
# Check specific service
docker logs vault
```
### Network Issues
```bash
# Verify networks exist
docker network ls | grep -E "frontend|backend"
# Recreate networks
docker network rm frontend backend
./infra/scripts/setup-networks.sh
```
### Environment Variables Not Loading
```bash
# Verify .env file exists
ls -la infra/environments/production/.env
# Check variables
cat infra/environments/production/.env | grep DOMAIN
```
---
## Next Steps
1. ✅ Infrastructure running
2. 📖 Read [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) for detailed instructions
3. 🔧 Configure Authentik OAuth providers
4. 🚀 Deploy application services
5. 📊 Setup Grafana dashboards
6. 🔐 Initialize Vault secrets
---
## Support
- **Documentation**: See `infra/README.md`
- **Deployment Guide**: See `infra/DEPLOYMENT_GUIDE.md`
- **Migration Guide**: See `infra/MIGRATION_GUIDE.md`
- **Structure Overview**: See `infra/STRUCTURE_OVERVIEW.md`
---
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────┐
│ Traefik │
│ (Reverse Proxy) │
└─────────────────────────────────────────────────────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐
│ Authentik │ │ Monitoring │ │ Application │
│ (SSO) │ │ (Grafana) │ │ Services │
└────────────────┘ └──────────────┘ └─────────────────┘
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐
│ PostgreSQL │ │ Neo4j │ │ Qdrant │
└────────────────┘ └──────────────┘ └─────────────────┘
│ │ │
┌───────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐
│ MinIO │ │ Redis │ │ NATS │
└────────────────┘ └──────────────┘ └─────────────────┘
```
---
## Environment Comparison
| Feature | Local | Development | Production |
|---------|-------|-------------|------------|
| Domain | localhost | dev.harkon.co.uk | harkon.co.uk |
| SSL | Self-signed | Let's Encrypt | Let's Encrypt |
| Auth | Optional | Authentik | Authentik |
| Passwords | Simple | Strong | Strong |
| Monitoring | Optional | Full | Full |
| Backups | No | Daily | Daily |
---
## Service Ports (Local)
| Service | Port | URL |
|---------|------|-----|
| Traefik Dashboard | 8080 | http://localhost:8080 |
| Grafana | 3000 | http://localhost:3000 |
| MinIO Console | 9093 | http://localhost:9093 |
| Vault | 8200 | http://localhost:8200 |
| PostgreSQL | 5432 | localhost:5432 |
| Neo4j | 7474 | http://localhost:7474 |
| Redis | 6379 | localhost:6379 |
| Qdrant | 6333 | http://localhost:6333 |
---
## Deployment Checklist
### Before Deployment
- [ ] Environment file created
- [ ] Secrets generated (dev/prod)
- [ ] Docker networks created
- [ ] DNS configured (dev/prod)
- [ ] GoDaddy API credentials set (dev/prod)
- [ ] Gitea registry configured (dev/prod)
### After Deployment
- [ ] All services running (`docker ps`)
- [ ] Services accessible via URLs
- [ ] Grafana dashboards loaded
- [ ] Vault initialized
- [ ] MinIO buckets created
- [ ] Authentik configured (dev/prod)
- [ ] Monitoring alerts configured
---
## Quick Reference
### Environment Files
- Local: `infra/environments/local/.env`
- Development: `infra/environments/development/.env`
- Production: `infra/environments/production/.env`
### Compose Files
- Infrastructure: `infra/base/infrastructure.yaml`
- Services: `infra/base/services.yaml`
- Monitoring: `infra/base/monitoring.yaml`
- External: `infra/base/external.yaml`
### Scripts
- Deploy: `./infra/scripts/deploy.sh <env> <stack>`
- Setup Networks: `./infra/scripts/setup-networks.sh`
- Reorganize: `./infra/scripts/reorganize-structure.sh`
---
**Ready to deploy? Start with local development!**
```bash
cp infra/environments/local/.env.example infra/environments/local/.env
./infra/scripts/setup-networks.sh
./infra/scripts/deploy.sh local all
```

247
infra/README.md Normal file
View File

@@ -0,0 +1,247 @@
# AI Tax Agent Infrastructure
Multi-environment Docker Compose infrastructure for AI Tax Agent.
## Directory Structure
```
infra/
├── environments/ # Environment-specific configurations
│ ├── local/ # Local development (localhost, self-signed certs)
│ ├── development/ # Development server (dev.harkon.co.uk)
│ └── production/ # Production server (harkon.co.uk)
├── base/ # Base compose files (shared across environments)
│ ├── infrastructure.yaml # Core infra (Vault, MinIO, DBs, etc.)
│ ├── monitoring.yaml # Monitoring stack (Prometheus, Grafana, Loki)
│ ├── services.yaml # Application services
│ └── external.yaml # External services (Traefik, Authentik, Gitea, etc.)
├── configs/ # Service configurations
│ ├── traefik/ # Traefik configs
│ ├── grafana/ # Grafana dashboards & provisioning
│ ├── prometheus/ # Prometheus config
│ ├── loki/ # Loki config
│ ├── vault/ # Vault config
│ └── authentik/ # Authentik bootstrap
├── certs/ # SSL certificates (gitignored)
│ ├── local/ # Self-signed certs for local
│ ├── development/ # Let's Encrypt certs for dev
│ └── production/ # Let's Encrypt certs for prod
└── scripts/ # Deployment scripts
├── deploy.sh # Main deployment script
├── setup-networks.sh # Create Docker networks
└── cleanup.sh # Cleanup script
```
## Environments
### Local Development
- **Domain**: `localhost` / `*.local.harkon.co.uk`
- **SSL**: Self-signed certificates
- **Auth**: Authentik (optional)
- **Registry**: Local Docker registry or Gitea
- **Purpose**: Local development and testing
### Development
- **Domain**: `*.dev.harkon.co.uk`
- **SSL**: Let's Encrypt (DNS-01 challenge)
- **Auth**: Authentik SSO
- **Registry**: Gitea container registry
- **Purpose**: Staging/testing before production
### Production
- **Domain**: `*.harkon.co.uk`
- **SSL**: Let's Encrypt (DNS-01 challenge)
- **Auth**: Authentik SSO
- **Registry**: Gitea container registry
- **Purpose**: Production deployment
## Quick Start
### 1. Setup Environment
```bash
# Choose your environment
export ENV=local # or development, production
# Copy environment template
cp infra/environments/$ENV/.env.example infra/environments/$ENV/.env
# Edit environment variables
vim infra/environments/$ENV/.env
```
### 2. Generate Secrets (Production/Development only)
```bash
./scripts/generate-production-secrets.sh
```
### 3. Create Docker Networks
```bash
./infra/scripts/setup-networks.sh
```
### 4. Deploy Infrastructure
```bash
# Deploy everything
./infra/scripts/deploy.sh $ENV all
# Or deploy specific stacks
./infra/scripts/deploy.sh $ENV infrastructure
./infra/scripts/deploy.sh $ENV monitoring
./infra/scripts/deploy.sh $ENV services
```
## Environment Variables
Each environment has its own `.env` file with:
- **Domain Configuration**: `DOMAIN`, `EMAIL`
- **Database Passwords**: `POSTGRES_PASSWORD`, `NEO4J_PASSWORD`, etc.
- **Object Storage**: `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`
- **Secrets Management**: `VAULT_DEV_ROOT_TOKEN_ID`
- **SSO/Auth**: `AUTHENTIK_SECRET_KEY`, `AUTHENTIK_BOOTSTRAP_PASSWORD`
- **Monitoring**: `GRAFANA_PASSWORD`, OAuth secrets
- **Application**: Service-specific configs
## Deployment Commands
### Deploy Full Stack
```bash
# Local
./infra/scripts/deploy.sh local all
# Development
./infra/scripts/deploy.sh development all
# Production
./infra/scripts/deploy.sh production all
```
### Deploy Individual Stacks
```bash
# Infrastructure only (Vault, MinIO, DBs, etc.)
./infra/scripts/deploy.sh production infrastructure
# Monitoring only (Prometheus, Grafana, Loki)
./infra/scripts/deploy.sh production monitoring
# Services only (Application microservices)
./infra/scripts/deploy.sh production services
# External services (Traefik, Authentik, Gitea - usually pre-existing)
./infra/scripts/deploy.sh production external
```
### Stop/Remove Stacks
```bash
# Stop all
./infra/scripts/deploy.sh production down
# Stop specific stack
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env down
```
## Network Architecture
All environments use two Docker networks:
- **frontend**: Public-facing services (Traefik, UI)
- **backend**: Internal services (DBs, message queues, etc.)
Networks are created with:
```bash
docker network create frontend
docker network create backend
```
## Volume Management
Volumes are environment-specific and named with environment prefix:
- Local: `local_postgres_data`, `local_vault_data`, etc.
- Development: `dev_postgres_data`, `dev_vault_data`, etc.
- Production: `prod_postgres_data`, `prod_vault_data`, etc.
## SSL Certificates
### Local
- Self-signed certificates in `infra/certs/local/`
- Generated with `scripts/generate-dev-certs.sh`
### Development/Production
- Let's Encrypt certificates via Traefik
- DNS-01 challenge using GoDaddy API
- Stored in `infra/certs/{environment}/`
## External Services
Some services (Traefik, Authentik, Gitea, Nextcloud, Portainer) may already exist on the server.
To use existing services:
1. Don't deploy `external.yaml`
2. Ensure networks are shared
3. Update service discovery labels
## Monitoring
Access monitoring dashboards:
- **Grafana**: `https://grafana.{domain}`
- **Prometheus**: `https://prometheus.{domain}`
- **Traefik Dashboard**: `https://traefik.{domain}/dashboard/`
## Troubleshooting
### Check Service Status
```bash
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env ps
```
### View Logs
```bash
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env logs -f vault
```
### Restart Service
```bash
docker compose -f infra/base/infrastructure.yaml --env-file infra/environments/production/.env restart vault
```
## Security Notes
- **Never commit `.env` files** - They contain secrets!
- **Rotate secrets regularly** - Use `generate-production-secrets.sh`
- **Use strong passwords** - Minimum 32 characters
- **Enable Authentik SSO** - For all production services
- **Backup volumes** - Especially databases and Vault
## Migration from Old Structure
If migrating from the old structure:
1. Copy environment variables from old `.env` files
2. Update volume names if needed
3. Migrate data volumes
4. Update Traefik labels if using existing Traefik
5. Test in development first!
## Support
For issues or questions:
- Check logs: `docker compose logs -f <service>`
- Review documentation in `docs/`
- Check Traefik dashboard for routing issues

243
infra/STRUCTURE_CLEANUP.md Normal file
View File

@@ -0,0 +1,243 @@
# Infrastructure Structure Cleanup Plan
## Current Situation
We have two parallel structures that need to be aligned:
### 1. External Services (Production/Remote)
Located in `infra/compose/` - These are deployed individually on the remote server:
- **Traefik** - `infra/compose/traefik/`
- **Authentik** - `infra/compose/authentik/`
- **Gitea** - `infra/compose/gitea/`
- **Nextcloud** - `infra/compose/nextcloud/`
- **Portainer** - `infra/compose/portainer/`
### 2. Application Infrastructure (Multi-Environment)
Located in `infra/base/` and `infra/environments/`:
- Infrastructure services (Vault, MinIO, DBs, etc.)
- Application services (14 microservices)
- Monitoring stack (Prometheus, Grafana, Loki)
### 3. Configuration Duplication
- `infra/compose/traefik/config/` - Production Traefik config
- `infra/configs/traefik/` - Application Traefik config (copied)
- Similar duplication for other services
---
## Cleanup Strategy
### Phase 1: Consolidate Configurations
#### Traefik
- **Keep**: `infra/compose/traefik/` as the source of truth for production
- **Symlink**: `infra/configs/traefik/``../compose/traefik/`
- **Reason**: External service configs should live with their compose files
#### Authentik
- **Keep**: `infra/compose/authentik/` for production
- **Keep**: `infra/configs/authentik/` for application-specific bootstrap
- **Reason**: Different purposes - one for service, one for app integration
#### Grafana/Prometheus/Loki
- **Keep**: `infra/configs/grafana/`, `infra/configs/prometheus/`, `infra/configs/loki/`
- **Reason**: These are application-specific, not external services
### Phase 2: Update References
#### Makefile
- Update paths to reference correct locations
- Add targets for external service deployment
- Separate local dev from production deployment
#### Scripts
- Update `scripts/deploy.sh` to handle external services
- Create `scripts/deploy-external.sh` for production external services
- Update `infra/scripts/deploy.sh` for application infrastructure
### Phase 3: Documentation
- Clear separation between:
- External services (production only)
- Application infrastructure (multi-environment)
- Development environment (local only)
---
## Proposed Final Structure
```
ai-tax-agent/
├── infra/
│ ├── compose/ # External services (production)
│ │ ├── traefik/
│ │ │ ├── compose.yaml # Traefik service definition
│ │ │ ├── config/ # Traefik configuration
│ │ │ ├── certs/ # SSL certificates
│ │ │ └── .provider.env # GoDaddy API credentials
│ │ ├── authentik/
│ │ │ ├── compose.yaml
│ │ │ ├── .env
│ │ │ ├── media/
│ │ │ └── custom-templates/
│ │ ├── gitea/
│ │ │ ├── compose.yaml
│ │ │ └── .env
│ │ ├── nextcloud/
│ │ │ └── compose.yaml
│ │ ├── portainer/
│ │ │ └── docker-compose.yaml
│ │ ├── docker-compose.local.yml # Local dev (all-in-one)
│ │ └── docker-compose.backend.yml # Backend services
│ │
│ ├── base/ # Application infrastructure (multi-env)
│ │ ├── infrastructure.yaml # Core infra services
│ │ ├── services.yaml # Application microservices
│ │ └── monitoring.yaml # Monitoring stack
│ │
│ ├── environments/ # Environment-specific configs
│ │ ├── local/
│ │ │ ├── .env.example
│ │ │ └── .env
│ │ ├── development/
│ │ │ ├── .env.example
│ │ │ └── .env
│ │ └── production/
│ │ ├── .env.example
│ │ └── .env
│ │
│ ├── configs/ # Application service configs
│ │ ├── authentik/ # App-specific Authentik bootstrap
│ │ ├── grafana/ # Grafana dashboards
│ │ ├── prometheus/ # Prometheus scrape configs
│ │ ├── loki/ # Loki config
│ │ └── vault/ # Vault config
│ │
│ └── scripts/ # Infrastructure deployment scripts
│ ├── deploy.sh # Deploy application infrastructure
│ ├── setup-networks.sh # Create Docker networks
│ └── reorganize-structure.sh
├── scripts/ # Project-wide scripts
│ ├── deploy-external.sh # Deploy external services (production)
│ ├── build-and-push-images.sh # Build and push Docker images
│ ├── generate-secrets.sh # Generate secrets
│ └── ...
└── Makefile # Project commands
```
---
## Deployment Workflows
### Local Development
```bash
# Use all-in-one compose file
make bootstrap
make run
# OR
cd infra/compose
docker compose -f docker-compose.local.yml up -d
```
### Production - External Services
```bash
# Deploy individually on remote server
cd /opt/ai-tax-agent/infra/compose/traefik
docker compose up -d
cd /opt/ai-tax-agent/infra/compose/authentik
docker compose up -d
cd /opt/ai-tax-agent/infra/compose/gitea
docker compose up -d
```
### Production - Application Infrastructure
```bash
# Deploy application infrastructure
./infra/scripts/deploy.sh production infrastructure
./infra/scripts/deploy.sh production monitoring
./infra/scripts/deploy.sh production services
```
---
## Migration Steps
### Step 1: Align Traefik Configs
```bash
# Remove duplicate configs
rm -rf infra/configs/traefik/config/traefik-dynamic.yml
# Keep only app-specific middleware
# Move production configs to compose/traefik/config/
```
### Step 2: Update Makefile
- Add targets for external service deployment
- Update paths to reference correct locations
- Separate local dev from production
### Step 3: Update Scripts
- Create `scripts/deploy-external.sh` for production
- Update `infra/scripts/deploy.sh` for application infra
- Update all path references
### Step 4: Documentation
- Update README files
- Create deployment guides for each environment
- Document external vs application services
---
## Key Decisions
### 1. External Services Location
**Decision**: Keep in `infra/compose/` with individual folders
**Reason**: These are production-only, deployed separately, have their own configs
### 2. Application Infrastructure Location
**Decision**: Keep in `infra/base/` with environment-specific `.env` files
**Reason**: Multi-environment support, shared compose files
### 3. Configuration Management
**Decision**:
- External service configs live with their compose files
- Application configs live in `infra/configs/`
**Reason**: Clear separation of concerns
### 4. Makefile Targets
**Decision**:
- `make run` - Local development (all-in-one)
- `make deploy-external` - Production external services
- `make deploy-infra` - Application infrastructure
**Reason**: Clear separation of deployment targets
---
## Benefits
**Clear Separation** - External vs application services
**No Duplication** - Single source of truth for configs
**Multi-Environment** - Easy to deploy to local/dev/prod
**Maintainable** - Logical organization
**Scalable** - Easy to add new services
**Production-Ready** - Matches actual deployment
---
## Next Steps
1. Run cleanup script to align configurations
2. Update Makefile with new targets
3. Update deployment scripts
4. Test local deployment
5. Test production deployment
6. Update documentation

346
infra/STRUCTURE_OVERVIEW.md Normal file
View File

@@ -0,0 +1,346 @@
# Infrastructure Structure Overview
## New Multi-Environment Structure
```
infra/
├── README.md # Main infrastructure documentation
├── DEPLOYMENT_GUIDE.md # Complete deployment guide
├── MIGRATION_GUIDE.md # Migration from old structure
├── STRUCTURE_OVERVIEW.md # This file
├── base/ # Base compose files (environment-agnostic)
│ ├── infrastructure.yaml # Core infrastructure services
│ ├── services.yaml # Application microservices
│ ├── monitoring.yaml # Monitoring stack
│ └── external.yaml # External services (Traefik, Authentik, etc.)
├── environments/ # Environment-specific configurations
│ ├── local/ # Local development
│ │ ├── .env.example # Template
│ │ └── .env # Actual config (gitignored)
│ ├── development/ # Development server
│ │ ├── .env.example # Template
│ │ └── .env # Actual config (gitignored)
│ └── production/ # Production server
│ ├── .env.example # Template
│ └── .env # Actual config (gitignored)
├── configs/ # Service configuration files
│ ├── traefik/ # Traefik configs
│ │ ├── config/ # Dynamic configuration
│ │ │ ├── middlewares.yml
│ │ │ ├── routers.yml
│ │ │ └── services.yml
│ │ ├── traefik.yml # Static configuration
│ │ └── .provider.env # GoDaddy API credentials (gitignored)
│ ├── grafana/ # Grafana configs
│ │ ├── dashboards/ # Dashboard JSON files
│ │ └── provisioning/ # Datasources, dashboards
│ ├── prometheus/ # Prometheus config
│ │ └── prometheus.yml
│ ├── loki/ # Loki config
│ │ └── loki-config.yml
│ ├── promtail/ # Promtail config
│ │ └── promtail-config.yml
│ ├── vault/ # Vault config
│ │ └── config/
│ └── authentik/ # Authentik bootstrap
│ ├── bootstrap.yaml
│ ├── custom-templates/
│ └── media/
├── certs/ # SSL certificates (gitignored)
│ ├── local/ # Self-signed certs
│ ├── development/ # Let's Encrypt certs
│ └── production/ # Let's Encrypt certs
├── docker/ # Dockerfile templates
│ ├── base-runtime.Dockerfile # Base image for all services
│ ├── base-ml.Dockerfile # Base image for ML services
│ └── Dockerfile.ml-service.template
└── scripts/ # Deployment and utility scripts
├── deploy.sh # Main deployment script
├── setup-networks.sh # Create Docker networks
└── cleanup.sh # Cleanup script
```
## Base Compose Files
### infrastructure.yaml
Core infrastructure services needed by the application:
- **Vault** - Secrets management
- **MinIO** - Object storage (S3-compatible)
- **PostgreSQL** - Relational database
- **Neo4j** - Graph database
- **Qdrant** - Vector database
- **Redis** - Cache and session store
- **NATS** - Message queue (with JetStream)
### services.yaml
Application microservices (14 services):
- **svc-ingestion** - Document ingestion
- **svc-extract** - Data extraction
- **svc-kg** - Knowledge graph
- **svc-rag-indexer** - RAG indexing (ML)
- **svc-rag-retriever** - RAG retrieval (ML)
- **svc-forms** - Form processing
- **svc-hmrc** - HMRC integration
- **svc-ocr** - OCR processing (ML)
- **svc-rpa** - RPA automation
- **svc-normalize-map** - Data normalization
- **svc-reason** - Reasoning engine
- **svc-firm-connectors** - Firm integrations
- **svc-coverage** - Coverage analysis
- **ui-review** - Review UI (Next.js)
### monitoring.yaml
Monitoring and observability stack:
- **Prometheus** - Metrics collection
- **Grafana** - Dashboards and visualization
- **Loki** - Log aggregation
- **Promtail** - Log collection
### external.yaml (optional)
External services that may already exist:
- **Traefik** - Reverse proxy and load balancer
- **Authentik** - SSO and authentication
- **Gitea** - Git repository and container registry
- **Nextcloud** - File storage
- **Portainer** - Docker management UI
## Environment Configurations
### Local Development
- **Domain**: `localhost` or `*.local.harkon.co.uk`
- **SSL**: Self-signed certificates
- **Auth**: Optional (can disable Authentik)
- **Registry**: Local Docker registry or Gitea
- **Passwords**: Simple (postgres, admin, etc.)
- **Purpose**: Local development and testing
- **Traefik Dashboard**: Exposed on port 8080
### Development Server
- **Domain**: `*.dev.harkon.co.uk`
- **SSL**: Let's Encrypt (DNS-01 via GoDaddy)
- **Auth**: Authentik SSO enabled
- **Registry**: Gitea container registry
- **Passwords**: Strong (auto-generated)
- **Purpose**: Staging and integration testing
- **Traefik Dashboard**: Protected by Authentik
### Production Server
- **Domain**: `*.harkon.co.uk`
- **SSL**: Let's Encrypt (DNS-01 via GoDaddy)
- **Auth**: Authentik SSO enabled
- **Registry**: Gitea container registry
- **Passwords**: Strong (auto-generated)
- **Purpose**: Production deployment
- **Traefik Dashboard**: Protected by Authentik
- **Monitoring**: Full stack enabled
## Docker Networks
All environments use two networks:
### frontend
- Public-facing services
- Connected to Traefik
- Services: UI, Grafana, Vault, MinIO console
### backend
- Internal services
- Not directly accessible
- Services: Databases, message queues, internal APIs
## Volume Naming
Volumes are named consistently across environments:
- `postgres_data`
- `neo4j_data`
- `neo4j_logs`
- `qdrant_data`
- `minio_data`
- `vault_data`
- `redis_data`
- `nats_data`
- `prometheus_data`
- `grafana_data`
- `loki_data`
## Deployment Workflow
### 1. Setup Environment
```bash
cp infra/environments/production/.env.example infra/environments/production/.env
vim infra/environments/production/.env
```
### 2. Generate Secrets
```bash
./scripts/generate-production-secrets.sh
```
### 3. Setup Networks
```bash
./infra/scripts/setup-networks.sh
```
### 4. Deploy Infrastructure
```bash
./infra/scripts/deploy.sh production infrastructure
```
### 5. Deploy Monitoring
```bash
./infra/scripts/deploy.sh production monitoring
```
### 6. Deploy Services
```bash
./infra/scripts/deploy.sh production services
```
## Key Features
### ✅ Multi-Environment Support
Single codebase deploys to local, development, and production with environment-specific configurations.
### ✅ Modular Architecture
Services split into logical groups (infrastructure, monitoring, services, external) for independent deployment.
### ✅ Unified Deployment
Single `deploy.sh` script handles all environments and stacks.
### ✅ Environment Isolation
Each environment has its own `.env` file with appropriate secrets and configurations.
### ✅ Shared Configurations
Common service configs in `configs/` directory, referenced by all environments.
### ✅ Security Best Practices
- Secrets in gitignored `.env` files
- Strong password generation
- Authentik SSO integration
- SSL/TLS everywhere (Let's Encrypt)
### ✅ Easy Maintenance
- Clear directory structure
- Comprehensive documentation
- Migration guide from old structure
- Troubleshooting guides
## Service Access
### Local
- http://localhost:3000 - Grafana
- http://localhost:9093 - MinIO
- http://localhost:8200 - Vault
- http://localhost:8080 - Traefik Dashboard
### Development
- https://grafana.dev.harkon.co.uk
- https://minio.dev.harkon.co.uk
- https://vault.dev.harkon.co.uk
- https://ui-review.dev.harkon.co.uk
### Production
- https://grafana.harkon.co.uk
- https://minio.harkon.co.uk
- https://vault.harkon.co.uk
- https://ui-review.harkon.co.uk
## Configuration Management
### Environment Variables
All configuration via environment variables in `.env` files:
- Domain settings
- Database passwords
- API keys
- OAuth secrets
- Registry credentials
### Service Configs
Static configurations in `configs/` directory:
- Traefik routing rules
- Grafana dashboards
- Prometheus scrape configs
- Loki retention policies
### Secrets Management
- Development/Production: Vault
- Local: Environment variables
- Rotation: `generate-production-secrets.sh`
## Monitoring and Observability
### Metrics (Prometheus)
- Service health
- Resource usage
- Request rates
- Error rates
### Logs (Loki)
- Centralized logging
- Query via Grafana
- Retention policies
- Log aggregation
### Dashboards (Grafana)
- Infrastructure overview
- Service metrics
- Application performance
- Business metrics
### Alerts
- Prometheus AlertManager
- Slack/Email notifications
- PagerDuty integration
## Backup Strategy
### What to Backup
- PostgreSQL database
- Neo4j graph data
- Vault secrets
- MinIO objects
- Qdrant vectors
- Grafana dashboards
### How to Backup
```bash
# Automated backup script
./scripts/backup-volumes.sh production
# Manual backup
docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres.tar.gz /data
```
### Backup Schedule
- Daily: Databases
- Weekly: Full system
- Monthly: Archive
## Disaster Recovery
### Recovery Steps
1. Restore infrastructure
2. Restore volumes from backup
3. Deploy services
4. Verify functionality
5. Update DNS if needed
### RTO/RPO
- **RTO**: 4 hours (Recovery Time Objective)
- **RPO**: 24 hours (Recovery Point Objective)
## Next Steps
1. Review [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) for deployment instructions
2. Review [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) if migrating from old structure
3. Setup environment files
4. Deploy to local first
5. Test in development
6. Deploy to production

View File

@@ -0,0 +1,228 @@
# FILE: infra/base/infrastructure.yaml
# Infrastructure Services for AI Tax Agent
# Environment-agnostic - use with environment-specific .env files
# Deploy with: ./infra/scripts/deploy.sh <environment> infrastructure
networks:
frontend:
external: true
name: frontend
backend:
external: true
name: backend
volumes:
postgres_data:
neo4j_data:
neo4j_logs:
qdrant_data:
minio_data:
vault_data:
redis_data:
nats_data:
services:
# Secrets Management
vault:
image: hashicorp/vault:1.15
container_name: vault
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- vault_data:/vault/data
environment:
VAULT_DEV_ROOT_TOKEN_ID: ${VAULT_DEV_ROOT_TOKEN_ID}
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
command: vault server -dev -dev-listen-address=0.0.0.0:8200
cap_add:
- IPC_LOCK
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.services.vault.loadbalancer.server.port=8200"
# Object Storage
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: minio
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
MINIO_BROWSER_REDIRECT_URL: https://minio.${DOMAIN}
command: server /data --address ":9092" --console-address ":9093"
healthcheck:
test: ["CMD", "mc", "--version"]
interval: 30s
timeout: 20s
retries: 3
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.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.service=minio-console"
- "traefik.http.services.minio-console.loadbalancer.server.port=9093"
# Vector Database
qdrant:
image: qdrant/qdrant:v1.7.4
container_name: qdrant
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- qdrant_data:/qdrant/storage
environment:
QDRANT__SERVICE__GRPC_PORT: ${QDRANT__SERVICE__GRPC_PORT:-6334}
QDRANT__SERVICE__HTTP_PORT: 6333
QDRANT__LOG_LEVEL: INFO
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.qdrant.loadbalancer.server.port=6333"
# Knowledge Graph Database
neo4j:
image: neo4j:5.15-community
container_name: neo4j
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
environment:
NEO4J_AUTH: neo4j/${NEO4J_PASSWORD}
NEO4J_PLUGINS: '["apoc", "graph-data-science"]'
NEO4J_dbms_security_procedures_unrestricted: gds.*,apoc.*
NEO4J_dbms_security_procedures_allowlist: gds.*,apoc.*
NEO4J_apoc_export_file_enabled: true
NEO4J_apoc_import_file_enabled: true
NEO4J_apoc_import_file_use__neo4j__config: true
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.neo4j.loadbalancer.server.port=7474"
# Secure Client Data Store
postgres:
image: postgres:15-alpine
container_name: postgres
restart: unless-stopped
networks:
- backend
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: tax_system
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
command: >
postgres
-c shared_preload_libraries=pg_stat_statements
-c pg_stat_statements.track=all
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c maintenance_work_mem=64MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
-c random_page_cost=1.1
-c effective_io_concurrency=200
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 10s
retries: 3
# Cache & Session Store
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
networks:
- backend
volumes:
- redis_data:/data
command: >
redis-server
--appendonly yes
--appendfsync everysec
--maxmemory 512mb
--maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 30s
timeout: 10s
retries: 3
# Message Broker & Event Streaming
nats:
image: nats:2.10-alpine
container_name: nats
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- nats_data:/data
command: >
--jetstream
--store_dir=/data
--http_port=8222
environment:
NATS_LOG_LEVEL: ${NATS_LOG_LEVEL:-info}
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:8222/healthz",
]
interval: 30s
timeout: 10s
retries: 3
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.nats-monitor.loadbalancer.server.port=8222"

126
infra/base/monitoring.yaml Normal file
View File

@@ -0,0 +1,126 @@
# FILE: infra/compose/production/monitoring.yaml
# Production Monitoring Stack for AI Tax Agent
# Deploy to: /opt/compose/ai-tax-agent/monitoring.yaml
networks:
frontend:
external: true
name: frontend
backend:
external: true
name: backend
volumes:
prometheus_data:
grafana_data:
loki_data:
services:
# Metrics Collection
prometheus:
image: prom/prometheus:v2.48.1
container_name: prometheus
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- prometheus_data:/prometheus
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/etc/prometheus/console_libraries"
- "--web.console.templates=/etc/prometheus/consoles"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
# Visualization & Dashboards
grafana:
image: grafana/grafana:10.2.3
container_name: grafana
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: false
GF_USERS_AUTO_ASSIGN_ORG: true
GF_USERS_AUTO_ASSIGN_ORG_ROLE: Viewer
GF_AUTH_GENERIC_OAUTH_ENABLED: true
GF_AUTH_GENERIC_OAUTH_NAME: Authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: ${GRAFANA_OAUTH_CLIENT_ID}
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ${GRAFANA_OAUTH_CLIENT_SECRET}
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email groups
GF_AUTH_GENERIC_OAUTH_AUTH_URL: https://authentik.${DOMAIN}/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: https://authentik.${DOMAIN}/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL: https://authentik.${DOMAIN}/application/o/userinfo/
GF_AUTH_GENERIC_OAUTH_AUTO_LOGIN: false
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: true
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: role
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: false
GF_AUTH_GENERIC_OAUTH_GROUPS_ATTRIBUTE_PATH: groups
GF_AUTH_OAUTH_AUTO_LOGIN: false
GF_AUTH_DISABLE_LOGIN_FORM: false
GF_SERVER_ROOT_URL: https://grafana.${DOMAIN}
GF_SERVER_SERVE_FROM_SUB_PATH: false
GF_SECURITY_COOKIE_SECURE: true
GF_SECURITY_COOKIE_SAMESITE: lax
GF_AUTH_GENERIC_OAUTH_USE_PKCE: true
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.services.grafana.loadbalancer.server.port=3000"
# Log Aggregation
loki:
image: grafana/loki:2.9.4
container_name: loki
restart: unless-stopped
networks:
- backend
- frontend
volumes:
- loki_data:/loki
- ./loki/loki.yml:/etc/loki/local-config.yaml:ro
command: -config.file=/etc/loki/local-config.yaml
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.loki.loadbalancer.server.port=3100"
# Log Shipper (for Docker containers)
promtail:
image: grafana/promtail:2.9.4
container_name: promtail
restart: unless-stopped
networks:
- backend
volumes:
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./loki/promtail-config.yml:/etc/promtail/config.yml:ro
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki

453
infra/base/services.yaml Normal file
View File

@@ -0,0 +1,453 @@
# FILE: infra/compose/production/services.yaml
# Production Application Services for AI Tax Agent
# Deploy to: /opt/compose/ai-tax-agent/services.yaml
# NOTE: Build images locally and push to registry before deploying
networks:
frontend:
external: true
name: frontend
backend:
external: true
name: backend
services:
# Document Ingestion Service
svc-ingestion:
image: gitea.harkon.co.uk/harkon/svc-ingestion:latest
container_name: svc-ingestion
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-ingestion.loadbalancer.server.port=8000"
# Data Extraction Service
svc-extract:
image: gitea.harkon.co.uk/harkon/svc-extract:latest
container_name: svc-extract
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- RAG_EMBEDDING_MODEL=${RAG_EMBEDDING_MODEL}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-extract.loadbalancer.server.port=8000"
# Knowledge Graph Service
svc-kg:
image: gitea.harkon.co.uk/harkon/svc-kg:latest
container_name: svc-kg
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-kg.loadbalancer.server.port=8000"
# RAG Retrieval Service
svc-rag-retriever:
image: gitea.harkon.co.uk/harkon/svc-rag-retriever:latest
container_name: svc-rag-retriever
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- QDRANT_URL=http://qdrant:6333
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- RAG_EMBEDDING_MODEL=${RAG_EMBEDDING_MODEL}
- RAG_RERANKER_MODEL=${RAG_RERANKER_MODEL}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-rag-retriever.loadbalancer.server.port=8000"
# Forms Service
svc-forms:
image: gitea.harkon.co.uk/harkon/svc-forms:latest
container_name: svc-forms
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-forms.loadbalancer.server.port=8000"
# HMRC Integration Service
svc-hmrc:
image: gitea.harkon.co.uk/harkon/svc-hmrc:latest
container_name: svc-hmrc
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- HMRC_MTD_ITSA_MODE=${HMRC_MTD_ITSA_MODE}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-hmrc.loadbalancer.server.port=8000"
# OCR Service
svc-ocr:
image: gitea.harkon.co.uk/harkon/svc-ocr:latest
container_name: svc-ocr
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-ocr.loadbalancer.server.port=8000"
# RAG Indexer Service
svc-rag-indexer:
image: gitea.harkon.co.uk/harkon/svc-rag-indexer:latest
container_name: svc-rag-indexer
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-rag-indexer.loadbalancer.server.port=8000"
# Reasoning Service
svc-reason:
image: gitea.harkon.co.uk/harkon/svc-reason:latest
container_name: svc-reason
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-reason.loadbalancer.server.port=8000"
# RPA Service
svc-rpa:
image: gitea.harkon.co.uk/harkon/svc-rpa:latest
container_name: svc-rpa
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-rpa.loadbalancer.server.port=8000"
# Normalize & Map Service
svc-normalize-map:
image: gitea.harkon.co.uk/harkon/svc-normalize-map:latest
container_name: svc-normalize-map
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-normalize-map.loadbalancer.server.port=8000"
# Coverage Service
svc-coverage:
image: gitea.harkon.co.uk/harkon/svc-coverage:latest
container_name: svc-coverage
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-coverage.loadbalancer.server.port=8000"
# Firm Connectors Service
svc-firm-connectors:
image: gitea.harkon.co.uk/harkon/svc-firm-connectors:latest
container_name: svc-firm-connectors
restart: unless-stopped
networks:
- backend
- frontend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ROOT_USER}
- MINIO_SECRET_KEY=${MINIO_ROOT_PASSWORD}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE}
- NATS_SERVERS=${NATS_SERVERS}
- NATS_STREAM_NAME=${NATS_STREAM_NAME}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP}
labels:
- "traefik.enable=true"
- "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.services.svc-firm-connectors.loadbalancer.server.port=8000"
# Review UI
ui-review:
image: gitea.harkon.co.uk/harkon/ui-review:latest
container_name: ui-review
restart: unless-stopped
networks:
- frontend
environment:
- NEXTAUTH_URL=https://app.${DOMAIN}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
- API_BASE_URL=https://api.${DOMAIN}
labels:
- "traefik.enable=true"
- "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.middlewares=authentik-forwardauth@file"
- "traefik.http.services.ui-review.loadbalancer.server.port=3030"

23
infra/certs/local.crt Normal file
View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIJAKln1RPU8Us4MA0GCSqGSIb3DQEBCwUAMBAxDjAMBgNV
BAMMBWxvY2FsMB4XDTI1MDkxOTA3NDg1N1oXDTM1MDkxNzA3NDg1N1owEDEOMAwG
A1UEAwwFbG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYyuta
8C2BBd8lzbbLCsemT3hMDrVrYfKab8Iog1wpRWImuupyToWUaqgc0noy0GvaMM08
SV72cjEOOgvcXwEu1a1hUow00uc7Mm1qjWZGIjD2QxnydpibmnhKWtEGkVZpZXk1
aZ6cYiv6w60fGDRNbLdZHUHtq0IC8szVUbvC4WA3W1aFMxWkP3oXyhdUM/i9P/y8
njyW+QZdCYeX9zUPH6CpMeGSofGTvsWSwLCIiBXQHkDLzmUItTYZEQFMBbo/SwjW
VTQi5lWwQya/CKpnXQUldnMjvPOi4aZLVTnDPRjONILR0ZSUwijMdOaf2rNvN7C3
7WwSiy0V423ExXj/AgMBAAGjggEdMIIBGTCCARUGA1UdEQSCAQwwggEIgglsb2Nh
bGhvc3SHBH8AAAGCCyoubG9jYWwubGFugg5hdXRoLmxvY2FsLmxhboIRZ3JhZmFu
YS5sb2NhbC5sYW6CEHJldmlldy5sb2NhbC5sYW6CDWFwaS5sb2NhbC5sYW6CD3Zh
dWx0LmxvY2FsLmxhboIPbWluaW8ubG9jYWwubGFughNtaW5pby1hcGkubG9jYWwu
bGFughBxZHJhbnQubG9jYWwubGFugg9uZW80ai5sb2NhbC5sYW6CFHByb21ldGhl
dXMubG9jYWwubGFugg5sb2tpLmxvY2FsLmxhboIRdW5sZWFzaC5sb2NhbC5sYW6C
EXRyYWVmaWsubG9jYWwubGFuMA0GCSqGSIb3DQEBCwUAA4IBAQAGC2uAwxlKbRnH
QutOXJNKvcwZ8BnrIbVSdvuRRzaGDDXxjpFa35Z3QvmO+Qd6IqZHEEQvxRVZWtR6
eWr9jiqi/NmhbxKiXnC0QzWotW4/As4uMGPPbJQXn35EbnSpt0XBrYjXvkX6kRUq
WI3h0gH7sTrA8GObi9vZ5ySAGT0xxTxu9m4juBNqi++z6urr8exxoIMUNm9H49lW
u4euVbI5+CKm603P9+pgTQPIT32U6ciPcrp4NJDTQ1lbejCi1aM9HTrvATgH5kYU
2CsRU1oOOjn1kLyu+slk0T0HKOfDZtp6ByPrzQKnTz0TVLlXn7UBOimHEmPlXYW4
O8Q/a0tg
-----END CERTIFICATE-----

28
infra/certs/local.key Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCYyuta8C2BBd8l
zbbLCsemT3hMDrVrYfKab8Iog1wpRWImuupyToWUaqgc0noy0GvaMM08SV72cjEO
OgvcXwEu1a1hUow00uc7Mm1qjWZGIjD2QxnydpibmnhKWtEGkVZpZXk1aZ6cYiv6
w60fGDRNbLdZHUHtq0IC8szVUbvC4WA3W1aFMxWkP3oXyhdUM/i9P/y8njyW+QZd
CYeX9zUPH6CpMeGSofGTvsWSwLCIiBXQHkDLzmUItTYZEQFMBbo/SwjWVTQi5lWw
Qya/CKpnXQUldnMjvPOi4aZLVTnDPRjONILR0ZSUwijMdOaf2rNvN7C37WwSiy0V
423ExXj/AgMBAAECggEACGI5/8dl98pmsCBVg1aYFdwOcb3s3nOFaEvxj1+F0w3n
kNB4xMTiN36SsuIpqlgdUt+So1gzSbqCTpGIzRK5ceRvmwN4hf18ipb9wfb4Qajm
ntyXs+ImBYO4TfwltAKNh0L2H6Qn+9S3LQ9HlIkzdXwdo1ojn/LhsF+6NYpCjzLQ
6m3RT4n9HEFB4N/S4WHJKL5RHrwlQ9A9o6t/BwLIMbUKDNZWHpcyrX/Ne3tzoYtd
9csQ3GGGiVoqwB9m75nLEmB/atGK6sV7Zos6fa1Ln1mGKWRfkm+UYzuC/S1RPEHq
LfbcQRGbgoEp7yoPZgLCEoX3xNp3vE+PJ+JVchZuYQKBgQDLOaU8msF6SApZwNjL
QG20hHu4m99pe0h8bDd9MMzbMuHRY5FZFv6MrffEDa48vAPiKcbUiJ0N4p7txQJ3
VBdz7ryZ92vE4H8R61IJRzpGVjuac/tWUpK5+pIF7TvGVrd8hS3i3so3m4AYmlW0
5HBvNIF0qjT0o1SZwlZBDtJ10wKBgQDAeIiO2x3XSPXB7plS018AjammlI2Pn207
WmLKuQw5E/d9smDSK4bT55gXyDWKgkPhjlk6LxbvSnyBHhVwshzJAf43ZByRaIht
V7D4YnbgdiOWVr8aDeEgHRH1vy5Z99SZuyQU/beWxasb2P36xKifi92nNSSiPEqL
9lQguIVYpQKBgQDDuvF6PVK7A0d0ylgC6jq+8hp24yl53lMiAtguqyGivI7hrJQA
yjTAKY3INaTquerDmJj3edxJ00pelrCZXVR5RCZB5BrXs6CvEYYhiYiG1ebyC2K2
8TCADuU08BfyHvL56wsWxpzckdf92idR4fKoKFnGk2gNdoG01YddgXkSIQKBgB7S
bpPp9QJn1atDyVvhK4KMLRHXEkBguH5bwBxUu+dcEjMX4LdnbwT6Pnn4ftJ6f+Jc
CF/v8I1LcVq/9ZEBhOiPoCVAq+6BPe+8rkNoiT7yzEokBCBo/pdE8H5ZKlQQAwTH
WkTeSIslhnxEKJAC9DnwjQNc2Ev+ubVmMhy3T+tdAoGAN99dnO11+qQ1jHEwIKeI
1JYeOizER62rk1CDStuGs92eiUF/eeYhzejgmk6pvtXeKgTw7vv4SKTfTxyg0pBY
uTd3dctKLcZqE5sb6vrHH/GD1/TbEY0hCgcDQWBiO6hoq3KR8qK2+SvvZCmxr3H8
zSB9dB99lRP1gfdPVyiwBr4=
-----END PRIVATE KEY-----

133
infra/compose/README.md Normal file
View File

@@ -0,0 +1,133 @@
# External Services
This directory contains Docker Compose configurations for external services that run on the production server.
## Services
### 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
### 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
```

View File

@@ -0,0 +1,127 @@
---
services:
authentik-server:
image: ghcr.io/goauthentik/server:2025.8.1
container_name: authentik-server
command: server
environment:
- AUTHENTIK_REDIS__HOST=authentik-redis
- AUTHENTIK_POSTGRESQL__HOST=authentik-postgres
- AUTHENTIK_POSTGRESQL__USER=${POSTGRES_USER:-authentik}
- AUTHENTIK_POSTGRESQL__NAME=${POSTGRES_DB:-authentik}
- AUTHENTIK_POSTGRESQL__PASSWORD=${POSTGRES_PASSWORD:?error}
- AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY:?error}
- AUTHENTIK_ERROR_REPORTING__ENABLED=${AUTHENTIK_ERROR_REPORTING:-false}
labels:
# (Optional) Enable Traefik integration for the Authentik Web UI. For more information
# about integrating other services with Traefik and Authentik, see the
# documentation at https://goauthentik.io/docs/outposts/integrations/traefik
# and the middleware example files in `docker-compose/traefik/config`.
- traefik.enable=true
- traefik.http.services.authentik.loadbalancer.server.port=9000
- traefik.http.services.authentik.loadbalancer.server.scheme=http
- traefik.http.routers.authentik.entrypoints=websecure
- traefik.http.routers.authentik.rule=Host(`authentik.harkon.co.uk`)
- traefik.http.routers.authentik.tls=true
- traefik.http.routers.authentik.tls.certresolver=godaddy
- traefik.http.routers.authentik.service=authentik
volumes:
- ./media:/media
- ./custom-templates:/templates
depends_on:
- authentik-postgres
- authentik-redis
networks:
- frontend
- backend
restart: unless-stopped
authentik-worker:
image: ghcr.io/goauthentik/server:2025.8.1
container_name: authentik-worker
command: worker
environment:
- AUTHENTIK_REDIS__HOST=authentik-redis
- AUTHENTIK_POSTGRESQL__HOST=authentik-postgres
- AUTHENTIK_POSTGRESQL__USER=${POSTGRES_USER:-authentik}
- AUTHENTIK_POSTGRESQL__NAME=${POSTGRES_DB:-authentik}
- AUTHENTIK_POSTGRESQL__PASSWORD=${POSTGRES_PASSWORD:?error}
- AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY:?error}
- AUTHENTIK_ERROR_REPORTING__ENABLED=${AUTHENTIK_ERROR_REPORTING:-false}
# (Optional) Enable Email Sending
# Highly recommended to notify you about alerts and configuration issues.
# - AUTHENTIK_EMAIL__HOST=${EMAIL_HOST:?error}
# - AUTHENTIK_EMAIL__PORT=${EMAIL_PORT:-25}
# - AUTHENTIK_EMAIL__USERNAME=${EMAIL_USERNAME:?error}
# - AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD:?error}
# - AUTHENTIK_EMAIL__USE_TLS=${EMAIL_USE_TLS:-false}
# - AUTHENTIK_EMAIL__USE_SSL=${EMAIL_USE_SSL:-false}
# - AUTHENTIK_EMAIL__TIMEOUT=${EMAIL_TIMEOUT:-10}
# - AUTHENTIK_EMAIL__FROM=${EMAIL_FROM:?error}
# (Optional) See more for the docker socket integration here:
# https://goauthentik.io/docs/outposts/integrations/docker
user: root
volumes:
- /run/docker.sock:/run/docker.sock
- ./media:/media
- ./certs:/certs
- ./custom-templates:/templates
depends_on:
- authentik-postgres
- authentik-redis
networks:
- backend
restart: unless-stopped
authentik-redis:
image: docker.io/library/redis:8.2.1
container_name: authentik-redis
command: --save 60 1 --loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
authentik-postgres:
# (Optional) Add a PostgreSQL Database for Authentik
# Alternatively, you can host your PostgreSQL database externally, and
# change the connection settings in the `authentik-server` and
# `authentik-worker`.
image: docker.io/library/postgres:17.6
container_name: authentik-db
environment:
- POSTGRES_USER=${POSTGRES_USER:-authentik}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?error}
- POSTGRES_DB=${POSTGRES_DB:-authentik}
- TZ=${TZ:-UTC}
healthcheck:
test: ["CMD-SHELL", 'pg_isready -U "${POSTGRES_USER:-authentik}"']
start_period: 30s
interval: 10s
timeout: 10s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend
restart: unless-stopped
volumes:
postgres_data:
driver: local
redis_data:
driver: local
networks:
frontend:
external: true
backend:
external: true

View File

@@ -0,0 +1,990 @@
# FILE: infra/compose/docker-compose.local.yml
# Traefik (with Authentik ForwardAuth), Authentik, Vault, MinIO, Qdrant, Neo4j, Postgres, Redis, Prometheus/Grafana, Loki, Unleash, all services
networks:
frontend:
external: true
name: ai-tax-agent-frontend
backend:
external: true
name: ai-tax-agent-backend
volumes:
postgres_data:
neo4j_data:
neo4j_logs:
qdrant_data:
minio_data:
vault_data:
redis_data:
nats_data:
prometheus_data:
grafana_data:
loki_data:
authentik_data:
portainer-data:
services:
# Identity & SSO
authentik-db:
image: postgres:15-alpine
container_name: authentik-db
restart: unless-stopped
networks:
- backend
volumes:
- authentik_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD:-authentik}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U authentik"]
interval: 30s
timeout: 10s
retries: 3
authentik-redis:
image: redis:7-alpine
container_name: authentik-redis
restart: unless-stopped
networks:
- backend
command: --save 60 1 --loglevel warning
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 30s
timeout: 10s
retries: 3
authentik-server:
image: ghcr.io/goauthentik/server:2025.8.3
container_name: authentik-server
restart: unless-stopped
networks:
- backend
- frontend
command: server
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-changeme}
AUTHENTIK_ERROR_REPORTING__ENABLED: false
# Optional bootstrap for automated setup (create admin and API token)
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@local.lan}
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin123}
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN:-}
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
- ./authentik/bootstrap.yaml:/blueprints/bootstrap.yaml
depends_on:
- authentik-db
- authentik-redis
labels:
- "traefik.enable=true"
- "traefik.http.routers.authentik.rule=Host(`auth.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.authentik.entrypoints=websecure"
- "traefik.http.routers.authentik.tls=true"
- "traefik.docker.network=ai-tax-agent-frontend"
- "traefik.http.services.authentik.loadbalancer.server.port=9000"
authentik-worker:
image: ghcr.io/goauthentik/server:2025.8.3
container_name: authentik-worker
restart: unless-stopped
networks:
- backend
command: worker
environment:
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-changeme}
AUTHENTIK_ERROR_REPORTING__ENABLED: false
volumes:
- ./authentik/media:/media
- ./authentik/custom-templates:/templates
depends_on:
- authentik-db
- authentik-redis
authentik-outpost:
image: ghcr.io/goauthentik/proxy:2025.8.3
container_name: authentik-outpost
restart: unless-stopped
networks:
- backend
- frontend
environment:
AUTHENTIK_HOST: http://authentik-server:9000
AUTHENTIK_INSECURE: true
AUTHENTIK_TOKEN: ${AUTHENTIK_OUTPOST_TOKEN:-changeme}
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_REDIS__PORT: 6379
depends_on:
- authentik-server
- authentik-redis
# Secrets Management
vault:
image: hashicorp/vault:1.15
container_name: vault
restart: unless-stopped
networks:
- backend
ports:
- "8200:8200"
volumes:
- vault_data:/vault/data
- ./vault/config:/vault/config:ro
environment:
VAULT_DEV_ROOT_TOKEN_ID: ${VAULT_DEV_ROOT_TOKEN_ID:-root}
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
command: vault server -dev -dev-listen-address=0.0.0.0:8200
cap_add:
- IPC_LOCK
labels:
- "traefik.enable=true"
- "traefik.http.routers.vault.rule=Host(`vault.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.vault.entrypoints=websecure"
- "traefik.http.routers.vault.tls=true"
- "traefik.http.routers.vault.middlewares=authentik-forwardauth@file"
- "traefik.http.services.vault.loadbalancer.server.port=8200"
# Object Storage
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: minio
restart: unless-stopped
networks:
- backend
ports:
- "9092:9092"
- "9093:9093"
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniopass}
MINIO_BROWSER_REDIRECT_URL: https://minio.${DOMAIN:-local.lan}
command: server /data --address ":9092" --console-address ":9093"
healthcheck:
test: ["CMD", "mc", "--version"]
interval: 30s
timeout: 20s
retries: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.minio-api.rule=Host(`minio-api.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.minio-api.entrypoints=websecure"
- "traefik.http.routers.minio-api.tls=true"
- "traefik.http.routers.minio-api.middlewares=authentik-forwardauth@file"
- "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:-local.lan}`)"
- "traefik.http.routers.minio-console.entrypoints=websecure"
- "traefik.http.routers.minio-console.tls=true"
- "traefik.http.routers.minio-console.middlewares=authentik-forwardauth@file"
- "traefik.http.routers.minio-console.service=minio-console"
- "traefik.http.services.minio-console.loadbalancer.server.port=9093"
# Vector Database
qdrant:
image: qdrant/qdrant:v1.7.4
container_name: qdrant
restart: unless-stopped
networks:
- backend
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
environment:
QDRANT__SERVICE__GRPC_PORT: ${QDRANT__SERVICE__GRPC_PORT:-6334}
QDRANT__SERVICE__HTTP_PORT: 6333
QDRANT__LOG_LEVEL: INFO
labels:
- "traefik.enable=true"
- "traefik.http.routers.qdrant.rule=Host(`qdrant.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.qdrant.entrypoints=websecure"
- "traefik.http.routers.qdrant.tls=true"
- "traefik.http.routers.qdrant.middlewares=authentik-forwardauth@file"
- "traefik.http.services.qdrant.loadbalancer.server.port=6333"
# Knowledge Graph Database
neo4j:
image: neo4j:5.15-community
container_name: neo4j
restart: unless-stopped
networks:
- backend
ports:
- "7474:7474"
- "7687:7687"
volumes:
- neo4j_data:/data
- neo4j_logs:/logs
- ./neo4j/plugins:/plugins
environment:
NEO4J_AUTH: neo4j/${NEO4J_PASSWORD:-neo4jpass}
NEO4J_PLUGINS: '["apoc", "graph-data-science"]'
NEO4J_dbms_security_procedures_unrestricted: gds.*,apoc.*
NEO4J_dbms_security_procedures_allowlist: gds.*,apoc.*
NEO4J_apoc_export_file_enabled: true
NEO4J_apoc_import_file_enabled: true
NEO4J_apoc_import_file_use__neo4j__config: true
labels:
- "traefik.enable=true"
- "traefik.http.routers.neo4j.rule=Host(`neo4j.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.neo4j.entrypoints=websecure"
- "traefik.http.routers.neo4j.tls=true"
- "traefik.http.routers.neo4j.middlewares=authentik-forwardauth@file"
- "traefik.http.services.neo4j.loadbalancer.server.port=7474"
# Secure Client Data Store
postgres:
image: postgres:15-alpine
container_name: postgres
restart: unless-stopped
networks:
- backend
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./postgres/init:/docker-entrypoint-initdb.d
environment:
POSTGRES_DB: tax_system
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
command: >
postgres
-c shared_preload_libraries=pg_stat_statements
-c pg_stat_statements.track=all
-c max_connections=200
-c shared_buffers=256MB
-c effective_cache_size=1GB
-c maintenance_work_mem=64MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
-c random_page_cost=1.1
-c effective_io_concurrency=200
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 10s
retries: 3
# Cache & Session Store
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
networks:
- backend
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: >
redis-server
--appendonly yes
--appendfsync everysec
--maxmemory 512mb
--maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
interval: 30s
timeout: 10s
retries: 3
# Message Broker & Event Streaming
nats:
image: nats:2.10-alpine
container_name: nats
restart: unless-stopped
networks:
- backend
ports:
- "4222:4222" # NATS client connections
- "8222:8222" # HTTP monitoring
- "6222:6222" # Cluster routing (for future clustering)
volumes:
- nats_data:/data
command: >
--jetstream
--store_dir=/data
--http_port=8222
environment:
NATS_LOG_LEVEL: ${NATS_LOG_LEVEL:-info}
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://localhost:8222/healthz",
]
interval: 30s
timeout: 10s
retries: 3
labels:
- "traefik.enable=true"
- "traefik.http.routers.nats-monitor.rule=Host(`nats.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.nats-monitor.entrypoints=websecure"
- "traefik.http.routers.nats-monitor.tls=true"
- "traefik.http.routers.nats-monitor.middlewares=authentik-forwardauth@file"
- "traefik.http.services.nats-monitor.loadbalancer.server.port=8222"
# Monitoring & Observability
prometheus:
image: prom/prometheus:v2.48.1
container_name: prometheus
restart: unless-stopped
networks:
- backend
ports:
- "9090:9090"
volumes:
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/etc/prometheus/console_libraries"
- "--web.console.templates=/etc/prometheus/consoles"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
labels:
- "traefik.enable=true"
- "traefik.http.routers.prometheus.rule=Host(`prometheus.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.prometheus.entrypoints=websecure"
- "traefik.http.routers.prometheus.tls=true"
- "traefik.http.routers.prometheus.middlewares=authentik-forwardauth@file"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
grafana:
image: grafana/grafana:10.2.3
container_name: grafana
restart: unless-stopped
networks:
- backend
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: false
GF_USERS_AUTO_ASSIGN_ORG: true
GF_USERS_AUTO_ASSIGN_ORG_ROLE: Viewer
GF_AUTH_GENERIC_OAUTH_ENABLED: true
GF_AUTH_GENERIC_OAUTH_NAME: Authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: ${GRAFANA_OAUTH_CLIENT_ID:-grafana}
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ${GRAFANA_OAUTH_CLIENT_SECRET:-changeme-grafana-secret}
GF_AUTH_GENERIC_OAUTH_SCOPES: openid profile email groups
GF_AUTH_GENERIC_OAUTH_AUTH_URL: https://auth.${DOMAIN:-local.lan}/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL: https://auth.${DOMAIN:-local.lan}/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL: https://auth.${DOMAIN:-local.lan}/application/o/userinfo/
GF_AUTH_GENERIC_OAUTH_AUTO_LOGIN: false
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: true
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: role
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_STRICT: false
GF_AUTH_GENERIC_OAUTH_GROUPS_ATTRIBUTE_PATH: groups
GF_AUTH_OAUTH_AUTO_LOGIN: false
GF_AUTH_DISABLE_LOGIN_FORM: false
# Cookie and security settings
GF_SERVER_ROOT_URL: https://grafana.${DOMAIN:-local.lan}
GF_SERVER_SERVE_FROM_SUB_PATH: false
GF_SECURITY_COOKIE_SECURE: false
GF_SECURITY_COOKIE_SAMESITE: lax
GF_AUTH_GENERIC_OAUTH_USE_PKCE: true
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.grafana.entrypoints=websecure"
- "traefik.http.routers.grafana.tls=true"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
loki:
image: grafana/loki:2.9.4
container_name: loki
restart: unless-stopped
networks:
- backend
ports:
- "3100:3100"
volumes:
- loki_data:/loki
labels:
- "traefik.enable=true"
- "traefik.http.routers.loki.rule=Host(`loki.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.loki.entrypoints=websecure"
- "traefik.http.routers.loki.tls=true"
- "traefik.http.routers.loki.middlewares=authentik-forwardauth@file"
- "traefik.http.services.loki.loadbalancer.server.port=3100"
# Feature Flags
unleash:
image: unleashorg/unleash-server:5.7.3
container_name: unleash
restart: unless-stopped
networks:
- frontend
- backend
ports:
- "4242:4242"
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/unleash
DATABASE_SSL: false
LOG_LEVEL: info
depends_on:
- postgres
labels:
- "traefik.docker.network=ai-tax-agent-frontend"
- "traefik.enable=true"
- "traefik.http.routers.unleash.rule=Host(`unleash.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.unleash.entrypoints=websecure"
- "traefik.http.routers.unleash.tls=true"
- "traefik.http.routers.unleash.middlewares=authentik-forwardauth@file"
- "traefik.http.services.unleash.loadbalancer.server.port=4242"
# Application Services
svc-ingestion:
build:
context: ../../
dockerfile: apps/svc_ingestion/Dockerfile
container_name: svc-ingestion
restart: unless-stopped
networks:
- backend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- MINIO_ENDPOINT=minio:9092
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- REDIS_URL=redis://redis:6379
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- vault
- minio
- postgres
- redis
- nats
- neo4j
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-ingestion.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/ingestion`)"
- "traefik.http.routers.svc-ingestion.entrypoints=websecure"
- "traefik.http.routers.svc-ingestion.tls=true"
- "traefik.http.routers.svc-ingestion.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-ingestion.loadbalancer.server.port=8000"
svc-extract:
build:
context: ../../
dockerfile: apps/svc_extract/Dockerfile
container_name: svc-extract
restart: unless-stopped
networks:
- backend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- MINIO_ENDPOINT=minio:9092
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- RAG_EMBEDDING_MODEL=${RAG_EMBEDDING_MODEL:-bge-small-en-v1.5}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- vault
- minio
- postgres
- nats
- neo4j
- redis
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-extract.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/extract`)"
- "traefik.http.routers.svc-extract.entrypoints=websecure"
- "traefik.http.routers.svc-extract.tls=true"
- "traefik.http.routers.svc-extract.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-extract.loadbalancer.server.port=8000"
svc-kg:
build:
context: ../../
dockerfile: apps/svc_kg/Dockerfile
container_name: svc-kg
restart: unless-stopped
networks:
- backend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-neo4jpass}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- vault
- neo4j
- nats
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-kg.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/kg`)"
- "traefik.http.routers.svc-kg.entrypoints=websecure"
- "traefik.http.routers.svc-kg.tls=true"
- "traefik.http.routers.svc-kg.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-kg.loadbalancer.server.port=8000"
svc-rag-retriever:
build:
context: ../../
dockerfile: apps/svc_rag_retriever/Dockerfile
container_name: svc-rag-retriever
restart: unless-stopped
networks:
- backend
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- QDRANT_URL=http://qdrant:6333
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-neo4jpass}
- RAG_EMBEDDING_MODEL=${RAG_EMBEDDING_MODEL:-bge-small-en-v1.5}
- RAG_RERANKER_MODEL=${RAG_RERANKER_MODEL:-cross-encoder/ms-marco-MiniLM-L-6-v2}
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- vault
- qdrant
- neo4j
- nats
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-rag-retriever.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/rag`)"
- "traefik.http.routers.svc-rag-retriever.entrypoints=websecure"
- "traefik.http.routers.svc-rag-retriever.tls=true"
- "traefik.http.routers.svc-rag-retriever.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-rag-retriever.loadbalancer.server.port=8000"
svc-coverage:
build:
context: ../../
dockerfile: apps/svc_coverage/Dockerfile
container_name: svc-coverage
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-neo4jpass}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- RAG_SERVICE_URL=http://svc-rag-retriever:8000
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- vault
- neo4j
- postgres
- nats
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-coverage.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/coverage`)"
- "traefik.http.routers.svc-coverage.entrypoints=websecure"
- "traefik.http.routers.svc-coverage.tls=true"
- "traefik.http.routers.svc-coverage.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-coverage.loadbalancer.server.port=8000"
svc-firm-connectors:
build:
context: ../../
dockerfile: apps/svc_firm_connectors/Dockerfile
container_name: svc-firm-connectors
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-firm-connectors.rule=Host(`api.${DOMAIN:-local.lan}`) && 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.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-firm-connectors.loadbalancer.server.port=8000"
svc-forms:
build:
context: ../../
dockerfile: apps/svc_forms/Dockerfile
container_name: svc-forms
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-forms.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/forms`)"
- "traefik.http.routers.svc-forms.entrypoints=websecure"
- "traefik.http.routers.svc-forms.tls=true"
- "traefik.http.routers.svc-forms.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-forms.loadbalancer.server.port=8000"
svc-hmrc:
build:
context: ../../
dockerfile: apps/svc_hmrc/Dockerfile
container_name: svc-hmrc
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-hmrc.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/hmrc`)"
- "traefik.http.routers.svc-hmrc.entrypoints=websecure"
- "traefik.http.routers.svc-hmrc.tls=true"
- "traefik.http.routers.svc-hmrc.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-hmrc.loadbalancer.server.port=8000"
svc-normalize-map:
build:
context: ../../
dockerfile: apps/svc_normalize_map/Dockerfile
container_name: svc-normalize-map
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-normalize-map.rule=Host(`api.${DOMAIN:-local.lan}`) && 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.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-normalize-map.loadbalancer.server.port=8000"
svc-ocr:
build:
context: ../../
dockerfile: apps/svc_ocr/Dockerfile
container_name: svc-ocr
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-ocr.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/ocr`)"
- "traefik.http.routers.svc-ocr.entrypoints=websecure"
- "traefik.http.routers.svc-ocr.tls=true"
- "traefik.http.routers.svc-ocr.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-ocr.loadbalancer.server.port=8000"
svc-rag-indexer:
build:
context: ../../
dockerfile: apps/svc_rag_indexer/Dockerfile
container_name: svc-rag-indexer
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-rag-indexer.rule=Host(`api.${DOMAIN:-.lan}`) && 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.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-rag-indexer.loadbalancer.server.port=8000"
svc-reason:
build:
context: ../../
dockerfile: apps/svc_reason/Dockerfile
container_name: svc-reason
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-reason.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/reason`)"
- "traefik.http.routers.svc-reason.entrypoints=websecure"
- "traefik.http.routers.svc-reason.tls=true"
- "traefik.http.routers.svc-reason.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-reason.loadbalancer.server.port=8000"
svc-rpa:
build:
context: ../../
dockerfile: apps/svc_rpa/Dockerfile
container_name: svc-rpa
restart: unless-stopped
networks:
- backend
volumes:
- ../../config:/app/config:ro
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_DEV_ROOT_TOKEN_ID:-root}
- POSTGRES_URL=postgresql://postgres:${POSTGRES_PASSWORD:-postgres}@postgres:5432/tax_system
- NEO4J_URL=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-password}
- REDIS_URL=redis://redis:6379
- MINIO_ENDPOINT=minio:9092
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- QDRANT_URL=http://qdrant:6333
- EVENT_BUS_TYPE=${EVENT_BUS_TYPE:-memory}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS:-}
- NATS_SERVERS=${NATS_SERVERS:-nats://nats:4222}
- NATS_STREAM_NAME=${NATS_STREAM_NAME:-TAX_AGENT_EVENTS}
- NATS_CONSUMER_GROUP=${NATS_CONSUMER_GROUP:-tax-agent}
depends_on:
- postgres
- neo4j
- minio
- qdrant
- nats
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.svc-rpa.rule=Host(`api.${DOMAIN:-local.lan}`) && PathPrefix(`/rpa`)"
- "traefik.http.routers.svc-rpa.entrypoints=websecure"
- "traefik.http.routers.svc-rpa.tls=true"
- "traefik.http.routers.svc-rpa.middlewares=authentik-forwardauth@file,rate-limit@file"
- "traefik.http.services.svc-rpa.loadbalancer.server.port=8000"
ui-review:
build:
context: ../../ui-review
dockerfile: Dockerfile
container_name: ui-review
restart: unless-stopped
networks:
- frontend
environment:
- NEXTAUTH_URL=https://review.${DOMAIN:-local.lan}
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-changeme}
- API_BASE_URL=https://api.${DOMAIN:-local.lan}
depends_on:
- traefik
labels:
- "traefik.docker.network=ai-tax-agent-frontend"
- "traefik.enable=true"
- "traefik.http.routers.ui-review.rule=Host(`review.${DOMAIN:-local.lan}`)"
- "traefik.http.routers.ui-review.entrypoints=websecure"
- "traefik.http.routers.ui-review.tls=true"
- "traefik.http.routers.ui-review.middlewares=authentik-forwardauth@file"
- "traefik.http.services.ui-review.loadbalancer.server.port=3030"

File diff suppressed because it is too large Load Diff

106
infra/compose/env.example Normal file
View File

@@ -0,0 +1,106 @@
# FILE: infra/compose/env.example
# Domain Configuration
DOMAIN=local
EMAIL=admin@local.lan
# Database Passwords
POSTGRES_PASSWORD=postgres
NEO4J_PASSWORD=neo4jpass
AUTHENTIK_DB_PASSWORD=authentik
# Object Storage
MINIO_ROOT_USER=minio
MINIO_ROOT_PASSWORD=miniopass
MINIO_ACCESS_KEY=minio
MINIO_SECRET_KEY=miniopass
# Vector Database
QDRANT__SERVICE__GRPC_PORT=6334
# Secrets Management
VAULT_DEV_ROOT_TOKEN_ID=root
# Identity & SSO
AUTHENTIK_SECRET_KEY=changeme
AUTHENTIK_OUTPOST_TOKEN=changeme
AUTHENTIK_BOOTSTRAP_EMAIL=admin@local.lan
AUTHENTIK_BOOTSTRAP_PASSWORD=admin123
AUTHENTIK_BOOTSTRAP_TOKEN=
# Monitoring
GRAFANA_PASSWORD=admin
GRAFANA_OAUTH_CLIENT_ID=grafana
GRAFANA_OAUTH_CLIENT_SECRET=changeme
# OAuth Client Secrets for Authentik Providers
AUTHENTIK_API_CLIENT_SECRET=changeme-api-secret
AUTHENTIK_UI_REVIEW_CLIENT_SECRET=changeme-ui-review-secret
AUTHENTIK_GRAFANA_CLIENT_SECRET=changeme-grafana-secret
AUTHENTIK_MINIO_CLIENT_SECRET=changeme-minio-secret
AUTHENTIK_VAULT_CLIENT_SECRET=changeme-vault-secret
# Feature Flags
UNLEASH_ADMIN_TOKEN=development.unleash-insecure-admin-api-token
# Application Configuration
NEXTAUTH_SECRET=changeme
# RAG & ML Models
RAG_EMBEDDING_MODEL=bge-small-en-v1.5
RAG_RERANKER_MODEL=cross-encoder/ms-marco-MiniLM-L-6-v2
RAG_ALPHA_BETA_GAMMA=0.5,0.3,0.2
# HMRC Integration
HMRC_MTD_ITSA_MODE=sandbox
# Rate Limits
RATE_LIMITS_HMRC_API_RPS=3
RATE_LIMITS_HMRC_API_BURST=6
RATE_LIMITS_LLM_API_RPS=10
RATE_LIMITS_LLM_API_BURST=20
# Confidence Thresholds
CONFIDENCE_AUTO_SUBMIT=0.95
CONFIDENCE_HUMAN_REVIEW=0.85
CONFIDENCE_REJECT=0.50
# Logging
LOG_LEVEL=INFO
LOG_FORMAT=json
# Development Settings
DEBUG=false
DEVELOPMENT_MODE=true
# Security
ENCRYPTION_KEY_ID=default
AUDIT_LOG_RETENTION_DAYS=90
PII_LOG_RETENTION_DAYS=30
# Backup & DR
BACKUP_ENABLED=true
BACKUP_SCHEDULE=0 2 * * *
BACKUP_RETENTION_DAYS=30
# Performance Tuning
MAX_WORKERS=4
BATCH_SIZE=100
CACHE_TTL_SECONDS=3600
CONNECTION_POOL_SIZE=20
# Feature Flags
FEATURE_RAG_ENABLED=true
FEATURE_FIRM_CONNECTORS_ENABLED=false
FEATURE_HMRC_SUBMISSION_ENABLED=false
FEATURE_ADVANCED_CALCULATIONS_ENABLED=true
# Event Bus Configuration
EVENT_BUS_TYPE=memory
KAFKA_BOOTSTRAP_SERVERS=
# NATS Configuration
NATS_SERVERS=nats://nats:4222
NATS_STREAM_NAME=TAX_AGENT_EVENTS
NATS_CONSUMER_GROUP=tax-agent
NATS_LOG_LEVEL=info

View File

@@ -0,0 +1,63 @@
---
services:
gitea-server:
image: docker.io/gitea/gitea:1.24.5
container_name: gitea-server
env_file:
- ./.env # contains the GoDaddy API Key and Secret
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=${POSTGRES_HOST:-gitea-postgres}:${POSTGRES_PORT:-5432}
- GITEA__database__NAME=${POSTGRES_DB:-gitea}
- GITEA__database__USER=${POSTGRES_USER:-gitea}
- GITEA__database__PASSWD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
- GITEA__server__SSH_PORT=2221 # <-- (Optional) Replace with your desired SSH port
- GITEA__server__ROOT_URL=https://gitea.harkon.co.uk # <-- Replace with your FQDN
networks:
- frontend
- backend
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "2221:22" # <-- (Optional) Replace with your desired SSH port
depends_on:
- gitea-postgres
labels:
- traefik.enable=true
- traefik.http.services.gitea.loadbalancer.server.port=3000
- traefik.http.services.gitea.loadbalancer.server.scheme=http
- traefik.http.routers.gitea-https.entrypoints=websecure
- traefik.http.routers.gitea-https.rule=Host(`gitea.harkon.co.uk`) # <-- Replace with your FQDN
- traefik.http.routers.gitea-https.tls=true
- traefik.http.routers.gitea-https.tls.certresolver=godaddy # <-- Replace with your certresolver
- traefik.http.routers.gitea.service=gitea
restart: unless-stopped
gitea-postgres:
image: docker.io/library/postgres:17.5
container_name: gitea-postgres
environment:
- POSTGRES_USER=${POSTGRES_USER:-gitea}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD not set}
- POSTGRES_DB=${POSTGRES_DB:-gitea}
networks:
- backend
volumes:
- gitea-db:/var/lib/postgresql/data
restart: unless-stopped
volumes:
gitea-data:
driver: local
gitea-db:
driver: local
networks:
frontend:
external: true
backend:
external: true

View File

@@ -0,0 +1,104 @@
# /opt/compose/nextcloud/compose.yml
networks:
frontend:
external: true
backend:
external: true
volumes:
nextcloud_html:
nextcloud_data:
nextcloud_config:
nextcloud_apps:
nextcloud_postgres:
nextcloud_redis:
services:
nextcloud-postgres:
image: postgres:16-alpine
container_name: nextcloud-postgres
restart: unless-stopped
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
volumes:
- nextcloud_postgres:/var/lib/postgresql/data
networks: [backend]
nextcloud-redis:
image: redis:7-alpine
container_name: nextcloud-redis
restart: unless-stopped
command:
[
"redis-server",
"--appendonly",
"yes",
"--requirepass",
"${REDIS_PASSWORD}",
]
volumes:
- nextcloud_redis:/data
networks: [backend]
nextcloud-server:
image: nextcloud:apache
container_name: nextcloud-server
restart: unless-stopped
depends_on: [nextcloud-postgres, nextcloud-redis]
env_file:
- ./.env
environment:
# DB
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
POSTGRES_HOST: nextcloud-postgres
# Initial admin (used only on first run)
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
# Reverse frontend awareness
NEXTCLOUD_TRUSTED_DOMAINS: cloud.harkon.co.uk
OVERWRITEHOST: cloud.harkon.co.uk
OVERWRITEPROTOCOL: https
# Redis for locks/cache
REDIS_HOST: nextcloud-redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
volumes:
- nextcloud_html:/var/www/html
- nextcloud_data:/var/www/html/data
- nextcloud_config:/var/www/html/config
- nextcloud_apps:/var/www/html/custom_apps
networks:
- frontend # for Traefik
- backend # for DB/Redis
labels:
- traefik.enable=true
- traefik.http.routers.nextcloud.rule=Host(`cloud.harkon.co.uk`)
- traefik.http.routers.nextcloud.entrypoints=websecure
- traefik.http.routers.nextcloud.tls=true
- traefik.http.routers.nextcloud.tls.certresolver=godaddy
- traefik.http.services.nextcloud.loadbalancer.server.port=80
- traefik.http.routers.nextcloud.service=nextcloud
# Run background jobs as a separate container
cron:
image: nextcloud:apache
container_name: nextcloud-cron
restart: unless-stopped
depends_on: [nc_db, nc_redis]
entrypoint: /cron.sh
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${NEXTCLOUD_DB_PASSWORD}
POSTGRES_HOST: db
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
volumes:
- nextcloud_html:/var/www/html
- nextcloud_data:/var/www/html/data
- nextcloud_config:/var/www/html/config
- nextcloud_apps:/var/www/html/custom_apps
networks: [backend]

View File

@@ -0,0 +1,27 @@
---
services:
app:
container_name: portainer
image: docker.io/portainer/portainer-ce:2.33.1-alpine
volumes:
- /run/docker.sock:/var/run/docker.sock
- portainer-data:/data
labels:
- traefik.enable=true
- traefik.http.services.portainer.loadbalancer.server.port=9000
- traefik.http.routers.portainer.service=portainer
- traefik.http.routers.portainer.entrypoints=websecure
- traefik.http.routers.portainer.rule=Host(`portainer.harkon.co.uk`)
- traefik.http.routers.portainer.tls=true
- traefik.http.routers.portainer.tls.certresolver=godaddy
networks:
- frontend
restart: unless-stopped
volumes:
portainer-data:
driver: local
networks:
frontend:
external: true

View File

@@ -0,0 +1,39 @@
# FILE: infra/compose/traefik/compose.yaml
# there is another traefik instance in the infra used by the application.
# Current instance used for company services on the dev environment.
# TODO: Unify the two traefik instances.
---
services:
traefik:
image: docker.io/library/traefik:v3.5.1
container_name: traefik
ports:
- 80:80
- 443:443
# --> (Optional) Enable Dashboard, don't do in production
# - 8080:8080
# <--
volumes:
- /run/docker.sock:/run/docker.sock:ro
- ./config/:/etc/traefik/:ro
- ./certs/:/var/traefik/certs/:rw
environment:
- CF_DNS_API_TOKEN=your-cloudflare-api-token # <-- Change this to your Cloudflare API Token
env_file:
- ./.provider.env # contains the GoDaddy API Key and Secret
networks:
- frontend
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.middlewares.basicauth.basicauth.users=admin:$2y$05$/B2hjJGytCjjMK4Rah1/aeJofBrzqEnAVoZCMKKwetS9mgmck.MVS
- traefik.http.routers.traefik.rule=Host(`traefik.harkon.co.uk`)
- traefik.http.routers.traefik.entrypoints=websecure
- traefik.http.routers.traefik.tls.certresolver=le
- traefik.http.routers.traefik.middlewares=basicauth@docker
- traefik.http.routers.traefik.service=api@internal
networks:
frontend:
external: true # <-- (Optional) Change this to false if you want to create a new network
#

View File

@@ -0,0 +1,21 @@
# --> (Example) Expose an external service using Traefik...
# http:
# # -- Change Router Configuration here...
# routers:
# your-local-router:
# rule: "Host(`your-local-service.your-domain.com`)" # <-- Change Rules here...
# service: your-local-service # <-- Change Service Name here...
# priority: 1000 # <-- (Optional) Change Routing Priority here...
# entryPoints:
# - web
# - websecure
# tls:
# certResolver: cloudflare
#
# # -- Change Service Configuration here...
# services:
# your-local-service: # <-- Change Service Name here...
# loadBalancer:
# servers:
# - url: "http://your-local-service:port" # <-- Change Target Service URL here...
# <--

View File

@@ -0,0 +1,19 @@
# --> (Example) Securely expose apps using the Traefik proxy outpost...
http:
middlewares:
authentik:
forwardAuth:
address: http://authentik-server: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

View File

@@ -0,0 +1,22 @@
# --> (Optional) When using Passbolt with Traefik...
# http:
# middlewares:
# passbolt-middleware:
# headers:
# FrameDeny: true
# AccessControlAllowMethods: 'GET,OPTIONS,PUT'
# AccessControlAllowOriginList:
# - origin-list-or-null
# AccessControlMaxAge: 100
# AddVaryHeader: true
# BrowserXssFilter: true
# ContentTypeNosniff: true
# ForceSTSHeader: true
# STSIncludeSubdomains: true
# STSPreload: true
# ContentSecurityPolicy: default-src 'self' 'unsafe-inline'
# CustomFrameOptionsValue: SAMEORIGIN
# ReferrerPolicy: same-origin
# PermissionsPolicy: vibrate 'self'
# STSSeconds: 315360000
# <--

View File

@@ -0,0 +1,18 @@
# --> (Example) Change TLS Configuration here...
# tls:
# options:
# default:
# minVersion: VersionTLS12
# sniStrict: true
# curvePreferences:
# - CurveP256
# - CurveP384
# - CurveP521
# cipherSuites:
# - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
# - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
# - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
# - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
# <--

View File

@@ -0,0 +1,64 @@
---
global:
checkNewVersion: false
sendAnonymousUsage: false
# --> (Optional) Change log level and format here ...
# - level: [TRACE, DEBUG, INFO, WARN, ERROR, FATAL]
log:
level: DEBUG
# <--
# --> (Optional) Enable accesslog here ...
accesslog: {}
# <--
# --> (Optional) Enable API and Dashboard here, don't do in production
api:
dashboard: true
insecure: true
# <--
# -- Change EntryPoints here...
entryPoints:
web:
address: :80
# --> (Optional) Redirect all HTTP to HTTPS
http:
redirections:
entryPoint:
to: websecure
scheme: https
# <--
websecure:
address: :443
# -- 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
# --> (Optional) Disable TLS Cert verification check
# serversTransport:
# insecureSkipVerify: true
# <--
providers:
docker:
exposedByDefault: false # <-- (Optional) Change this to true if you want to expose all services
# Specify discovery network - This ensures correct name resolving and possible issues with containers, that are in multiple networks.
# E.g. Database container in a separate network and a container in the frontend and database network.
network: frontend
file:
directory: /etc/traefik
watch: true

View File

@@ -0,0 +1,334 @@
# 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"]]
# --- Scope mappings (find existing ones and get stable IDs) -----------------
- id: scope_openid
model: authentik_providers_oauth2.scopemapping
identifiers:
scope_name: openid
- id: scope_profile
model: authentik_providers_oauth2.scopemapping
identifiers:
scope_name: profile
- id: scope_email
model: authentik_providers_oauth2.scopemapping
identifiers:
scope_name: email
- id: scope_groups
model: authentik_providers_oauth2.scopemapping
identifiers:
scope_name: groups
- id: scope_offline
model: authentik_providers_oauth2.scopemapping
identifiers:
scope_name: offline_access
# Helper finders
- id: default_signing_key
model: authentik_crypto.certificatekeypair
state: present
identifiers:
name: "authentik Self-signed Certificate"
- id: default_authz_flow
model: authentik_flows.flow
state: present
identifiers:
slug: "default-authentication-flow"
- id: default_inval_flow
model: authentik_flows.flow
state: present
identifiers:
slug: "default-invalidation-flow"
# ========= OIDC Providers + Applications ==================================
# --- AI Tax Agent API ------------------------------------------------------
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "AI Tax Agent API"
attrs:
client_id: "ai-tax-agent-api"
client_secret: !Env [AUTHENTIK_API_CLIENT_SECRET, "changeme-api-secret"]
authorization_grant_type: "authorization-code"
client_type: "confidential"
issuer_mode: "per_provider"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
signing_key: !KeyOf default_signing_key
redirect_uris:
- matching_mode: strict
url: "https://api.local.lan/auth/callback"
- matching_mode: strict
url: "https://review.local.lan/auth/callback"
scope_mappings:
- !KeyOf scope_openid
- !KeyOf scope_profile
- !KeyOf scope_email
- !KeyOf scope_groups
- !KeyOf scope_offline
authorization_flow: !KeyOf default_authz_flow
invalidation_flow: !KeyOf default_inval_flow
- model: authentik_core.application
state: present
identifiers:
slug: "ai-tax-agent-api"
attrs:
name: "AI Tax Agent API"
provider:
!Find [
authentik_providers_oauth2.oauth2provider,
[name, "AI Tax Agent API"],
]
meta_launch_url: "https://api.local.lan"
meta_description: "AI Tax Agent API Services"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- MinIO -----------------------------------------------------------------
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "MinIO"
attrs:
client_id: "minio"
client_secret:
!Env [AUTHENTIK_MINIO_CLIENT_SECRET, "changeme-minio-secret"]
authorization_grant_type: "authorization-code"
client_type: "confidential"
issuer_mode: "per_provider"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
signing_key: !KeyOf default_signing_key
redirect_uris:
- matching_mode: strict
url: "https://minio.local.lan/oauth_callback"
scope_mappings:
- !KeyOf scope_openid
- !KeyOf scope_profile
- !KeyOf scope_email
- !KeyOf scope_groups
- !KeyOf scope_offline
authorization_flow: !KeyOf default_authz_flow
invalidation_flow: !KeyOf default_inval_flow
- model: authentik_core.application
state: present
identifiers:
slug: "minio"
attrs:
name: "MinIO"
provider:
!Find [authentik_providers_oauth2.oauth2provider, [name, "MinIO"]]
meta_launch_url: "https://minio.local.lan"
meta_description: "Object storage console"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- UI Review (Proxy Provider for ForwardAuth) ---------------------------
- model: authentik_providers_proxy.proxyprovider
state: present
identifiers:
name: "UI Review Proxy"
attrs:
external_host: "https://review.${DOMAIN:-local}"
internal_host: "http://ui-review:3030"
authorization_flow: !KeyOf default_authz_flow
invalidation_flow: !KeyOf default_inval_flow
mode: "forward_single"
cookie_domain: "${DOMAIN:-local}"
- 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.${DOMAIN:-local}"
meta_description: "Tax Agent Platform - Review UI"
meta_publisher: "AI Tax Agent"
policy_engine_mode: "any"
# --- Vault -----------------------------------------------------------------
- model: authentik_providers_oauth2.oauth2provider
state: present
identifiers:
name: "Vault"
attrs:
client_id: "vault"
client_secret:
!Env [AUTHENTIK_VAULT_CLIENT_SECRET, "changeme-vault-secret"]
authorization_grant_type: "authorization-code"
client_type: "confidential"
issuer_mode: "per_provider"
sub_mode: "hashed_user_id"
include_claims_in_id_token: true
signing_key: !KeyOf default_signing_key
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"
scope_mappings:
- !KeyOf scope_openid
- !KeyOf scope_profile
- !KeyOf scope_email
- !KeyOf scope_groups
- !KeyOf scope_offline
authorization_flow: !KeyOf default_authz_flow
invalidation_flow: !KeyOf default_inval_flow
- model: authentik_core.application
state: present
identifiers:
slug: "vault"
attrs:
name: "Vault"
provider:
!Find [authentik_providers_oauth2.oauth2provider, [name, "Vault"]]
meta_launch_url: "https://vault.local.lan"
meta_description: "Secrets management (Vault)"
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: "grafana"
client_secret: "${AUTHENTIK_GRAFANA_CLIENT_SECRET:-changeme-grafana-secret}"
client_type: "confidential"
redirect_uris: "https://grafana.${DOMAIN:-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,
[scope_name, "groups"],
]
- !Find [
authentik_providers_oauth2.scopemapping,
[name, "Grafana Role Mapping"],
]
authorization_flow: !KeyOf default_authz_flow
invalidation_flow: !KeyOf default_inval_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.${DOMAIN:-local.lan}"
meta_description: "Grafana monitoring and observability platform"
meta_publisher: "Grafana Labs"
policy_engine_mode: "any"

View File

@@ -0,0 +1,61 @@
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
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
# Retention configuration
limits_config:
retention_period: 744h # 31 days
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 10
ingestion_burst_size_mb: 20
# Compactor for retention
compactor:
working_directory: /loki/compactor
shared_store: filesystem
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 2h
retention_delete_worker_count: 150
# Table manager for retention
table_manager:
retention_deletes_enabled: true
retention_period: 744h

View File

@@ -0,0 +1,49 @@
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Docker container logs
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: label
values: ["logging=promtail"]
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: 'container'
- source_labels: ['__meta_docker_container_log_stream']
target_label: 'logstream'
- source_labels: ['__meta_docker_container_label_com_docker_compose_project']
target_label: 'project'
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: 'service'
# System logs (optional)
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
__path__: /var/log/*log
# Application-specific logs
- job_name: ai-tax-agent
static_configs:
- targets:
- localhost
labels:
job: ai-tax-agent
environment: production
__path__: /var/log/ai-tax-agent/*.log

View File

@@ -0,0 +1,31 @@
# Application-specific Traefik middlewares
# These are loaded by the application infrastructure, not the external Traefik
http:
middlewares:
# 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

View File

View File

@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIUbOm5g4Xhb08Lk6DIpVst7+xZHOswDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAwwFbG9jYWwwHhcNMjUwOTI4MTExNTM1WhcNMzUwOTI2MTEx
NTM1WjAQMQ4wDAYDVQQDDAVsb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAK0370DEo3dScS8uLwBsXkuaAHn9wO2fjxEHLZwHWfFo/16t+EEAi5c3
zDs7nYQ7LPLndxBfO6xZ5uWKNIVtp6ARzAeRbGgbjXDdK3fOyRdhhKR3aZVOH1D0
xUjEm/X5jEDv81sufSjk+DIQmh8hQnp3RwdHyhkIZUCTsBXMfnj+zs1UKTdRQBF5
SUplGsbh6z3xCSI4jiNRb7mNHXqV3Fv6ycwF8YdthSDfueltBP4vT/CDtebkkKPF
dx7YWEIPPUNqEoHqeI5iYP6gnWJYcr3vU+p2BuTwUICo+njzAf+P/SsjPHbujJob
dbHUclBHIrIO4BpYZtY1a7E219MbqcECAwEAAaOCAW4wggFqMB0GA1UdDgQWBBQ7
qHpza0Bb1xI1g7cMBx33JnFQljAfBgNVHSMEGDAWgBQ7qHpza0Bb1xI1g7cMBx33
JnFQljAPBgNVHRMBAf8EBTADAQH/MIIBFQYDVR0RBIIBDDCCAQiCCWxvY2FsaG9z
dIcEfwAAAYILKi5sb2NhbC5sYW6CDmF1dGgubG9jYWwubGFughFncmFmYW5hLmxv
Y2FsLmxhboIQcmV2aWV3LmxvY2FsLmxhboINYXBpLmxvY2FsLmxhboIPdmF1bHQu
bG9jYWwubGFugg9taW5pby5sb2NhbC5sYW6CE21pbmlvLWFwaS5sb2NhbC5sYW6C
EHFkcmFudC5sb2NhbC5sYW6CD25lbzRqLmxvY2FsLmxhboIUcHJvbWV0aGV1cy5s
b2NhbC5sYW6CDmxva2kubG9jYWwubGFughF1bmxlYXNoLmxvY2FsLmxhboIRdHJh
ZWZpay5sb2NhbC5sYW4wDQYJKoZIhvcNAQELBQADggEBAICf+2MZ7BHbSD/pnvll
G7Zmk+Bntj2F6RBQVZ2ZsKPWkHeZEYJDRvU0I2uL5tvvDJp4q0hjdluJllchhGgr
qfu7i+kRnhzme7oyRTFGYp8b3zHBvLyJLmdIALxuNSjIEeh1Fx0lEhKwqOlA4y6T
jziPmsGv3IonGJM2dURGNcR7DfG6H/Yl12qV8u/tVFTxqWL+hyCE7u8v+ZIcZ+fj
82X7hXt1HvfP84EhVtfqQMb5xykLtXvPqggSCFXYIj2PanWdwEdE6P5Yr2D1Yz7r
tzpmpoetrGoMWIeB0yiWgt0qJ/KK7meoCp64mqfBc48p1p/7kj2R/FRH1Jx3gFWy
dT4=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCtN+9AxKN3UnEv
Li8AbF5LmgB5/cDtn48RBy2cB1nxaP9erfhBAIuXN8w7O52EOyzy53cQXzusWebl
ijSFbaegEcwHkWxoG41w3St3zskXYYSkd2mVTh9Q9MVIxJv1+YxA7/NbLn0o5Pgy
EJofIUJ6d0cHR8oZCGVAk7AVzH54/s7NVCk3UUAReUlKZRrG4es98QkiOI4jUW+5
jR16ldxb+snMBfGHbYUg37npbQT+L0/wg7Xm5JCjxXce2FhCDz1DahKB6niOYmD+
oJ1iWHK971Pqdgbk8FCAqPp48wH/j/0rIzx27oyaG3Wx1HJQRyKyDuAaWGbWNWux
NtfTG6nBAgMBAAECggEAHvtkNcd2HX+HcxLloUPA0fDnqOo0OcxSQI9yHvhJpB5N
nterEaVRUmjOhMGy+NXEwmWYLDt8ZuVloSTJJBxq4PyN68SdCTn0YH2Oqs03tpDg
srIRFn10qHw/VTalVqed6HeCpYp5JHlf00SY7Hx8cX8oGytCAJw50AUad6ut62IM
sp/QFdtkLhtq9vGzQUqyIP92Y/+GbxhB+eHkuvvFau1KJq7K8qhroFTwQFts9er2
890Ujmz3bF2RhHixQcpXpsf/DMyylGJTbZDmSFkTDa/c1PzqvKrmL3wP7A3bk1E5
CP8/a65ykotJEX8RkWqH2XxvRKpdWtCaeuCsmWUQ4QKBgQDTLbC9DWHCUYMWJhyW
TKAeXx5xFGHIqggN28lIkXFiCVsTZyOuRDN7Q/CbOat/0JthrzyP18L+6ewZt2ZN
RjdfGdnpUCJx6LR4dtBH8Rc+CjlSnqEgJIkgfIs8b9uEhMI1eQV+BAFQON3BzdpT
wQ86aGsrdqtpfav7cImVfGcY/QKBgQDR+7OcnEwh8s/1J2niMKjk8agyCGGHWW4M
g+vIv7lptavgEGOPMBv7QgmeuUjwSszphQXL36m39ZRmI5B+J0/onuQzv04tJeZY
WZhA+T12a+1VnvUZNZm/qp0I2rW+4m+DmJoLQlvpaaFit/1fPJ6+IzI2VzPeWhw2
vUQ5QIYhFQKBgFUWZc3mpGsNOMol1QLiIOnb3YImejfF+rTKx9FLeOnNZzrsJb5D
kJKsDzgcBnPbc5/qYXZ7sv/O9OhvsvKTxh+1ZM3TEe3fm0emZ8l05K6EpBAcBkPT
NMU4KUnSsBo2+6Fb/9CEgJr4LrG15bA1a5NXG0dJ60r37eHDuEvY8hlpAoGADWv2
PhNrdlwL2NKtHO0ZTpD3vEL24OzhcOFZx9ohYtVe6BKEGpnrn/LHpKKZO+q8EE0V
YsOoGH8U/jZVvQqMPAUz9u7Kc25Ru+H2Lmj/+brKT8e6SOM5MZwZL4CzT0Ev+Yxe
hEu4jkHXM/Uot9arGuIrCngmc5b06LbOTo6GREUCgYArWyPYeETah/GVwU7/TNY5
5f8lNbWBoXZfpVbWdoUZT6tGWciZsiXSR4x9f+1/LMIuChegSEazrJUDt7TbCkZs
s4A66pnME37aYP2sMvJF3zSnQWVIyBgGI5xX0XW/WdozKl1mdFfigyWp58uo2dS2
TxE3dy8rxpUdDCUmvJT/Fw==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,47 @@
# Template Dockerfile for ML Services
# This uses the pre-built base-ml image which contains all heavy ML dependencies
# Only the application code is added on top (~50MB vs 1.2GB)
#
# Usage: Copy this template to apps/svc_*/Dockerfile and replace SERVICE_NAME
# Use the pre-built ML base image
ARG REGISTRY=gitea.harkon.co.uk
ARG OWNER=harkon
ARG BASE_VERSION=v1.0.1
FROM ${REGISTRY}/${OWNER}/base-ml:${BASE_VERSION}
# Switch to root to install service-specific dependencies
USER root
# Set working directory
WORKDIR /app
# Copy service-specific requirements (if any additional deps needed)
# Most ML deps are already in base-ml, so this should be minimal
COPY apps/SERVICE_NAME/requirements.txt /tmp/service-requirements.txt
# Install any service-specific dependencies (should be very few)
RUN if [ -s /tmp/service-requirements.txt ]; then \
pip install --no-cache-dir -r /tmp/service-requirements.txt; \
fi
# Copy application code
COPY libs/ ./libs/
COPY apps/SERVICE_NAME/ ./apps/SERVICE_NAME/
# Set permissions
RUN chown -R appuser:appuser /app
# Switch back to non-root user
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/healthz || exit 1
# Expose port
EXPOSE 8000
# Run the application
CMD ["python", "-m", "uvicorn", "apps.SERVICE_NAME.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -0,0 +1,58 @@
# Base ML Image - Contains all heavy ML dependencies
# This image is built once and reused by all ML services (svc-ocr, svc-rag-indexer, svc-rag-retriever)
#
# Build: docker build -t gitea.harkon.co.uk/harkon/base-ml:v1.0.1 -f infra/docker/base-ml.Dockerfile .
# Push: docker push gitea.harkon.co.uk/harkon/base-ml:v1.0.1
FROM python:3.12-slim as builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy requirements files
COPY libs/requirements-base.txt /tmp/requirements-base.txt
COPY libs/requirements-ml.txt /tmp/requirements-ml.txt
# Install all dependencies (base + ML)
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r /tmp/requirements-base.txt && \
pip install --no-cache-dir -r /tmp/requirements-ml.txt
# Final stage - Runtime image
FROM python:3.12-slim
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -r appuser \
&& useradd -r -g appuser appuser
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
# Set environment variables
ENV PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sentence_transformers; import transformers; print('ML base image healthy')"
# Default user
USER appuser
# Label
LABEL maintainer="AI Tax Agent Team" \
description="Base ML image with sentence-transformers, PyTorch, and ML dependencies" \
version="1.0.1"

View File

@@ -0,0 +1,55 @@
# Base Runtime Image - Contains core dependencies for ALL services
# This image is built once and reused by all non-ML services
#
# Build: docker build -t gitea.harkon.co.uk/harkon/base-runtime:v1.0.1 -f infra/docker/base-runtime.Dockerfile .
# Push: docker push gitea.harkon.co.uk/harkon/base-runtime:v1.0.1
FROM python:3.12-slim as builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy requirements file
COPY libs/requirements-base.txt /tmp/requirements-base.txt
# Install base dependencies
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r /tmp/requirements-base.txt
# Final stage - Runtime image
FROM python:3.12-slim
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -r appuser \
&& useradd -r -g appuser appuser
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
# Set environment variables
ENV PATH="/opt/venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import fastapi; import uvicorn; print('Base runtime image healthy')"
# Default user
USER appuser
# Label
LABEL maintainer="AI Tax Agent Team" \
description="Base runtime image with FastAPI, database drivers, and core dependencies" \
version="1.0.1"

View File

@@ -0,0 +1,74 @@
# FILE: infra/environments/development/.env.example
# Development Environment Configuration
# Copy this to .env and customize for your development server
# WARNING: This file contains sensitive credentials. DO NOT commit to git!
# Domain Configuration
DOMAIN=dev.harkon.co.uk
EMAIL=dev@harkon.co.uk
# Database Passwords (CHANGE THESE!)
POSTGRES_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD
NEO4J_PASSWORD=CHANGE_ME_NEO4J_PASSWORD
AUTHENTIK_DB_PASSWORD=CHANGE_ME_AUTHENTIK_DB_PASSWORD
# Object Storage (CHANGE THESE!)
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=CHANGE_ME_MINIO_ROOT_PASSWORD
MINIO_ACCESS_KEY=admin
MINIO_SECRET_KEY=CHANGE_ME_MINIO_SECRET_KEY
# Vector Database
QDRANT__SERVICE__GRPC_PORT=6334
# Secrets Management (CHANGE THIS!)
VAULT_DEV_ROOT_TOKEN_ID=CHANGE_ME_VAULT_TOKEN
# Identity & SSO (CHANGE THESE!)
# Generate with: openssl rand -base64 32
AUTHENTIK_SECRET_KEY=CHANGE_ME_AUTHENTIK_SECRET
AUTHENTIK_OUTPOST_TOKEN=CHANGE_ME_OUTPOST_TOKEN
AUTHENTIK_BOOTSTRAP_EMAIL=admin@dev.harkon.co.uk
AUTHENTIK_BOOTSTRAP_PASSWORD=CHANGE_ME_ADMIN_PASSWORD
AUTHENTIK_BOOTSTRAP_TOKEN=
# Monitoring (CHANGE THIS!)
GRAFANA_PASSWORD=CHANGE_ME_GRAFANA_PASSWORD
GRAFANA_OAUTH_CLIENT_ID=grafana
GRAFANA_OAUTH_CLIENT_SECRET=CHANGE_ME_GRAFANA_OAUTH_SECRET
# OAuth Client Secrets for Authentik Providers (CHANGE THESE!)
AUTHENTIK_API_CLIENT_SECRET=CHANGE_ME_API_SECRET
AUTHENTIK_UI_REVIEW_CLIENT_SECRET=CHANGE_ME_UI_SECRET
AUTHENTIK_GRAFANA_CLIENT_SECRET=CHANGE_ME_GRAFANA_SECRET
AUTHENTIK_MINIO_CLIENT_SECRET=CHANGE_ME_MINIO_SECRET
AUTHENTIK_VAULT_CLIENT_SECRET=CHANGE_ME_VAULT_SECRET
# Feature Flags
UNLEASH_ADMIN_TOKEN=development.unleash-admin-api-token
# Application Configuration (CHANGE THIS!)
NEXTAUTH_SECRET=CHANGE_ME_NEXTAUTH_SECRET
# Redis Configuration
REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD
# NATS Configuration
NATS_USER=nats
NATS_PASSWORD=CHANGE_ME_NATS_PASSWORD
# Application Secrets
JWT_SECRET=CHANGE_ME_JWT_SECRET_32_CHARS_MIN
ENCRYPTION_KEY=CHANGE_ME_ENCRYPTION_KEY_32_CHARS
# API Keys (for development testing)
OPENAI_API_KEY=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
# Registry Configuration
REGISTRY=gitea.dev.harkon.co.uk
REGISTRY_USER=harkon
REGISTRY_PASSWORD=CHANGE_ME_GITEA_TOKEN
IMAGE_TAG=dev
OWNER=harkon

View File

@@ -0,0 +1,72 @@
# FILE: infra/environments/local/.env.example
# Local Development Environment Configuration
# Copy this to .env and customize for your local setup
# Domain Configuration
DOMAIN=localhost
EMAIL=dev@localhost
# Database Passwords (use simple passwords for local)
POSTGRES_PASSWORD=postgres
NEO4J_PASSWORD=neo4j123
AUTHENTIK_DB_PASSWORD=authentik123
# Object Storage
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=minioadmin
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
# Vector Database
QDRANT__SERVICE__GRPC_PORT=6334
# Secrets Management
VAULT_DEV_ROOT_TOKEN_ID=dev-root-token
# Identity & SSO (optional for local)
AUTHENTIK_SECRET_KEY=local-secret-key-change-me
AUTHENTIK_OUTPOST_TOKEN=local-outpost-token
AUTHENTIK_BOOTSTRAP_EMAIL=admin@localhost
AUTHENTIK_BOOTSTRAP_PASSWORD=admin123
AUTHENTIK_BOOTSTRAP_TOKEN=
# Monitoring
GRAFANA_PASSWORD=admin
GRAFANA_OAUTH_CLIENT_ID=grafana
GRAFANA_OAUTH_CLIENT_SECRET=grafana-secret
# OAuth Client Secrets (not needed for local without Authentik)
AUTHENTIK_API_CLIENT_SECRET=api-secret
AUTHENTIK_UI_REVIEW_CLIENT_SECRET=ui-secret
AUTHENTIK_GRAFANA_CLIENT_SECRET=grafana-secret
AUTHENTIK_MINIO_CLIENT_SECRET=minio-secret
AUTHENTIK_VAULT_CLIENT_SECRET=vault-secret
# Feature Flags
UNLEASH_ADMIN_TOKEN=local-unleash-token
# Application Configuration
NEXTAUTH_SECRET=local-nextauth-secret
# Redis Configuration
REDIS_PASSWORD=redis123
# NATS Configuration
NATS_USER=nats
NATS_PASSWORD=nats123
# Application Secrets
JWT_SECRET=local-jwt-secret-change-me
ENCRYPTION_KEY=local-encryption-key-32-chars!!
# API Keys (for local testing)
OPENAI_API_KEY=sk-test-key
ANTHROPIC_API_KEY=sk-ant-test-key
# Registry Configuration
REGISTRY=localhost:5000
REGISTRY_USER=admin
REGISTRY_PASSWORD=admin123
IMAGE_TAG=latest
OWNER=local

View File

@@ -0,0 +1,74 @@
# FILE: infra/environments/production/.env.example
# Production Environment Configuration
# Copy this to .env and customize for your production server
# WARNING: This file contains sensitive credentials. DO NOT commit to git!
# Domain Configuration
DOMAIN=harkon.co.uk
EMAIL=info@harkon.co.uk
# Database Passwords (CHANGE THESE!)
POSTGRES_PASSWORD=CHANGE_ME_POSTGRES_PASSWORD
NEO4J_PASSWORD=CHANGE_ME_NEO4J_PASSWORD
AUTHENTIK_DB_PASSWORD=CHANGE_ME_AUTHENTIK_DB_PASSWORD
# Object Storage (CHANGE THESE!)
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=CHANGE_ME_MINIO_ROOT_PASSWORD
MINIO_ACCESS_KEY=admin
MINIO_SECRET_KEY=CHANGE_ME_MINIO_SECRET_KEY
# Vector Database
QDRANT__SERVICE__GRPC_PORT=6334
# Secrets Management (CHANGE THIS!)
VAULT_DEV_ROOT_TOKEN_ID=CHANGE_ME_VAULT_TOKEN
# Identity & SSO (CHANGE THESE!)
# Generate with: openssl rand -base64 32
AUTHENTIK_SECRET_KEY=CHANGE_ME_AUTHENTIK_SECRET
AUTHENTIK_OUTPOST_TOKEN=CHANGE_ME_OUTPOST_TOKEN
AUTHENTIK_BOOTSTRAP_EMAIL=admin@harkon.co.uk
AUTHENTIK_BOOTSTRAP_PASSWORD=CHANGE_ME_ADMIN_PASSWORD
AUTHENTIK_BOOTSTRAP_TOKEN=
# Monitoring (CHANGE THIS!)
GRAFANA_PASSWORD=CHANGE_ME_GRAFANA_PASSWORD
GRAFANA_OAUTH_CLIENT_ID=grafana
GRAFANA_OAUTH_CLIENT_SECRET=CHANGE_ME_GRAFANA_OAUTH_SECRET
# OAuth Client Secrets for Authentik Providers (CHANGE THESE!)
AUTHENTIK_API_CLIENT_SECRET=CHANGE_ME_API_SECRET
AUTHENTIK_UI_REVIEW_CLIENT_SECRET=CHANGE_ME_UI_SECRET
AUTHENTIK_GRAFANA_CLIENT_SECRET=CHANGE_ME_GRAFANA_SECRET
AUTHENTIK_MINIO_CLIENT_SECRET=CHANGE_ME_MINIO_SECRET
AUTHENTIK_VAULT_CLIENT_SECRET=CHANGE_ME_VAULT_SECRET
# Feature Flags
UNLEASH_ADMIN_TOKEN=production.unleash-admin-api-token
# Application Configuration (CHANGE THIS!)
NEXTAUTH_SECRET=CHANGE_ME_NEXTAUTH_SECRET
# Redis Configuration
REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD
# NATS Configuration
NATS_USER=nats
NATS_PASSWORD=CHANGE_ME_NATS_PASSWORD
# Application Secrets
JWT_SECRET=CHANGE_ME_JWT_SECRET_32_CHARS_MIN
ENCRYPTION_KEY=CHANGE_ME_ENCRYPTION_KEY_32_CHARS
# API Keys
OPENAI_API_KEY=sk-your-production-openai-key
ANTHROPIC_API_KEY=sk-ant-your-production-anthropic-key
# Registry Configuration
REGISTRY=gitea.harkon.co.uk
REGISTRY_USER=harkon
REGISTRY_PASSWORD=CHANGE_ME_GITEA_TOKEN
IMAGE_TAG=v1.0.1
OWNER=harkon

BIN
infra/neo4j/plugins/apoc.jar Executable file

Binary file not shown.

Binary file not shown.

241
infra/scripts/deploy.sh Executable file
View File

@@ -0,0 +1,241 @@
#!/bin/bash
# AI Tax Agent Infrastructure Deployment Script
# Supports multiple environments: local, development, production
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INFRA_DIR="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$INFRA_DIR")"
# Usage
usage() {
cat << EOF
Usage: $0 <environment> <stack> [options]
Environments:
local - Local development (localhost)
development - Development server (dev.harkon.co.uk)
production - Production server (harkon.co.uk)
Stacks:
all - Deploy all stacks
infrastructure - Core infrastructure (Vault, MinIO, DBs, Redis, NATS)
monitoring - Monitoring stack (Prometheus, Grafana, Loki)
services - Application services
external - External services (Traefik, Authentik, Gitea)
down - Stop and remove all stacks
Options:
--build - Build images before deploying
--pull - Pull images before deploying
--force - Force recreate containers
Examples:
$0 local all
$0 production infrastructure
$0 development services --build
$0 production down
EOF
exit 1
}
# Check arguments
if [ $# -lt 2 ]; then
usage
fi
ENVIRONMENT=$1
STACK=$2
shift 2
# Validate environment
case $ENVIRONMENT in
local|development|production)
;;
*)
log_error "Invalid environment: $ENVIRONMENT"
usage
;;
esac
# Paths
ENV_FILE="$INFRA_DIR/environments/$ENVIRONMENT/.env"
BASE_DIR="$INFRA_DIR/base"
# Check if environment file exists
if [ ! -f "$ENV_FILE" ]; then
log_error "Environment file not found: $ENV_FILE"
log_info "Copy from template: cp $INFRA_DIR/environments/$ENVIRONMENT/.env.example $ENV_FILE"
exit 1
fi
# Load environment variables
set -a
source "$ENV_FILE"
set +a
log_info "Deploying AI Tax Agent Infrastructure"
echo " Environment: $ENVIRONMENT"
echo " Stack: $STACK"
echo " Env File: $ENV_FILE"
echo ""
# Docker Compose command builder
compose_cmd() {
local file=$1
shift
docker compose -f "$BASE_DIR/$file" --env-file "$ENV_FILE" --project-name "ai-tax-agent-$ENVIRONMENT" "$@"
}
# Deploy infrastructure stack
deploy_infrastructure() {
log_info "Deploying infrastructure stack..."
compose_cmd "infrastructure.yaml" up -d "$@"
log_success "Infrastructure stack deployed"
}
# Deploy monitoring stack
deploy_monitoring() {
log_info "Deploying monitoring stack..."
compose_cmd "monitoring.yaml" up -d "$@"
log_success "Monitoring stack deployed"
}
# Deploy services stack
deploy_services() {
log_info "Deploying services stack..."
compose_cmd "services.yaml" up -d "$@"
log_success "Services stack deployed"
}
# 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
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
log_info "Skipping external services"
return
fi
fi
compose_cmd "external.yaml" up -d "$@"
log_success "External services stack deployed"
}
# 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 frontend >/dev/null 2>&1; then
log_warning "Network 'frontend' does not exist. Creating..."
docker network create frontend
fi
if ! docker network inspect backend >/dev/null 2>&1; then
log_warning "Network 'backend' does not exist. Creating..."
docker network create backend
fi
# Deploy in order
deploy_infrastructure "$@"
sleep 5
deploy_monitoring "$@"
sleep 5
deploy_services "$@"
log_success "All stacks deployed successfully!"
echo ""
log_info "Access your services:"
echo " - Grafana: https://grafana.$DOMAIN"
echo " - Prometheus: https://prometheus.$DOMAIN"
echo " - Vault: https://vault.$DOMAIN"
echo " - MinIO: https://minio.$DOMAIN"
echo " - UI Review: https://ui-review.$DOMAIN"
}
# Main deployment logic
case $STACK in
all)
deploy_all "$@"
;;
infrastructure)
deploy_infrastructure "$@"
;;
monitoring)
deploy_monitoring "$@"
;;
services)
deploy_services "$@"
;;
external)
deploy_external "$@"
;;
down)
stop_all
;;
*)
log_error "Invalid stack: $STACK"
usage
;;
esac
log_success "Deployment complete!"

View File

@@ -0,0 +1,178 @@
#!/bin/bash
# Script to reorganize infrastructure from old structure to new structure
# This is a helper script to move files around
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m'
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INFRA_DIR="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$INFRA_DIR")"
log_info "Reorganizing infrastructure structure..."
echo " Infra Dir: $INFRA_DIR"
echo ""
# Step 1: Create directory structure (already done by mkdir command)
log_info "Step 1: Verifying directory structure..."
if [ -d "$INFRA_DIR/base" ] && [ -d "$INFRA_DIR/environments" ]; then
log_success "Directory structure exists"
else
log_error "Directory structure not found. Run: mkdir -p infra/{base,environments/{local,development,production},configs/{traefik,grafana,prometheus,loki,vault,authentik},certs/{local,development,production}}"
exit 1
fi
# Step 2: Move config files
log_info "Step 2: Moving configuration files..."
# Traefik configs
if [ -d "$INFRA_DIR/traefik" ] && [ ! -f "$INFRA_DIR/configs/traefik/.moved" ]; then
log_info " Moving Traefik configs..."
cp -r "$INFRA_DIR/traefik/"* "$INFRA_DIR/configs/traefik/" 2>/dev/null || true
touch "$INFRA_DIR/configs/traefik/.moved"
log_success " Traefik configs moved"
fi
# Grafana configs
if [ -d "$INFRA_DIR/grafana" ] && [ ! -f "$INFRA_DIR/configs/grafana/.moved" ]; then
log_info " Moving Grafana configs..."
cp -r "$INFRA_DIR/grafana/"* "$INFRA_DIR/configs/grafana/" 2>/dev/null || true
touch "$INFRA_DIR/configs/grafana/.moved"
log_success " Grafana configs moved"
fi
# Prometheus configs
if [ -d "$INFRA_DIR/prometheus" ] && [ ! -f "$INFRA_DIR/configs/prometheus/.moved" ]; then
log_info " Moving Prometheus configs..."
cp -r "$INFRA_DIR/prometheus/"* "$INFRA_DIR/configs/prometheus/" 2>/dev/null || true
touch "$INFRA_DIR/configs/prometheus/.moved"
log_success " Prometheus configs moved"
fi
# Loki configs
if [ -d "$INFRA_DIR/loki" ] && [ ! -f "$INFRA_DIR/configs/loki/.moved" ]; then
log_info " Moving Loki configs..."
cp -r "$INFRA_DIR/loki/"* "$INFRA_DIR/configs/loki/" 2>/dev/null || true
touch "$INFRA_DIR/configs/loki/.moved"
log_success " Loki configs moved"
fi
# Promtail configs
if [ -d "$INFRA_DIR/promtail" ] && [ ! -f "$INFRA_DIR/configs/promtail/.moved" ]; then
log_info " Moving Promtail configs..."
mkdir -p "$INFRA_DIR/configs/promtail"
cp -r "$INFRA_DIR/promtail/"* "$INFRA_DIR/configs/promtail/" 2>/dev/null || true
touch "$INFRA_DIR/configs/promtail/.moved"
log_success " Promtail configs moved"
fi
# Vault configs
if [ -d "$INFRA_DIR/vault" ] && [ ! -f "$INFRA_DIR/configs/vault/.moved" ]; then
log_info " Moving Vault configs..."
cp -r "$INFRA_DIR/vault/"* "$INFRA_DIR/configs/vault/" 2>/dev/null || true
touch "$INFRA_DIR/configs/vault/.moved"
log_success " Vault configs moved"
fi
# Authentik configs
if [ -d "$INFRA_DIR/authentik" ] && [ ! -f "$INFRA_DIR/configs/authentik/.moved" ]; then
log_info " Moving Authentik configs..."
cp -r "$INFRA_DIR/authentik/"* "$INFRA_DIR/configs/authentik/" 2>/dev/null || true
touch "$INFRA_DIR/configs/authentik/.moved"
log_success " Authentik configs moved"
fi
# Step 3: Move certificates
log_info "Step 3: Moving certificates..."
if [ -d "$INFRA_DIR/certs" ] && [ -f "$INFRA_DIR/certs/local.crt" ]; then
log_info " Moving local certificates..."
cp "$INFRA_DIR/certs/local.crt" "$INFRA_DIR/certs/local/" 2>/dev/null || true
cp "$INFRA_DIR/certs/local.key" "$INFRA_DIR/certs/local/" 2>/dev/null || true
log_success " Certificates moved"
fi
# Step 4: Update base compose files paths
log_info "Step 4: Updating base compose file paths..."
# Update infrastructure.yaml
if [ -f "$INFRA_DIR/base/infrastructure.yaml" ]; then
log_info " Updating infrastructure.yaml paths..."
# This would require sed commands to update volume paths
# For now, just log that manual update may be needed
log_warning " Manual review recommended for volume paths"
fi
# Step 5: Create .gitignore for sensitive files
log_info "Step 5: Creating .gitignore..."
cat > "$INFRA_DIR/.gitignore" << 'EOF'
# Environment files (contain secrets)
environments/*/.env
!environments/*/.env.example
# Certificates
certs/*/
!certs/.gitkeep
# Traefik provider credentials
configs/traefik/.provider.env
# Backup files
*.backup
*.tmp
# Docker volumes (if mounted locally)
volumes/
# Logs
*.log
EOF
log_success ".gitignore created"
# Step 6: Create .gitkeep files
log_info "Step 6: Creating .gitkeep files..."
touch "$INFRA_DIR/certs/local/.gitkeep"
touch "$INFRA_DIR/certs/development/.gitkeep"
touch "$INFRA_DIR/certs/production/.gitkeep"
log_success ".gitkeep files created"
# Step 7: Summary
echo ""
log_success "Reorganization complete!"
echo ""
log_info "Next steps:"
echo " 1. Review moved files in configs/ directory"
echo " 2. Update compose file paths if needed"
echo " 3. Create environment files:"
echo " cp infra/environments/local/.env.example infra/environments/local/.env"
echo " cp infra/environments/development/.env.example infra/environments/development/.env"
echo " 4. Test deployment:"
echo " ./infra/scripts/deploy.sh local infrastructure"
echo ""
log_warning "Old directories (traefik/, grafana/, etc.) are preserved for safety"
log_warning "You can remove them after verifying the new structure works"
echo ""

48
infra/scripts/setup-networks.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Setup Docker Networks for AI Tax Agent
# Creates frontend and backend networks if they don't exist
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${BLUE} $1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_info "Setting up Docker networks..."
# Create frontend network
if docker network inspect frontend >/dev/null 2>&1; then
log_warning "Network 'frontend' already exists"
else
docker network create frontend
log_success "Created network 'frontend'"
fi
# Create backend network
if docker network inspect backend >/dev/null 2>&1; then
log_warning "Network 'backend' already exists"
else
docker network create backend
log_success "Created network 'backend'"
fi
log_success "Docker networks ready!"
echo ""
log_info "Networks:"
docker network ls | grep -E "frontend|backend"