Initial commit on Forgejo
Fresh repository history for elektrine/elektrine-haraka hosted at https://git.elektrine.com/elektrine/elektrine-haraka.
This commit is contained in:
commit
67c1d41a0c
82 changed files with 8713 additions and 0 deletions
53
deployment/.env.example
Normal file
53
deployment/.env.example
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Haraka environment variables
|
||||
# Copy to .env and set your values
|
||||
|
||||
# Domain Configuration
|
||||
HARAKA_DOMAIN=mail.example.com
|
||||
|
||||
# SSL Configuration
|
||||
SSL_EMAIL=admin@example.com
|
||||
STAGING=0
|
||||
|
||||
# Shared key (optional)
|
||||
HARAKA_API_KEY=your-secure-api-key-here
|
||||
|
||||
# Directional keys
|
||||
# Haraka -> Phoenix
|
||||
PHOENIX_API_KEY=replace-with-haraka-to-phoenix-key
|
||||
# Phoenix -> Haraka
|
||||
HARAKA_HTTP_API_KEY=replace-with-phoenix-to-haraka-key
|
||||
|
||||
# Phoenix App Integration
|
||||
PHOENIX_WEBHOOK_URL=https://your-phoenix-app.com/api/haraka/inbound
|
||||
PHOENIX_VERIFY_URL=https://your-phoenix-app.com/api/haraka/verify-recipient
|
||||
PHOENIX_DOMAINS_URL=https://your-phoenix-app.com/api/haraka/domains
|
||||
|
||||
# Image tag
|
||||
HARAKA_IMAGE=git.elektrine.com/elektrine/elektrine-haraka
|
||||
HARAKA_IMAGE_TAG=latest
|
||||
|
||||
# Optional service images
|
||||
CADDY_IMAGE=caddy:2-alpine
|
||||
REDIS_IMAGE=redis:7-alpine
|
||||
CLAMAV_IMAGE=clamav/clamav:stable
|
||||
SPAMASSASSIN_IMAGE=dinkel/spamassassin:latest
|
||||
FAIL2BAN_IMAGE=crazymax/fail2ban:latest
|
||||
CERT_COPIER_IMAGE=alpine:3.20
|
||||
|
||||
# Redis + async queue
|
||||
REDIS_URL=redis://redis:6379
|
||||
ELEKTRINE_QUEUE_NAME=elektrine:inbound
|
||||
ELEKTRINE_DLQ_NAME=elektrine:inbound:dlq
|
||||
|
||||
# Worker retry policy
|
||||
WEBHOOK_MAX_RETRIES=5
|
||||
WEBHOOK_RETRY_BASE_MS=1000
|
||||
|
||||
# Optional ops CIDRs for keyless access to /status, /healthz, and /metrics.
|
||||
# Leave blank to require X-API-Key by default.
|
||||
METRICS_ALLOWED_CIDRS=
|
||||
OPS_ALLOWED_CIDRS=
|
||||
|
||||
# Queue thresholds
|
||||
QUEUE_WARN_THRESHOLD=1000
|
||||
DLQ_CRIT_THRESHOLD=10
|
||||
38
deployment/.env.same-server.example
Normal file
38
deployment/.env.same-server.example
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Same-server bare-metal deployment
|
||||
# Run this beside the main elektrine Docker stack.
|
||||
|
||||
HARAKA_DOMAIN=mail.example.com
|
||||
LOCAL_DOMAINS=example.com,mail.example.com
|
||||
|
||||
# Shared from the main Elektrine deployment
|
||||
PHOENIX_WEBHOOK_URL=http://elektrine_app:8080/api/haraka/inbound
|
||||
PHOENIX_VERIFY_URL=http://elektrine_app:8080/api/haraka/verify-recipient
|
||||
PHOENIX_DOMAINS_URL=http://elektrine_app:8080/api/haraka/domains
|
||||
|
||||
# Haraka -> Phoenix
|
||||
PHOENIX_API_KEY=replace-with-haraka-to-phoenix-key
|
||||
|
||||
# Phoenix -> Haraka
|
||||
HARAKA_HTTP_API_KEY=replace-with-phoenix-to-haraka-key
|
||||
|
||||
# Optional legacy shared key
|
||||
# HARAKA_API_KEY=
|
||||
|
||||
# Match the main Elektrine Caddy volume name.
|
||||
MAIN_CADDY_VOLUME=elektrine_caddy_data
|
||||
MAIN_APP_NETWORK=docker_elektrine_network
|
||||
|
||||
# Haraka outbound API stays loopback-only on the same host.
|
||||
HARAKA_HTTP_BIND_PORT=18080
|
||||
|
||||
# Optional trusted networks that may read /status, /healthz, and /metrics without X-API-Key.
|
||||
OPS_ALLOWED_CIDRS=
|
||||
METRICS_ALLOWED_CIDRS=
|
||||
|
||||
HARAKA_IMAGE=git.elektrine.com/elektrine/elektrine-haraka
|
||||
HARAKA_IMAGE_TAG=latest
|
||||
REDIS_URL=redis://redis:6379
|
||||
ELEKTRINE_QUEUE_NAME=elektrine:inbound
|
||||
ELEKTRINE_DLQ_NAME=elektrine:inbound:dlq
|
||||
WEBHOOK_MAX_RETRIES=5
|
||||
WEBHOOK_RETRY_BASE_MS=1000
|
||||
71
deployment/README.md
Normal file
71
deployment/README.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Deployment
|
||||
|
||||
Compose-based production deployment for the multi-role Haraka topology. This is
|
||||
the supported deployment path for this repository.
|
||||
|
||||
## Services
|
||||
|
||||
- `haraka-inbound`: public MX listener on `:25`
|
||||
- `haraka-outbound`: internal HTTP send/ops API on `:8080`
|
||||
- `haraka-worker`: Redis consumer for inbound delivery to Phoenix
|
||||
- `haraka-submission`: optional authenticated submission role, not published by default
|
||||
- `redis`, `clamav`, `spamassassin`
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cd deployment
|
||||
cp .env.example .env
|
||||
# edit .env
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## Same-server setup
|
||||
|
||||
For a single-host deployment beside the main Elektrine stack:
|
||||
|
||||
```bash
|
||||
cp .env.same-server.example .env
|
||||
# edit .env
|
||||
../scripts/deploy/docker_deploy.sh
|
||||
```
|
||||
|
||||
This uses `docker-compose.same-server.yml`, publishes MX on `25`, binds the
|
||||
Haraka HTTP API to `127.0.0.1:18080`, and reads certificates from the main
|
||||
Elektrine Caddy volume. It also joins the main Elektrine Docker network so
|
||||
Haraka can call `http://elektrine_app:8080` directly.
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# inspect all services
|
||||
docker compose ps
|
||||
|
||||
# view role logs
|
||||
docker compose logs -f haraka-inbound
|
||||
docker compose logs -f haraka-submission
|
||||
docker compose logs -f haraka-outbound
|
||||
docker compose logs -f haraka-worker
|
||||
|
||||
# queue state
|
||||
docker compose exec redis redis-cli LLEN elektrine:inbound
|
||||
docker compose exec redis redis-cli LLEN elektrine:inbound:dlq
|
||||
|
||||
# queue alert check (non-zero exit when thresholds exceeded)
|
||||
../scripts/check-queues.sh
|
||||
|
||||
# health checks from the host running Haraka
|
||||
curl -s -H 'X-API-Key: <key>' http://127.0.0.1:18080/status
|
||||
curl -s -H 'X-API-Key: <key>' http://127.0.0.1:18080/metrics
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Elektrine should call the HTTP API through `HARAKA_BASE_URL`, commonly `http://127.0.0.1:18080` on same-host installs or `http://haraka-outbound:8080` on a shared Docker network.
|
||||
- SMTP HTTP API traffic is served directly by `haraka-outbound:8080` inside Compose.
|
||||
- Inbound SMTP processing is async: accept fast on `haraka-inbound`, parse/deliver from `haraka-worker`.
|
||||
- Client SMTP submission normally lives in Elektrine. Publish `haraka-submission` only if you intentionally want Haraka-managed submission.
|
||||
- `/status`, `/healthz`, and `/metrics` accept `X-API-Key` by default.
|
||||
- Set `OPS_ALLOWED_CIDRS` and `METRICS_ALLOWED_CIDRS` in `.env` only if you also want keyless access from trusted networks.
|
||||
- Use immutable `HARAKA_IMAGE_TAG` values for reproducible rollouts.
|
||||
- Override `HARAKA_IMAGE` in `.env` if you need to pull from a different registry/repo.
|
||||
223
deployment/docker-compose.same-server.yml
Normal file
223
deployment/docker-compose.same-server.yml
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
services:
|
||||
haraka-inbound:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
ports:
|
||||
- "25:25"
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=inbound-mx
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- LOCAL_DOMAINS=${LOCAL_DOMAINS:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- ELEKTRINE_QUEUE_NAME=${ELEKTRINE_QUEUE_NAME:-elektrine:inbound}
|
||||
- ELEKTRINE_DLQ_NAME=${ELEKTRINE_DLQ_NAME:-elektrine:inbound:dlq}
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
- clamav
|
||||
- spamassassin
|
||||
|
||||
haraka-submission:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=submission
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- LOCAL_DOMAINS=${LOCAL_DOMAINS:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
haraka-outbound:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
ports:
|
||||
- "127.0.0.1:${HARAKA_HTTP_BIND_PORT:-18080}:8080"
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=outbound-relay
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- HARAKA_HTTP_API_KEY=${HARAKA_HTTP_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- LOCAL_DOMAINS=${LOCAL_DOMAINS:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- HARAKA_HTTP_PORT=8080
|
||||
- HARAKA_HTTP_HOST=0.0.0.0
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
haraka-worker:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
command: ["node", "/app/scripts/elektrine-worker.js"]
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=worker
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- LOCAL_DOMAINS=${LOCAL_DOMAINS:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- ELEKTRINE_QUEUE_NAME=${ELEKTRINE_QUEUE_NAME:-elektrine:inbound}
|
||||
- ELEKTRINE_DLQ_NAME=${ELEKTRINE_DLQ_NAME:-elektrine:inbound:dlq}
|
||||
- WEBHOOK_MAX_RETRIES=${WEBHOOK_MAX_RETRIES:-5}
|
||||
- WEBHOOK_RETRY_BASE_MS=${WEBHOOK_RETRY_BASE_MS:-1000}
|
||||
restart: unless-stopped
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
cert-copier:
|
||||
image: ${CERT_COPIER_IMAGE:-alpine:3.20}
|
||||
volumes:
|
||||
- main-caddy-data:/caddy-data:ro
|
||||
- ssl-certs:/ssl-certs
|
||||
environment:
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
command: >
|
||||
sh -c "
|
||||
while true; do
|
||||
if [ -f /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wildcard_.elektrine.com/wildcard_.elektrine.com.crt ]; then
|
||||
cp /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wildcard_.elektrine.com/wildcard_.elektrine.com.crt /ssl-certs/cert.crt
|
||||
cp /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/wildcard_.elektrine.com/wildcard_.elektrine.com.key /ssl-certs/cert.key
|
||||
chmod 644 /ssl-certs/cert.crt
|
||||
chmod 600 /ssl-certs/cert.key
|
||||
elif [ -f /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/$$HARAKA_DOMAIN/$$HARAKA_DOMAIN.crt ]; then
|
||||
cp /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/$$HARAKA_DOMAIN/$$HARAKA_DOMAIN.crt /ssl-certs/cert.crt
|
||||
cp /caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/$$HARAKA_DOMAIN/$$HARAKA_DOMAIN.key /ssl-certs/cert.key
|
||||
chmod 644 /ssl-certs/cert.crt
|
||||
chmod 600 /ssl-certs/cert.key
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
"
|
||||
networks:
|
||||
- haraka-network
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: ${REDIS_IMAGE:-redis:7-alpine}
|
||||
command:
|
||||
- redis-server
|
||||
- --appendonly
|
||||
- yes
|
||||
- --appendfsync
|
||||
- everysec
|
||||
- --save
|
||||
- "900 1"
|
||||
- --save
|
||||
- "300 10"
|
||||
- --save
|
||||
- "60 10000"
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
|
||||
clamav:
|
||||
image: ${CLAMAV_IMAGE:-clamav/clamav:stable}
|
||||
ports:
|
||||
- "127.0.0.1:3310:3310"
|
||||
volumes:
|
||||
- clamav-data:/var/lib/clamav
|
||||
environment:
|
||||
- CLAMAV_NO_FRESHCLAM=false
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
|
||||
spamassassin:
|
||||
image: ${SPAMASSASSIN_IMAGE:-dinkel/spamassassin:latest}
|
||||
ports:
|
||||
- "127.0.0.1:783:783"
|
||||
volumes:
|
||||
- spamassassin-data:/var/lib/spamassassin
|
||||
environment:
|
||||
- SA_UPDATE=1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
|
||||
networks:
|
||||
haraka-network:
|
||||
driver: bridge
|
||||
main-app-network:
|
||||
external: true
|
||||
name: ${MAIN_APP_NETWORK:-docker_elektrine_network}
|
||||
|
||||
volumes:
|
||||
main-caddy-data:
|
||||
external: true
|
||||
name: ${MAIN_CADDY_VOLUME:-elektrine_caddy_data}
|
||||
redis-data:
|
||||
ssl-certs:
|
||||
clamav-data:
|
||||
spamassassin-data:
|
||||
240
deployment/docker-compose.yml
Normal file
240
deployment/docker-compose.yml
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
services:
|
||||
haraka-inbound:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
# For local development, comment out image and uncomment build:
|
||||
# build: ../
|
||||
ports:
|
||||
- "25:25"
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=inbound-mx
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- ELEKTRINE_QUEUE_NAME=${ELEKTRINE_QUEUE_NAME:-elektrine:inbound}
|
||||
- ELEKTRINE_DLQ_NAME=${ELEKTRINE_DLQ_NAME:-elektrine:inbound:dlq}
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
- clamav
|
||||
- spamassassin
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: '2.0'
|
||||
healthcheck:
|
||||
test: ["CMD", "nc", "-z", "localhost", "25"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
haraka-submission:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=submission
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 768M
|
||||
cpus: '1.0'
|
||||
|
||||
haraka-outbound:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
ports:
|
||||
- "127.0.0.1:${HARAKA_HTTP_BIND_PORT:-18080}:8080"
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
- ssl-certs:/app/ssl
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=outbound-relay
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- HARAKA_HTTP_API_KEY=${HARAKA_HTTP_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- HARAKA_HTTP_PORT=8080
|
||||
- HARAKA_HTTP_HOST=0.0.0.0
|
||||
- HARAKA_DOMAIN=${HARAKA_DOMAIN:-mail.example.com}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 768M
|
||||
cpus: '1.0'
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"const key = process.env.HARAKA_HTTP_API_KEY || process.env.HARAKA_API_KEY || ''; const req = require('http').request('http://127.0.0.1:8080/status', { headers: key ? { 'x-api-key': key } : {} }, (res) => process.exit(res.statusCode === 200 ? 0 : 1)); req.on('error', () => process.exit(1)); req.end();"
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
haraka-worker:
|
||||
image: ${HARAKA_IMAGE:-git.elektrine.com/elektrine/elektrine-haraka}:${HARAKA_IMAGE_TAG:-latest}
|
||||
command: ["node", "/app/scripts/elektrine-worker.js"]
|
||||
volumes:
|
||||
- ../logs:/app/logs
|
||||
- ../config:/app/config
|
||||
- ../config/dkim:/data/haraka/dkim
|
||||
- ../plugins:/app/plugins:ro
|
||||
- ../lib:/app/lib:ro
|
||||
- ../scripts:/app/scripts:ro
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HARAKA_ROLE=worker
|
||||
- HARAKA_API_KEY=${HARAKA_API_KEY:-}
|
||||
- PHOENIX_API_KEY=${PHOENIX_API_KEY:-}
|
||||
- PHOENIX_WEBHOOK_URL=${PHOENIX_WEBHOOK_URL:-}
|
||||
- PHOENIX_VERIFY_URL=${PHOENIX_VERIFY_URL:-}
|
||||
- PHOENIX_DOMAINS_URL=${PHOENIX_DOMAINS_URL:-}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
- ELEKTRINE_QUEUE_NAME=${ELEKTRINE_QUEUE_NAME:-elektrine:inbound}
|
||||
- ELEKTRINE_DLQ_NAME=${ELEKTRINE_DLQ_NAME:-elektrine:inbound:dlq}
|
||||
- WEBHOOK_MAX_RETRIES=${WEBHOOK_MAX_RETRIES:-5}
|
||||
- WEBHOOK_RETRY_BASE_MS=${WEBHOOK_RETRY_BASE_MS:-1000}
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
- main-app-network
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
redis:
|
||||
image: ${REDIS_IMAGE:-redis:7-alpine}
|
||||
command:
|
||||
- redis-server
|
||||
- --appendonly
|
||||
- yes
|
||||
- --appendfsync
|
||||
- everysec
|
||||
- --save
|
||||
- "900 1"
|
||||
- --save
|
||||
- "300 10"
|
||||
- --save
|
||||
- "60 10000"
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "PING"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
clamav:
|
||||
image: ${CLAMAV_IMAGE:-clamav/clamav:stable}
|
||||
ports:
|
||||
- "127.0.0.1:3310:3310"
|
||||
volumes:
|
||||
- clamav-data:/var/lib/clamav
|
||||
environment:
|
||||
- CLAMAV_NO_FRESHCLAM=false
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
healthcheck:
|
||||
test: ["CMD", "/usr/local/bin/clamdcheck.sh"]
|
||||
interval: 60s
|
||||
timeout: 120s
|
||||
retries: 3
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
cpus: '1.0'
|
||||
|
||||
spamassassin:
|
||||
image: ${SPAMASSASSIN_IMAGE:-dinkel/spamassassin:latest}
|
||||
ports:
|
||||
- "127.0.0.1:783:783"
|
||||
volumes:
|
||||
- spamassassin-data:/var/lib/spamassassin
|
||||
environment:
|
||||
- SA_UPDATE=1
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- haraka-network
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: '0.5'
|
||||
|
||||
networks:
|
||||
haraka-network:
|
||||
driver: bridge
|
||||
main-app-network:
|
||||
external: true
|
||||
name: ${MAIN_APP_NETWORK:-docker_elektrine_network}
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
ssl-certs:
|
||||
clamav-data:
|
||||
spamassassin-data:
|
||||
96
deployment/start.sh
Executable file
96
deployment/start.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Start elektrine-haraka step by step
|
||||
set -euo pipefail
|
||||
|
||||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||
compose() { docker compose "$@"; }
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
compose() { docker-compose "$@"; }
|
||||
else
|
||||
echo "ERROR: docker compose or docker-compose is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting elektrine-haraka deployment..."
|
||||
|
||||
# Validate required environment variables from shell and/or deployment/.env
|
||||
required_vars=(
|
||||
"HARAKA_DOMAIN"
|
||||
"PHOENIX_WEBHOOK_URL"
|
||||
"PHOENIX_VERIFY_URL"
|
||||
"PHOENIX_DOMAINS_URL"
|
||||
)
|
||||
|
||||
get_var() {
|
||||
local var_name="$1"
|
||||
local value="${!var_name:-}"
|
||||
|
||||
if [ -n "$value" ]; then
|
||||
printf "%s" "$value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f .env ]; then
|
||||
value=$(grep -E "^${var_name}=" .env | tail -n 1 | cut -d '=' -f 2-)
|
||||
printf "%s" "$value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf ""
|
||||
}
|
||||
|
||||
for var_name in "${required_vars[@]}"; do
|
||||
if [ -z "$(get_var "$var_name")" ]; then
|
||||
echo "ERROR: ${var_name} is required (set in shell or deployment/.env)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$(get_var "PHOENIX_API_KEY")" ] && [ -z "$(get_var "HARAKA_API_KEY")" ]; then
|
||||
echo "ERROR: set PHOENIX_API_KEY (recommended) or HARAKA_API_KEY (legacy fallback)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$(get_var "HARAKA_HTTP_API_KEY")" ] && [ -z "$(get_var "HARAKA_API_KEY")" ]; then
|
||||
echo "ERROR: set HARAKA_HTTP_API_KEY (recommended) or HARAKA_API_KEY (legacy fallback)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate compose interpolation early
|
||||
compose config >/dev/null
|
||||
|
||||
# 1. Start supporting services first
|
||||
echo "Starting Redis, ClamAV, and SpamAssassin..."
|
||||
compose up -d redis clamav spamassassin
|
||||
|
||||
# 2. Wait for services to be ready
|
||||
echo "Waiting for services to initialize..."
|
||||
sleep 10
|
||||
|
||||
# 3. Start Haraka roles and worker
|
||||
echo "Starting Haraka inbound/submission/outbound roles and worker..."
|
||||
compose up -d haraka-inbound haraka-submission haraka-outbound haraka-worker
|
||||
|
||||
# 4. Wait for Haraka roles to be ready
|
||||
sleep 5
|
||||
|
||||
# 5. Start Caddy (handles HTTPS/TLS automatically)
|
||||
echo "Starting Caddy reverse proxy..."
|
||||
compose up -d caddy
|
||||
|
||||
# 6. Start cert-copier (copies Caddy certs to Haraka for STARTTLS)
|
||||
echo "Starting certificate copier..."
|
||||
compose up -d cert-copier
|
||||
|
||||
echo ""
|
||||
echo "Deployment complete!"
|
||||
echo ""
|
||||
echo "Services running:"
|
||||
compose ps
|
||||
|
||||
echo ""
|
||||
echo "Test your setup:"
|
||||
echo " SMTP: telnet $(hostname) 25"
|
||||
echo " Submission: telnet $(hostname) 587"
|
||||
echo " API: curl -H 'X-API-Key: <key>' https://$(hostname)/api/v1/send"
|
||||
Loading…
Add table
Add a link
Reference in a new issue