feat(#swarm): Add Docker Swarm deployment with AI provider configuration
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Add setup-wizard.sh for interactive configuration - Add docker-compose.swarm.yml optimized for swarm deployment - Make CLAUDE_API_KEY optional based on AI_PROVIDER setting - Support multiple AI providers: Ollama, Claude API, OpenAI - Add BETTER_AUTH_SECRET to .env.example - Update deploy-swarm.sh to validate AI provider config - Add comprehensive documentation (DOCKER-SWARM.md, SWARM-QUICKREF.md) Changes: - AI_PROVIDER env var controls which AI backend to use - Ollama is default (no API key required) - Claude API and OpenAI require respective API keys - Deployment script validates based on selected provider - Removed Authentik services from swarm compose (using external) - Configured for upstream Traefik integration
This commit is contained in:
32
.env.example
32
.env.example
@@ -90,6 +90,14 @@ AUTHENTIK_PORT_HTTPS=9443
|
|||||||
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
|
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
|
||||||
JWT_EXPIRATION=24h
|
JWT_EXPIRATION=24h
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# BetterAuth Configuration
|
||||||
|
# ======================
|
||||||
|
# CRITICAL: Generate a random secret key with at least 32 characters
|
||||||
|
# This is used by BetterAuth for session management and CSRF protection
|
||||||
|
# Example: openssl rand -base64 32
|
||||||
|
BETTER_AUTH_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
|
||||||
|
|
||||||
# ======================
|
# ======================
|
||||||
# Encryption (Credential Security)
|
# Encryption (Credential Security)
|
||||||
# ======================
|
# ======================
|
||||||
@@ -264,6 +272,30 @@ RATE_LIMIT_STORAGE=redis
|
|||||||
# Health endpoints (/health/*) remain unauthenticated
|
# Health endpoints (/health/*) remain unauthenticated
|
||||||
ORCHESTRATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
ORCHESTRATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# AI Provider Configuration
|
||||||
|
# ======================
|
||||||
|
# Choose the AI provider for orchestrator agents
|
||||||
|
# Options: ollama, claude, openai
|
||||||
|
# Default: ollama (no API key required)
|
||||||
|
AI_PROVIDER=ollama
|
||||||
|
|
||||||
|
# Ollama Configuration (when AI_PROVIDER=ollama)
|
||||||
|
# For local Ollama: http://localhost:11434
|
||||||
|
# For remote Ollama: http://your-ollama-server:11434
|
||||||
|
OLLAMA_MODEL=llama3.1:latest
|
||||||
|
|
||||||
|
# Claude API Configuration (when AI_PROVIDER=claude)
|
||||||
|
# OPTIONAL: Only required if AI_PROVIDER=claude
|
||||||
|
# Get your API key from: https://console.anthropic.com/
|
||||||
|
# Note: Claude Max subscription users should use AI_PROVIDER=ollama instead
|
||||||
|
# CLAUDE_API_KEY=sk-ant-...
|
||||||
|
|
||||||
|
# OpenAI API Configuration (when AI_PROVIDER=openai)
|
||||||
|
# OPTIONAL: Only required if AI_PROVIDER=openai
|
||||||
|
# Get your API key from: https://platform.openai.com/api-keys
|
||||||
|
# OPENAI_API_KEY=sk-...
|
||||||
|
|
||||||
# ======================
|
# ======================
|
||||||
# Logging & Debugging
|
# Logging & Debugging
|
||||||
# ======================
|
# ======================
|
||||||
|
|||||||
147
.env.swarm.example
Normal file
147
.env.swarm.example
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# ==============================================
|
||||||
|
# Mosaic Stack - Docker Swarm Configuration
|
||||||
|
# ==============================================
|
||||||
|
# Copy this file to .env for Docker Swarm deployment
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Application Ports (Internal)
|
||||||
|
# ======================
|
||||||
|
API_PORT=3001
|
||||||
|
API_HOST=0.0.0.0
|
||||||
|
WEB_PORT=3000
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Domain Configuration (Traefik)
|
||||||
|
# ======================
|
||||||
|
# These domains must be configured in your DNS or /etc/hosts
|
||||||
|
MOSAIC_API_DOMAIN=api.mosaicstack.dev
|
||||||
|
MOSAIC_WEB_DOMAIN=mosaic.mosaicstack.dev
|
||||||
|
MOSAIC_AUTH_DOMAIN=auth.mosaicstack.dev
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Web Configuration
|
||||||
|
# ======================
|
||||||
|
# Use the Traefik domain for the API URL
|
||||||
|
NEXT_PUBLIC_APP_URL=http://mosaic.mosaicstack.dev
|
||||||
|
NEXT_PUBLIC_API_URL=http://api.mosaicstack.dev
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# PostgreSQL Database
|
||||||
|
# ======================
|
||||||
|
DATABASE_URL=postgresql://mosaic:REPLACE_WITH_SECURE_PASSWORD@postgres:5432/mosaic
|
||||||
|
POSTGRES_USER=mosaic
|
||||||
|
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||||
|
POSTGRES_DB=mosaic
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
# PostgreSQL Performance Tuning
|
||||||
|
POSTGRES_SHARED_BUFFERS=256MB
|
||||||
|
POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
|
||||||
|
POSTGRES_MAX_CONNECTIONS=100
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Valkey Cache
|
||||||
|
# ======================
|
||||||
|
VALKEY_URL=redis://valkey:6379
|
||||||
|
VALKEY_HOST=valkey
|
||||||
|
VALKEY_PORT=6379
|
||||||
|
VALKEY_MAXMEMORY=256mb
|
||||||
|
|
||||||
|
# Knowledge Module Cache Configuration
|
||||||
|
KNOWLEDGE_CACHE_ENABLED=true
|
||||||
|
KNOWLEDGE_CACHE_TTL=300
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Authentication (Authentik OIDC)
|
||||||
|
# ======================
|
||||||
|
OIDC_ENABLED=true
|
||||||
|
OIDC_ISSUER=http://auth.mosaicstack.dev/application/o/mosaic-stack/
|
||||||
|
OIDC_CLIENT_ID=your-client-id-here
|
||||||
|
OIDC_CLIENT_SECRET=your-client-secret-here
|
||||||
|
OIDC_REDIRECT_URI=http://api.mosaicstack.dev/auth/callback/authentik
|
||||||
|
|
||||||
|
# Authentik PostgreSQL Database
|
||||||
|
AUTHENTIK_POSTGRES_USER=authentik
|
||||||
|
AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||||
|
AUTHENTIK_POSTGRES_DB=authentik
|
||||||
|
|
||||||
|
# Authentik Configuration
|
||||||
|
AUTHENTIK_SECRET_KEY=REPLACE_WITH_RANDOM_SECRET_MINIMUM_50_CHARS
|
||||||
|
AUTHENTIK_ERROR_REPORTING=false
|
||||||
|
AUTHENTIK_BOOTSTRAP_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
|
||||||
|
AUTHENTIK_BOOTSTRAP_EMAIL=admin@mosaicstack.dev
|
||||||
|
AUTHENTIK_COOKIE_DOMAIN=.mosaicstack.dev
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# JWT Configuration
|
||||||
|
# ======================
|
||||||
|
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
|
||||||
|
JWT_EXPIRATION=24h
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Encryption (Credential Security)
|
||||||
|
# ======================
|
||||||
|
# Generate with: openssl rand -hex 32
|
||||||
|
ENCRYPTION_KEY=REPLACE_WITH_64_CHAR_HEX_STRING_GENERATE_WITH_OPENSSL_RAND_HEX_32
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# OpenBao Secrets Management
|
||||||
|
# ======================
|
||||||
|
OPENBAO_ADDR=http://openbao:8200
|
||||||
|
OPENBAO_PORT=8200
|
||||||
|
# For development only - remove in production
|
||||||
|
OPENBAO_DEV_ROOT_TOKEN_ID=root
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Ollama (Optional AI Service)
|
||||||
|
# ======================
|
||||||
|
OLLAMA_ENDPOINT=http://ollama:11434
|
||||||
|
OLLAMA_PORT=11434
|
||||||
|
OLLAMA_EMBEDDING_MODEL=mxbai-embed-large
|
||||||
|
|
||||||
|
# Semantic Search Configuration
|
||||||
|
SEMANTIC_SEARCH_SIMILARITY_THRESHOLD=0.5
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# OpenAI API (Optional)
|
||||||
|
# ======================
|
||||||
|
# OPENAI_API_KEY=sk-...
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Application Environment
|
||||||
|
# ======================
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Gitea Integration (Coordinator)
|
||||||
|
# ======================
|
||||||
|
GITEA_URL=https://git.mosaicstack.dev
|
||||||
|
GITEA_BOT_USERNAME=mosaic
|
||||||
|
GITEA_BOT_TOKEN=REPLACE_WITH_COORDINATOR_BOT_API_TOKEN
|
||||||
|
GITEA_BOT_PASSWORD=REPLACE_WITH_COORDINATOR_BOT_PASSWORD
|
||||||
|
GITEA_REPO_OWNER=mosaic
|
||||||
|
GITEA_REPO_NAME=stack
|
||||||
|
GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET
|
||||||
|
COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Rate Limiting
|
||||||
|
# ======================
|
||||||
|
RATE_LIMIT_TTL=60
|
||||||
|
RATE_LIMIT_GLOBAL_LIMIT=100
|
||||||
|
RATE_LIMIT_WEBHOOK_LIMIT=60
|
||||||
|
RATE_LIMIT_COORDINATOR_LIMIT=100
|
||||||
|
RATE_LIMIT_HEALTH_LIMIT=300
|
||||||
|
RATE_LIMIT_STORAGE=redis
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Orchestrator Configuration
|
||||||
|
# ======================
|
||||||
|
ORCHESTRATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
|
||||||
|
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Logging & Debugging
|
||||||
|
# ======================
|
||||||
|
LOG_LEVEL=info
|
||||||
|
DEBUG=false
|
||||||
299
DOCKER-SWARM.md
Normal file
299
DOCKER-SWARM.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# Mosaic Stack - Docker Swarm Deployment
|
||||||
|
|
||||||
|
This guide covers deploying Mosaic Stack to a Docker Swarm cluster with Traefik reverse proxy integration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Docker Swarm initialized:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker swarm init
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Traefik running on the swarm** with a network named `traefik-public`
|
||||||
|
|
||||||
|
3. **DNS or /etc/hosts configured** with your domain names:
|
||||||
|
- `mosaic.mosaicstack.dev` → Web UI
|
||||||
|
- `api.mosaicstack.dev` → API
|
||||||
|
- `auth.mosaicstack.dev` → Authentik SSO
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Configure Environment
|
||||||
|
|
||||||
|
Copy the swarm environment template:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.swarm.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env` and set the following **critical** values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Database passwords
|
||||||
|
POSTGRES_PASSWORD=your-secure-password-here
|
||||||
|
AUTHENTIK_POSTGRES_PASSWORD=your-secure-password-here
|
||||||
|
|
||||||
|
# Secrets (generate with openssl rand -hex 32 or openssl rand -base64 50)
|
||||||
|
AUTHENTIK_SECRET_KEY=$(openssl rand -base64 50)
|
||||||
|
JWT_SECRET=$(openssl rand -base64 32)
|
||||||
|
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||||
|
ORCHESTRATOR_API_KEY=$(openssl rand -base64 32)
|
||||||
|
COORDINATOR_API_KEY=$(openssl rand -base64 32)
|
||||||
|
|
||||||
|
# Claude API Key
|
||||||
|
CLAUDE_API_KEY=your-claude-api-key
|
||||||
|
|
||||||
|
# Authentik Bootstrap
|
||||||
|
AUTHENTIK_BOOTSTRAP_PASSWORD=your-admin-password
|
||||||
|
AUTHENTIK_BOOTSTRAP_EMAIL=admin@yourdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Traefik Network (if not exists)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create --driver=overlay traefik-public
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Deploy the Stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy-swarm.sh mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker stack deploy -c docker-compose.swarm.yml mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Verify Deployment
|
||||||
|
|
||||||
|
Check stack status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker stack services mosaic
|
||||||
|
docker stack ps mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
Check service logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service logs mosaic_api
|
||||||
|
docker service logs mosaic_web
|
||||||
|
docker service logs mosaic_postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stack Services
|
||||||
|
|
||||||
|
The following services will be deployed:
|
||||||
|
|
||||||
|
| Service | Internal Port | Traefik Domain | Description |
|
||||||
|
| ------------------ | ------------- | ------------------------ | ------------------------ |
|
||||||
|
| `web` | 3000 | `mosaic.mosaicstack.dev` | Next.js Web UI |
|
||||||
|
| `api` | 3001 | `api.mosaicstack.dev` | NestJS API |
|
||||||
|
| `authentik-server` | 9000 | `auth.mosaicstack.dev` | Authentik SSO |
|
||||||
|
| `postgres` | 5432 | - | PostgreSQL 17 + pgvector |
|
||||||
|
| `valkey` | 6379 | - | Redis-compatible cache |
|
||||||
|
| `openbao` | 8200 | - | Secrets vault |
|
||||||
|
| `ollama` | 11434 | - | LLM service (optional) |
|
||||||
|
| `orchestrator` | 3001 | - | Agent orchestrator |
|
||||||
|
|
||||||
|
## Traefik Integration
|
||||||
|
|
||||||
|
Services are automatically registered with Traefik using labels defined in `deploy.labels`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mosaic-web.rule=Host(`mosaic.mosaicstack.dev`)"
|
||||||
|
- "traefik.http.routers.mosaic-web.entrypoints=web"
|
||||||
|
- "traefik.http.services.mosaic-web.loadbalancer.server.port=3000"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Traefik labels MUST be under `deploy.labels` for Docker Swarm (not at service level).
|
||||||
|
|
||||||
|
## Accessing Services
|
||||||
|
|
||||||
|
Once deployed and Traefik is configured:
|
||||||
|
|
||||||
|
- **Web UI:** http://mosaic.mosaicstack.dev
|
||||||
|
- **API:** http://api.mosaicstack.dev
|
||||||
|
- **Authentik:** http://auth.mosaicstack.dev
|
||||||
|
|
||||||
|
## Scaling Services
|
||||||
|
|
||||||
|
Scale specific services:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scale web frontend to 3 replicas
|
||||||
|
docker service scale mosaic_web=3
|
||||||
|
|
||||||
|
# Scale API to 2 replicas
|
||||||
|
docker service scale mosaic_api=2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Database services (postgres, valkey) should NOT be scaled (remain at 1 replica).
|
||||||
|
|
||||||
|
## Updating Services
|
||||||
|
|
||||||
|
Update a specific service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild image
|
||||||
|
docker compose -f docker-compose.swarm.yml build api
|
||||||
|
|
||||||
|
# Update the service
|
||||||
|
docker service update --image mosaic-stack-api:latest mosaic_api
|
||||||
|
```
|
||||||
|
|
||||||
|
Or redeploy the entire stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy-swarm.sh mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rolling Updates
|
||||||
|
|
||||||
|
Docker Swarm supports rolling updates. To configure:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
deploy:
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
order: start-first
|
||||||
|
rollback_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Service Won't Start
|
||||||
|
|
||||||
|
Check service logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service logs mosaic_api --tail 100 --follow
|
||||||
|
```
|
||||||
|
|
||||||
|
Check service tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service ps mosaic_api --no-trunc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Traefik Not Routing
|
||||||
|
|
||||||
|
1. Verify service is on `traefik-public` network:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service inspect mosaic_web | grep -A 10 Networks
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Check Traefik dashboard for registered routes:
|
||||||
|
- Usually at http://traefik.yourdomain.com/dashboard/
|
||||||
|
|
||||||
|
3. Verify domain DNS/hosts resolution:
|
||||||
|
```bash
|
||||||
|
ping mosaic.mosaicstack.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Connection Issues
|
||||||
|
|
||||||
|
Check postgres is healthy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service logs mosaic_postgres --tail 50
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify DATABASE_URL in API service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker service inspect mosaic_api --format '{{json .Spec.TaskTemplate.ContainerSpec.Env}}' | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
### Volume Permissions
|
||||||
|
|
||||||
|
If volume permission errors occur, check service user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Orchestrator runs as user 1000:1000
|
||||||
|
docker service inspect mosaic_orchestrator | grep -A 5 User
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup & Restore
|
||||||
|
|
||||||
|
### Backup Volumes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup postgres data
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/postgres-backup-$(date +%Y%m%d).tar.gz -C /data .
|
||||||
|
|
||||||
|
# Backup authentik data
|
||||||
|
docker run --rm -v mosaic_authentik_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/authentik-backup-$(date +%Y%m%d).tar.gz -C /data .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Restore Volumes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restore postgres data
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar xzf /backup/postgres-backup-20260208.tar.gz -C /data
|
||||||
|
|
||||||
|
# Restore authentik data
|
||||||
|
docker run --rm -v mosaic_authentik_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar xzf /backup/authentik-backup-20260208.tar.gz -C /data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Removing the Stack
|
||||||
|
|
||||||
|
Remove all services and networks (volumes are preserved):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker stack rm mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove volumes (⚠️ **DATA WILL BE LOST**):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker volume rm mosaic_postgres_data
|
||||||
|
docker volume rm mosaic_valkey_data
|
||||||
|
docker volume rm mosaic_authentik_postgres_data
|
||||||
|
# ... etc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Change default passwords** in `.env` before deploying
|
||||||
|
2. **Use secrets management** for production:
|
||||||
|
```bash
|
||||||
|
echo "my-db-password" | docker secret create postgres_password -
|
||||||
|
```
|
||||||
|
3. **Enable TLS** in Traefik (Let's Encrypt)
|
||||||
|
4. **Restrict network access** using Docker network policies
|
||||||
|
5. **Run services as non-root** (orchestrator already does this)
|
||||||
|
|
||||||
|
## Differences from Docker Compose
|
||||||
|
|
||||||
|
Key differences when running in Swarm mode:
|
||||||
|
|
||||||
|
| Feature | Docker Compose | Docker Swarm |
|
||||||
|
| ---------------- | ---------------------------------- | ----------------------- |
|
||||||
|
| Container names | `container_name: foo` | Auto-generated |
|
||||||
|
| Restart policy | `restart: unless-stopped` | `deploy.restart_policy` |
|
||||||
|
| Labels (Traefik) | Service level | `deploy.labels` |
|
||||||
|
| Networks | `bridge` driver | `overlay` driver |
|
||||||
|
| Scaling | Manual `docker compose up --scale` | `docker service scale` |
|
||||||
|
| Updates | Stop/start containers | Rolling updates |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- **Compose file:** `docker-compose.swarm.yml`
|
||||||
|
- **Environment:** `.env.swarm.example`
|
||||||
|
- **Deployment script:** `deploy-swarm.sh`
|
||||||
|
- **Traefik example:** `../mosaic-telemetry/docker-compose.yml`
|
||||||
40
README.md
40
README.md
@@ -117,6 +117,46 @@ docker compose down
|
|||||||
|
|
||||||
See [Docker Deployment Guide](docs/1-getting-started/4-docker-deployment/) for complete documentation.
|
See [Docker Deployment Guide](docs/1-getting-started/4-docker-deployment/) for complete documentation.
|
||||||
|
|
||||||
|
### Docker Swarm Deployment (Production)
|
||||||
|
|
||||||
|
**Recommended for production deployments with high availability and auto-scaling.**
|
||||||
|
|
||||||
|
Deploy to a Docker Swarm cluster with integrated Traefik reverse proxy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Initialize swarm (if not already done)
|
||||||
|
docker swarm init
|
||||||
|
|
||||||
|
# 2. Create Traefik network
|
||||||
|
docker network create --driver=overlay traefik-public
|
||||||
|
|
||||||
|
# 3. Configure environment for swarm
|
||||||
|
cp .env.swarm.example .env
|
||||||
|
nano .env # Configure domains, passwords, API keys
|
||||||
|
|
||||||
|
# 4. Deploy stack
|
||||||
|
./deploy-swarm.sh mosaic
|
||||||
|
|
||||||
|
# 5. Check deployment status
|
||||||
|
docker stack services mosaic
|
||||||
|
docker stack ps mosaic
|
||||||
|
|
||||||
|
# Access services via Traefik
|
||||||
|
# Web: http://mosaic.mosaicstack.dev
|
||||||
|
# API: http://api.mosaicstack.dev
|
||||||
|
# Auth: http://auth.mosaicstack.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
|
||||||
|
- Automatic Traefik integration for routing
|
||||||
|
- Overlay networking for multi-host deployments
|
||||||
|
- Built-in health checks and rolling updates
|
||||||
|
- Horizontal scaling for web and API services
|
||||||
|
- Zero-downtime deployments
|
||||||
|
|
||||||
|
See [Docker Swarm Deployment Guide](DOCKER-SWARM.md) and [Quick Reference](SWARM-QUICKREF.md) for complete documentation.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
323
SWARM-QUICKREF.md
Normal file
323
SWARM-QUICKREF.md
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
# Docker Swarm Quick Reference
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Configure environment
|
||||||
|
cp .env.swarm.example .env
|
||||||
|
nano .env # Set passwords, API keys, domains
|
||||||
|
|
||||||
|
# 2. Create Traefik network (if needed)
|
||||||
|
docker network create --driver=overlay traefik-public
|
||||||
|
|
||||||
|
# 3. Deploy stack
|
||||||
|
./deploy-swarm.sh mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Stack Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy/update stack
|
||||||
|
docker stack deploy -c docker-compose.swarm.yml mosaic
|
||||||
|
|
||||||
|
# List all stacks
|
||||||
|
docker stack ls
|
||||||
|
|
||||||
|
# Remove stack
|
||||||
|
docker stack rm mosaic
|
||||||
|
|
||||||
|
# List services in stack
|
||||||
|
docker stack services mosaic
|
||||||
|
|
||||||
|
# List tasks in stack
|
||||||
|
docker stack ps mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all services
|
||||||
|
docker service ls
|
||||||
|
|
||||||
|
# Inspect service
|
||||||
|
docker service inspect mosaic_api
|
||||||
|
|
||||||
|
# View service logs
|
||||||
|
docker service logs mosaic_api --tail 100 --follow
|
||||||
|
|
||||||
|
# Scale service
|
||||||
|
docker service scale mosaic_web=3
|
||||||
|
|
||||||
|
# Update service (force redeploy)
|
||||||
|
docker service update --force mosaic_api
|
||||||
|
|
||||||
|
# Update service image
|
||||||
|
docker service update --image mosaic-stack-api:latest mosaic_api
|
||||||
|
|
||||||
|
# Rollback service
|
||||||
|
docker service rollback mosaic_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch service status
|
||||||
|
watch -n 2 'docker service ls'
|
||||||
|
|
||||||
|
# Service resource usage
|
||||||
|
docker stats $(docker ps --filter label=com.docker.swarm.service.name=mosaic_api -q)
|
||||||
|
|
||||||
|
# Check service placement
|
||||||
|
docker service ps mosaic_api --format "table {{.Name}}\t{{.Node}}\t{{.CurrentState}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check why service failed
|
||||||
|
docker service ps mosaic_api --no-trunc
|
||||||
|
|
||||||
|
# View recent logs with timestamps
|
||||||
|
docker service logs mosaic_api --timestamps --tail 50
|
||||||
|
|
||||||
|
# Follow logs in real-time
|
||||||
|
docker service logs mosaic_api --follow
|
||||||
|
|
||||||
|
# Exec into running container
|
||||||
|
docker exec -it $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_api) sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List networks
|
||||||
|
docker network ls
|
||||||
|
|
||||||
|
# Inspect traefik-public network
|
||||||
|
docker network inspect traefik-public
|
||||||
|
|
||||||
|
# List containers on traefik-public
|
||||||
|
docker network inspect traefik-public --format '{{range .Containers}}{{.Name}} {{end}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Volume Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List volumes
|
||||||
|
docker volume ls --filter label=com.docker.stack.namespace=mosaic
|
||||||
|
|
||||||
|
# Inspect volume
|
||||||
|
docker volume inspect mosaic_postgres_data
|
||||||
|
|
||||||
|
# Backup volume
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/postgres-$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
|
||||||
|
|
||||||
|
# Restore volume
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar xzf /backup/postgres-20260208-143022.tar.gz -C /data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service-Specific Commands
|
||||||
|
|
||||||
|
### Database (PostgreSQL)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to database
|
||||||
|
docker exec -it $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_postgres) \
|
||||||
|
psql -U mosaic -d mosaic
|
||||||
|
|
||||||
|
# Run migrations (from API container)
|
||||||
|
docker exec $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_api) \
|
||||||
|
pnpm prisma migrate deploy
|
||||||
|
|
||||||
|
# View database logs
|
||||||
|
docker service logs mosaic_postgres --tail 100
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View API logs
|
||||||
|
docker service logs mosaic_api --follow
|
||||||
|
|
||||||
|
# Check API health
|
||||||
|
curl http://api.mosaicstack.dev/health
|
||||||
|
|
||||||
|
# Force API redeploy
|
||||||
|
docker service update --force mosaic_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View web logs
|
||||||
|
docker service logs mosaic_web --follow
|
||||||
|
|
||||||
|
# Scale web to 3 replicas
|
||||||
|
docker service scale mosaic_web=3
|
||||||
|
|
||||||
|
# Check web health
|
||||||
|
curl http://mosaic.mosaicstack.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentik
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View Authentik logs
|
||||||
|
docker service logs mosaic_authentik-server --follow
|
||||||
|
docker service logs mosaic_authentik-worker --follow
|
||||||
|
|
||||||
|
# Access Authentik UI
|
||||||
|
open http://auth.mosaicstack.dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Service Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Check service tasks
|
||||||
|
docker service ps mosaic_api --no-trunc
|
||||||
|
|
||||||
|
# 2. View service logs
|
||||||
|
docker service logs mosaic_api --tail 100
|
||||||
|
|
||||||
|
# 3. Check if image exists
|
||||||
|
docker images | grep mosaic-stack-api
|
||||||
|
|
||||||
|
# 4. Rebuild and update
|
||||||
|
docker compose -f docker-compose.swarm.yml build api
|
||||||
|
docker service update --image mosaic-stack-api:latest mosaic_api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Traefik Not Routing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Verify service is on traefik-public network
|
||||||
|
docker service inspect mosaic_web | grep -A 10 Networks
|
||||||
|
|
||||||
|
# 2. Check Traefik labels
|
||||||
|
docker service inspect mosaic_web --format '{{json .Spec.Labels}}' | jq
|
||||||
|
|
||||||
|
# 3. Verify DNS resolution
|
||||||
|
ping mosaic.mosaicstack.dev
|
||||||
|
|
||||||
|
# 4. Check Traefik logs (if Traefik is a service)
|
||||||
|
docker service logs traefik --tail 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Connection Failed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Check postgres is running
|
||||||
|
docker service ls | grep postgres
|
||||||
|
|
||||||
|
# 2. Check postgres health
|
||||||
|
docker service ps mosaic_postgres
|
||||||
|
|
||||||
|
# 3. View postgres logs
|
||||||
|
docker service logs mosaic_postgres --tail 50
|
||||||
|
|
||||||
|
# 4. Test connection from API container
|
||||||
|
docker exec $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_api) \
|
||||||
|
sh -c 'nc -zv postgres 5432'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Out of Memory / Resources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check node resources
|
||||||
|
docker node ls
|
||||||
|
docker node inspect self --format '{{json .Description.Resources}}' | jq
|
||||||
|
|
||||||
|
# Check service resource limits
|
||||||
|
docker service inspect mosaic_api --format '{{json .Spec.TaskTemplate.Resources}}' | jq
|
||||||
|
|
||||||
|
# Update resource limits
|
||||||
|
docker service update --limit-memory 1g --reserve-memory 512m mosaic_api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Aliases
|
||||||
|
|
||||||
|
Add to `~/.bashrc` or `~/.zshrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stack shortcuts
|
||||||
|
alias dss='docker stack services'
|
||||||
|
alias dsp='docker stack ps'
|
||||||
|
alias dsl='docker service logs'
|
||||||
|
alias dsi='docker service inspect'
|
||||||
|
alias dsu='docker service update'
|
||||||
|
|
||||||
|
# Mosaic-specific
|
||||||
|
alias mosaic-logs='docker service logs mosaic_api --follow'
|
||||||
|
alias mosaic-status='docker stack services mosaic'
|
||||||
|
alias mosaic-ps='docker stack ps mosaic'
|
||||||
|
alias mosaic-deploy='./deploy-swarm.sh mosaic'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Emergency Procedures
|
||||||
|
|
||||||
|
### Complete Stack Restart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Remove stack (keeps volumes)
|
||||||
|
docker stack rm mosaic
|
||||||
|
|
||||||
|
# 2. Wait for cleanup (30 seconds)
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# 3. Redeploy
|
||||||
|
./deploy-swarm.sh mosaic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Recovery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Stop API to prevent writes
|
||||||
|
docker service scale mosaic_api=0
|
||||||
|
|
||||||
|
# 2. Backup current database
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/postgres-emergency-$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
|
||||||
|
|
||||||
|
# 3. Stop postgres
|
||||||
|
docker service scale mosaic_postgres=0
|
||||||
|
|
||||||
|
# 4. Restore from backup
|
||||||
|
docker run --rm -v mosaic_postgres_data:/data -v $(pwd):/backup alpine \
|
||||||
|
sh -c 'rm -rf /data/* && tar xzf /backup/postgres-20260208.tar.gz -C /data'
|
||||||
|
|
||||||
|
# 5. Restart postgres
|
||||||
|
docker service scale mosaic_postgres=1
|
||||||
|
|
||||||
|
# 6. Wait for postgres healthy
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# 7. Restart API
|
||||||
|
docker service scale mosaic_api=1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API
|
||||||
|
curl http://api.mosaicstack.dev/health
|
||||||
|
|
||||||
|
# Web
|
||||||
|
curl http://mosaic.mosaicstack.dev
|
||||||
|
|
||||||
|
# Authentik
|
||||||
|
curl http://auth.mosaicstack.dev/-/health/live/
|
||||||
|
|
||||||
|
# Postgres (from API container)
|
||||||
|
docker exec $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_api) \
|
||||||
|
sh -c 'nc -zv postgres 5432'
|
||||||
|
|
||||||
|
# Valkey (from API container)
|
||||||
|
docker exec $(docker ps -q -f label=com.docker.swarm.service.name=mosaic_api) \
|
||||||
|
sh -c 'nc -zv valkey 6379'
|
||||||
|
```
|
||||||
101
deploy-swarm.sh
Executable file
101
deploy-swarm.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Mosaic Stack - Docker Swarm Deployment Script
|
||||||
|
# Usage: ./deploy-swarm.sh [stack-name]
|
||||||
|
|
||||||
|
STACK_NAME="${1:-mosaic}"
|
||||||
|
COMPOSE_FILE="docker-compose.swarm.yml"
|
||||||
|
|
||||||
|
echo "🚀 Deploying Mosaic Stack to Docker Swarm..."
|
||||||
|
echo "Stack name: $STACK_NAME"
|
||||||
|
echo "Compose file: $COMPOSE_FILE"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if .env exists
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
echo "❌ Error: .env file not found"
|
||||||
|
echo "📝 Run the setup wizard to create it:"
|
||||||
|
echo " ./setup-wizard.sh"
|
||||||
|
echo ""
|
||||||
|
echo "Or copy from example:"
|
||||||
|
echo " cp .env.example .env"
|
||||||
|
echo " nano .env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
echo "🔍 Checking required environment variables..."
|
||||||
|
missing_vars=()
|
||||||
|
|
||||||
|
# Check critical variables
|
||||||
|
for var in POSTGRES_PASSWORD JWT_SECRET OIDC_CLIENT_ID OIDC_CLIENT_SECRET ENCRYPTION_KEY BETTER_AUTH_SECRET; do
|
||||||
|
if ! grep -q "^${var}=" .env || grep -q "^${var}=.*REPLACE" .env || [ -z "$(grep "^${var}=" .env | cut -d= -f2)" ]; then
|
||||||
|
missing_vars+=("$var")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check AI provider configuration
|
||||||
|
AI_PROVIDER=$(grep "^AI_PROVIDER=" .env 2>/dev/null | cut -d= -f2 || echo "ollama")
|
||||||
|
|
||||||
|
if [ "$AI_PROVIDER" = "claude" ]; then
|
||||||
|
if ! grep -q "^CLAUDE_API_KEY=" .env || grep -q "^CLAUDE_API_KEY=.*REPLACE" .env; then
|
||||||
|
missing_vars+=("CLAUDE_API_KEY (required when AI_PROVIDER=claude)")
|
||||||
|
fi
|
||||||
|
elif [ "$AI_PROVIDER" = "openai" ]; then
|
||||||
|
if ! grep -q "^OPENAI_API_KEY=" .env || grep -q "^OPENAI_API_KEY=.*REPLACE" .env; then
|
||||||
|
missing_vars+=("OPENAI_API_KEY (required when AI_PROVIDER=openai)")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing_vars[@]} -gt 0 ]; then
|
||||||
|
echo "❌ Missing or placeholder values for:"
|
||||||
|
printf " - %s\n" "${missing_vars[@]}"
|
||||||
|
echo ""
|
||||||
|
echo "Run the setup wizard to configure:"
|
||||||
|
echo " ./setup-wizard.sh"
|
||||||
|
echo ""
|
||||||
|
echo "Or manually edit .env"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ All required variables configured"
|
||||||
|
echo " AI Provider: $AI_PROVIDER"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if traefik-public network exists
|
||||||
|
if ! docker network ls --filter name=traefik-public --format '{{.Name}}' | grep -q '^traefik-public$'; then
|
||||||
|
echo "⚠️ traefik-public network not found. Creating it..."
|
||||||
|
docker network create --driver=overlay traefik-public
|
||||||
|
echo "✅ traefik-public network created"
|
||||||
|
else
|
||||||
|
echo "✅ traefik-public network already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build images (optional - uncomment if you want to build before deploying)
|
||||||
|
# echo ""
|
||||||
|
# echo "🔨 Building images..."
|
||||||
|
# docker compose -f $COMPOSE_FILE build
|
||||||
|
|
||||||
|
# Deploy the stack
|
||||||
|
echo ""
|
||||||
|
echo "📦 Deploying stack..."
|
||||||
|
docker stack deploy -c $COMPOSE_FILE --with-registry-auth $STACK_NAME
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Stack deployed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Stack status:"
|
||||||
|
docker stack ps $STACK_NAME --format "table {{.Name}}\t{{.CurrentState}}\t{{.Error}}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🔍 To check stack services:"
|
||||||
|
echo " docker stack services $STACK_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "🔍 To check stack logs:"
|
||||||
|
echo " docker service logs ${STACK_NAME}_api"
|
||||||
|
echo " docker service logs ${STACK_NAME}_web"
|
||||||
|
echo " docker service logs ${STACK_NAME}_postgres"
|
||||||
|
echo ""
|
||||||
|
echo "🗑️ To remove the stack:"
|
||||||
|
echo " docker stack rm $STACK_NAME"
|
||||||
380
docker-compose.swarm.yml
Normal file
380
docker-compose.swarm.yml
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
services:
|
||||||
|
# ======================
|
||||||
|
# PostgreSQL Database
|
||||||
|
# ======================
|
||||||
|
postgres:
|
||||||
|
build:
|
||||||
|
context: ./docker/postgres
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER:-mosaic}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB:-mosaic}
|
||||||
|
POSTGRES_SHARED_BUFFERS: ${POSTGRES_SHARED_BUFFERS:-256MB}
|
||||||
|
POSTGRES_EFFECTIVE_CACHE_SIZE: ${POSTGRES_EFFECTIVE_CACHE_SIZE:-1GB}
|
||||||
|
POSTGRES_MAX_CONNECTIONS: ${POSTGRES_MAX_CONNECTIONS:-100}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./docker/postgres/init-scripts:/docker-entrypoint-initdb.d:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Valkey Cache
|
||||||
|
# ======================
|
||||||
|
valkey:
|
||||||
|
image: valkey/valkey:8-alpine
|
||||||
|
env_file: .env
|
||||||
|
command:
|
||||||
|
- valkey-server
|
||||||
|
- --maxmemory ${VALKEY_MAXMEMORY:-256mb}
|
||||||
|
- --maxmemory-policy allkeys-lru
|
||||||
|
- --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- valkey_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "valkey-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# OpenBao Secrets Vault
|
||||||
|
# ======================
|
||||||
|
openbao:
|
||||||
|
build:
|
||||||
|
context: ./docker/openbao
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
OPENBAO_ADDR: ${OPENBAO_ADDR:-http://0.0.0.0:8200}
|
||||||
|
OPENBAO_DEV_ROOT_TOKEN_ID: ${OPENBAO_DEV_ROOT_TOKEN_ID:-root}
|
||||||
|
volumes:
|
||||||
|
- openbao_data:/openbao/data
|
||||||
|
- openbao_logs:/openbao/logs
|
||||||
|
- openbao_init:/openbao/init
|
||||||
|
cap_add:
|
||||||
|
- IPC_LOCK
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
["CMD", "wget", "--spider", "--quiet", "http://localhost:8200/v1/sys/health?standbyok=true"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Authentik PostgreSQL
|
||||||
|
# ======================
|
||||||
|
authentik-postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||||
|
POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||||
|
POSTGRES_DB: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||||
|
volumes:
|
||||||
|
- authentik_postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${AUTHENTIK_POSTGRES_USER:-authentik}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Authentik Redis
|
||||||
|
# ======================
|
||||||
|
authentik-redis:
|
||||||
|
image: valkey/valkey:8-alpine
|
||||||
|
env_file: .env
|
||||||
|
command: valkey-server --save 60 1 --loglevel warning
|
||||||
|
volumes:
|
||||||
|
- authentik_redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "valkey-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Authentik Server
|
||||||
|
# ======================
|
||||||
|
authentik-server:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.12.1
|
||||||
|
env_file: .env
|
||||||
|
command: server
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||||
|
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||||
|
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||||
|
AUTHENTIK_REDIS__PORT: 6379
|
||||||
|
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD:-admin}
|
||||||
|
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL:-admin@localhost}
|
||||||
|
AUTHENTIK_COOKIE_DOMAIN: ${AUTHENTIK_COOKIE_DOMAIN:-.mosaicstack.dev}
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_templates:/templates
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"--no-verbose",
|
||||||
|
"--tries=1",
|
||||||
|
"--spider",
|
||||||
|
"http://localhost:9000/-/health/live/",
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 90s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
- traefik-public
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mosaic-auth.rule=Host(`${MOSAIC_AUTH_DOMAIN:-auth.mosaicstack.dev}`)"
|
||||||
|
- "traefik.http.routers.mosaic-auth.entrypoints=web"
|
||||||
|
- "traefik.http.services.mosaic-auth.loadbalancer.server.port=9000"
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Authentik Worker
|
||||||
|
# ======================
|
||||||
|
authentik-worker:
|
||||||
|
image: ghcr.io/goauthentik/server:2024.12.1
|
||||||
|
env_file: .env
|
||||||
|
command: worker
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-change-this-to-a-random-secret}
|
||||||
|
AUTHENTIK_ERROR_REPORTING__ENABLED: ${AUTHENTIK_ERROR_REPORTING:-false}
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: authentik-postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__PORT: 5432
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRES_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRES_USER:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik_password}
|
||||||
|
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||||
|
AUTHENTIK_REDIS__PORT: 6379
|
||||||
|
volumes:
|
||||||
|
- authentik_media:/media
|
||||||
|
- authentik_certs:/certs
|
||||||
|
- authentik_templates:/templates
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Ollama (Optional AI Service)
|
||||||
|
# ======================
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- ollama_data:/root/.ollama
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Mosaic API
|
||||||
|
# ======================
|
||||||
|
api:
|
||||||
|
image: mosaic-stack-api:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/api/Dockerfile
|
||||||
|
args:
|
||||||
|
- NODE_ENV=production
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: ${API_PORT:-3001}
|
||||||
|
API_HOST: ${API_HOST:-0.0.0.0}
|
||||||
|
DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD:-mosaic_dev_password}@postgres:5432/${POSTGRES_DB:-mosaic}
|
||||||
|
VALKEY_URL: redis://valkey:6379
|
||||||
|
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||||
|
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
|
||||||
|
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
|
||||||
|
OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-http://localhost:3001/auth/callback}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret}
|
||||||
|
JWT_EXPIRATION: ${JWT_EXPIRATION:-24h}
|
||||||
|
OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434}
|
||||||
|
OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200}
|
||||||
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
'node -e "require(''http'').get(''http://localhost:${API_PORT:-3001}/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
- traefik-public
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)"
|
||||||
|
- "traefik.http.routers.mosaic-api.entrypoints=web"
|
||||||
|
- "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}"
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Mosaic Orchestrator
|
||||||
|
# ======================
|
||||||
|
orchestrator:
|
||||||
|
image: mosaic-stack-orchestrator:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/orchestrator/Dockerfile
|
||||||
|
env_file: .env
|
||||||
|
user: "1000:1000"
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
ORCHESTRATOR_PORT: 3001
|
||||||
|
VALKEY_URL: redis://valkey:6379
|
||||||
|
CLAUDE_API_KEY: ${CLAUDE_API_KEY}
|
||||||
|
DOCKER_SOCKET: /var/run/docker.sock
|
||||||
|
GIT_USER_NAME: "Mosaic Orchestrator"
|
||||||
|
GIT_USER_EMAIL: "orchestrator@mosaicstack.dev"
|
||||||
|
KILLSWITCH_ENABLED: true
|
||||||
|
SANDBOX_ENABLED: true
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- orchestrator_workspace:/workspace
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
cap_add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:noexec,nosuid,size=100m
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Mosaic Web
|
||||||
|
# ======================
|
||||||
|
web:
|
||||||
|
image: mosaic-stack-web:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/web/Dockerfile
|
||||||
|
args:
|
||||||
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: ${WEB_PORT:-3000}
|
||||||
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD-SHELL",
|
||||||
|
'node -e "require(''http'').get(''http://localhost:${WEB_PORT:-3000}'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"',
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
networks:
|
||||||
|
- traefik-public
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-mosaic.mosaicstack.dev}`)"
|
||||||
|
- "traefik.http.routers.mosaic-web.entrypoints=web"
|
||||||
|
- "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}"
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Volumes
|
||||||
|
# ======================
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
valkey_data:
|
||||||
|
openbao_data:
|
||||||
|
openbao_logs:
|
||||||
|
openbao_init:
|
||||||
|
authentik_postgres_data:
|
||||||
|
authentik_redis_data:
|
||||||
|
authentik_media:
|
||||||
|
authentik_certs:
|
||||||
|
authentik_templates:
|
||||||
|
ollama_data:
|
||||||
|
orchestrator_workspace:
|
||||||
|
|
||||||
|
# ======================
|
||||||
|
# Networks
|
||||||
|
# ======================
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
driver: overlay
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
221
setup-wizard.sh
Executable file
221
setup-wizard.sh
Executable file
@@ -0,0 +1,221 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo " Mosaic Stack - Interactive Setup Wizard"
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Backup existing .env if it exists
|
||||||
|
if [ -f .env ]; then
|
||||||
|
BACKUP=".env.bak.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
cp .env "$BACKUP"
|
||||||
|
echo "✓ Backed up existing .env to $BACKUP"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start with .env.example as base
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
cp .env.example .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Helper function to update or add env var
|
||||||
|
update_env() {
|
||||||
|
local key=$1
|
||||||
|
local value=$2
|
||||||
|
if grep -q "^$key=" .env; then
|
||||||
|
sed -i "s|^$key=.*|$key=$value|" .env
|
||||||
|
else
|
||||||
|
echo "$key=$value" >> .env
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Domain Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Web domain [app.mosaicstack.dev]: " WEB_DOMAIN
|
||||||
|
WEB_DOMAIN=${WEB_DOMAIN:-app.mosaicstack.dev}
|
||||||
|
update_env "MOSAIC_WEB_DOMAIN" "$WEB_DOMAIN"
|
||||||
|
|
||||||
|
read -p "API domain [api.mosaicstack.dev]: " API_DOMAIN
|
||||||
|
API_DOMAIN=${API_DOMAIN:-api.mosaicstack.dev}
|
||||||
|
update_env "MOSAIC_API_DOMAIN" "$API_DOMAIN"
|
||||||
|
|
||||||
|
# Update NEXT_PUBLIC_API_URL to use the configured domain
|
||||||
|
update_env "NEXT_PUBLIC_API_URL" "https://$API_DOMAIN"
|
||||||
|
update_env "MOSAIC_BASE_URL" "https://$WEB_DOMAIN"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Authentication Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Authentik OIDC Issuer URL: " OIDC_ISSUER
|
||||||
|
if [ -n "$OIDC_ISSUER" ]; then
|
||||||
|
update_env "OIDC_ISSUER" "$OIDC_ISSUER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "OIDC Client ID: " OIDC_CLIENT_ID
|
||||||
|
if [ -n "$OIDC_CLIENT_ID" ]; then
|
||||||
|
update_env "OIDC_CLIENT_ID" "$OIDC_CLIENT_ID"
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -sp "OIDC Client Secret: " OIDC_CLIENT_SECRET
|
||||||
|
echo ""
|
||||||
|
if [ -n "$OIDC_CLIENT_SECRET" ]; then
|
||||||
|
update_env "OIDC_CLIENT_SECRET" "$OIDC_CLIENT_SECRET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-set redirect URI
|
||||||
|
REDIRECT_URI="https://${API_DOMAIN}/auth/callback/authentik"
|
||||||
|
update_env "OIDC_REDIRECT_URI" "$REDIRECT_URI"
|
||||||
|
echo "✓ Set redirect URI to: $REDIRECT_URI"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " AI Provider Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo "Choose AI provider for orchestrator agents:"
|
||||||
|
echo " 1) Ollama (local or remote) - Recommended for self-hosted"
|
||||||
|
echo " 2) Claude API (requires API key)"
|
||||||
|
echo " 3) OpenAI API (requires API key)"
|
||||||
|
echo " 4) Skip (configure manually later)"
|
||||||
|
echo ""
|
||||||
|
read -p "Select [1]: " AI_CHOICE
|
||||||
|
AI_CHOICE=${AI_CHOICE:-1}
|
||||||
|
|
||||||
|
case $AI_CHOICE in
|
||||||
|
1)
|
||||||
|
echo ""
|
||||||
|
read -p "Ollama endpoint [http://localhost:11434]: " OLLAMA_ENDPOINT
|
||||||
|
OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-http://localhost:11434}
|
||||||
|
update_env "OLLAMA_ENDPOINT" "$OLLAMA_ENDPOINT"
|
||||||
|
update_env "OLLAMA_MODE" "remote"
|
||||||
|
|
||||||
|
read -p "Ollama model for orchestrator [llama3.1:latest]: " OLLAMA_MODEL
|
||||||
|
OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.1:latest}
|
||||||
|
update_env "OLLAMA_MODEL" "$OLLAMA_MODEL"
|
||||||
|
|
||||||
|
update_env "AI_PROVIDER" "ollama"
|
||||||
|
echo "✓ Configured Ollama at $OLLAMA_ENDPOINT"
|
||||||
|
echo " Note: Claude API key is NOT required for Ollama mode"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo ""
|
||||||
|
read -sp "Claude API Key: " CLAUDE_API_KEY
|
||||||
|
echo ""
|
||||||
|
if [ -n "$CLAUDE_API_KEY" ]; then
|
||||||
|
update_env "CLAUDE_API_KEY" "$CLAUDE_API_KEY"
|
||||||
|
update_env "AI_PROVIDER" "claude"
|
||||||
|
echo "✓ Configured Claude API"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
echo ""
|
||||||
|
read -sp "OpenAI API Key: " OPENAI_API_KEY
|
||||||
|
echo ""
|
||||||
|
if [ -n "$OPENAI_API_KEY" ]; then
|
||||||
|
update_env "OPENAI_API_KEY" "$OPENAI_API_KEY"
|
||||||
|
update_env "AI_PROVIDER" "openai"
|
||||||
|
echo "✓ Configured OpenAI API"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
echo "✓ Skipping AI provider configuration"
|
||||||
|
update_env "AI_PROVIDER" "ollama"
|
||||||
|
echo " Defaulting to Ollama (configure OLLAMA_ENDPOINT in .env)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Security Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Generate secrets if not present or contain REPLACE
|
||||||
|
echo "Generating security secrets..."
|
||||||
|
|
||||||
|
if ! grep -q "^JWT_SECRET=" .env || grep -q "REPLACE" .env; then
|
||||||
|
JWT_SECRET=$(openssl rand -base64 32)
|
||||||
|
update_env "JWT_SECRET" "$JWT_SECRET"
|
||||||
|
echo "✓ Generated JWT_SECRET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^ENCRYPTION_KEY=" .env || grep -q "REPLACE" .env; then
|
||||||
|
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||||
|
update_env "ENCRYPTION_KEY" "$ENCRYPTION_KEY"
|
||||||
|
echo "✓ Generated ENCRYPTION_KEY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "^BETTER_AUTH_SECRET=" .env || grep -q "REPLACE" .env; then
|
||||||
|
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
||||||
|
update_env "BETTER_AUTH_SECRET" "$BETTER_AUTH_SECRET"
|
||||||
|
echo "✓ Generated BETTER_AUTH_SECRET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Database Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if ! grep -q "^POSTGRES_PASSWORD=" .env || grep -q "REPLACE" .env; then
|
||||||
|
echo "Generate new PostgreSQL password? (recommended for new installations)"
|
||||||
|
read -p "[y/N]: " GEN_DB_PASS
|
||||||
|
if [[ $GEN_DB_PASS =~ ^[Yy]$ ]]; then
|
||||||
|
POSTGRES_PASSWORD=$(openssl rand -base64 24)
|
||||||
|
update_env "POSTGRES_PASSWORD" "$POSTGRES_PASSWORD"
|
||||||
|
# Update DATABASE_URL
|
||||||
|
update_env "DATABASE_URL" "postgresql://mosaic:${POSTGRES_PASSWORD}@postgres:5432/mosaic"
|
||||||
|
echo "✓ Generated PostgreSQL password"
|
||||||
|
else
|
||||||
|
echo "✓ Keeping existing PostgreSQL password"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "✓ Using existing PostgreSQL password"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Traefik Configuration"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Use external Traefik instance? [Y/n]: "
|
||||||
|
read -p "" USE_EXTERNAL_TRAEFIK
|
||||||
|
USE_EXTERNAL_TRAEFIK=${USE_EXTERNAL_TRAEFIK:-Y}
|
||||||
|
|
||||||
|
if [[ $USE_EXTERNAL_TRAEFIK =~ ^[Yy]$ ]]; then
|
||||||
|
update_env "TRAEFIK_MODE" "upstream"
|
||||||
|
update_env "TRAEFIK_NETWORK" "traefik-public"
|
||||||
|
echo "✓ Configured for upstream Traefik"
|
||||||
|
else
|
||||||
|
update_env "TRAEFIK_MODE" "bundled"
|
||||||
|
echo "✓ Configured for bundled Traefik"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo " Configuration Complete!"
|
||||||
|
echo "═══════════════════════════════════════════════════════"
|
||||||
|
echo ""
|
||||||
|
echo "Configuration saved to .env"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review .env file: nano .env"
|
||||||
|
echo " 2. Deploy stack:"
|
||||||
|
if [ -f deploy-swarm.sh ]; then
|
||||||
|
echo " ./deploy-swarm.sh mosaic"
|
||||||
|
else
|
||||||
|
echo " docker compose up -d"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "Services will be available at:"
|
||||||
|
echo " Web: https://$WEB_DOMAIN"
|
||||||
|
echo " API: https://$API_DOMAIN"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user