diff --git a/.env.example b/.env.example index 396d74e..8615344 100644 --- a/.env.example +++ b/.env.example @@ -19,7 +19,7 @@ NEXT_PUBLIC_API_URL=http://localhost:3001 # ====================== # PostgreSQL Database # ====================== -# Bundled PostgreSQL (when database profile enabled) +# Bundled PostgreSQL # SECURITY: Change POSTGRES_PASSWORD to a strong random password in production DATABASE_URL=postgresql://mosaic:REPLACE_WITH_SECURE_PASSWORD@postgres:5432/mosaic POSTGRES_USER=mosaic @@ -28,7 +28,7 @@ POSTGRES_DB=mosaic POSTGRES_PORT=5432 # External PostgreSQL (managed service) -# Disable 'database' profile and point DATABASE_URL to your external instance +# To use an external instance, update DATABASE_URL above # Example: DATABASE_URL=postgresql://user:pass@rds.amazonaws.com:5432/mosaic # PostgreSQL Performance Tuning (Optional) @@ -39,7 +39,7 @@ POSTGRES_MAX_CONNECTIONS=100 # ====================== # Valkey Cache (Redis-compatible) # ====================== -# Bundled Valkey (when cache profile enabled) +# Bundled Valkey VALKEY_URL=redis://valkey:6379 VALKEY_HOST=valkey VALKEY_PORT=6379 @@ -47,7 +47,7 @@ VALKEY_PORT=6379 VALKEY_MAXMEMORY=256mb # External Redis/Valkey (managed service) -# Disable 'cache' profile and point VALKEY_URL to your external instance +# To use an external instance, update VALKEY_URL above # Example: VALKEY_URL=redis://elasticache.amazonaws.com:6379 # Example with auth: VALKEY_URL=redis://:password@redis.example.com:6379 @@ -244,12 +244,16 @@ MOSAIC_API_DOMAIN=api.mosaic.local MOSAIC_WEB_DOMAIN=mosaic.local MOSAIC_AUTH_DOMAIN=auth.mosaic.local -# External Traefik network name (for upstream mode) +# External Traefik network name (for upstream mode and swarm) # Must match the network name of your existing Traefik instance TRAEFIK_NETWORK=traefik-public +TRAEFIK_DOCKER_NETWORK=traefik-public # TLS/SSL Configuration TRAEFIK_TLS_ENABLED=true +TRAEFIK_ENTRYPOINT=websecure +# Cert resolver name (leave empty if TLS is handled externally or using self-signed certs) +TRAEFIK_CERTRESOLVER= # For Let's Encrypt (production): TRAEFIK_ACME_EMAIL=admin@example.com # For self-signed certificates (development), leave TRAEFIK_ACME_EMAIL empty @@ -285,6 +289,15 @@ GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET # The coordinator service uses this key to authenticate with the API COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS +# Anthropic API Key (used by coordinator for issue parsing) +# Get your API key from: https://console.anthropic.com/ +ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY + +# Coordinator tuning +COORDINATOR_POLL_INTERVAL=5.0 +COORDINATOR_MAX_CONCURRENT_AGENTS=10 +COORDINATOR_ENABLED=true + # ====================== # Rate Limiting # ====================== @@ -329,16 +342,34 @@ RATE_LIMIT_STORAGE=redis # ====================== # Matrix bot integration for chat-based control via Matrix protocol # Requires a Matrix account with an access token for the bot user -# MATRIX_HOMESERVER_URL=https://matrix.example.com -# MATRIX_ACCESS_TOKEN= -# MATRIX_BOT_USER_ID=@mosaic-bot:example.com -# MATRIX_CONTROL_ROOM_ID=!roomid:example.com -# MATRIX_WORKSPACE_ID=your-workspace-uuid +# Set these AFTER deploying Synapse and creating the bot account. # # SECURITY: MATRIX_WORKSPACE_ID must be a valid workspace UUID from your database. # All Matrix commands will execute within this workspace context for proper # multi-tenant isolation. Each Matrix bot instance should be configured for # a single workspace. +MATRIX_HOMESERVER_URL=http://synapse:8008 +MATRIX_ACCESS_TOKEN= +MATRIX_BOT_USER_ID=@mosaic-bot:matrix.example.com +MATRIX_SERVER_NAME=matrix.example.com +# MATRIX_CONTROL_ROOM_ID=!roomid:matrix.example.com +# MATRIX_WORKSPACE_ID=your-workspace-uuid + +# ====================== +# Matrix / Synapse Deployment +# ====================== +# Domains for Traefik routing to Matrix services +MATRIX_DOMAIN=matrix.example.com +ELEMENT_DOMAIN=chat.example.com + +# Synapse database (created automatically by synapse-db-init in the swarm compose) +SYNAPSE_POSTGRES_DB=synapse +SYNAPSE_POSTGRES_USER=synapse +SYNAPSE_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_SYNAPSE_DB_PASSWORD + +# Image tags for Matrix services +SYNAPSE_IMAGE_TAG=latest +ELEMENT_IMAGE_TAG=latest # ====================== # Orchestrator Configuration @@ -363,11 +394,11 @@ AI_PROVIDER=ollama # 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 +# Claude API Key +# Required by the orchestrator service in swarm deployment. +# Also used when AI_PROVIDER=claude for other services. # 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-... +CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY # OpenAI API Configuration (when AI_PROVIDER=openai) # OPTIONAL: Only required if AI_PROVIDER=openai @@ -405,6 +436,9 @@ TTS_PREMIUM_URL=http://chatterbox-tts:8881/v1 TTS_FALLBACK_ENABLED=false TTS_FALLBACK_URL=http://openedai-speech:8000/v1 +# Whisper model for Speaches STT engine +SPEACHES_WHISPER_MODEL=Systran/faster-whisper-large-v3-turbo + # Speech Service Limits # Maximum upload file size in bytes (default: 25MB) SPEECH_MAX_UPLOAD_SIZE=25000000 @@ -439,28 +473,6 @@ MOSAIC_TELEMETRY_INSTANCE_ID=your-instance-uuid-here # Useful for development and debugging telemetry payloads MOSAIC_TELEMETRY_DRY_RUN=false -# ====================== -# Matrix Dev Environment (docker-compose.matrix.yml overlay) -# ====================== -# These variables configure the local Matrix dev environment. -# Only used when running: docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -# -# Synapse homeserver -# SYNAPSE_CLIENT_PORT=8008 -# SYNAPSE_FEDERATION_PORT=8448 -# SYNAPSE_POSTGRES_DB=synapse -# SYNAPSE_POSTGRES_USER=synapse -# SYNAPSE_POSTGRES_PASSWORD=synapse_dev_password -# -# Element Web client -# ELEMENT_PORT=8501 -# -# Matrix bridge connection (set after running docker/matrix/scripts/setup-bot.sh) -# MATRIX_HOMESERVER_URL=http://localhost:8008 -# MATRIX_ACCESS_TOKEN= -# MATRIX_BOT_USER_ID=@mosaic-bot:localhost -# MATRIX_SERVER_NAME=localhost - # ====================== # Logging & Debugging # ====================== diff --git a/.env.prod.example b/.env.prod.example deleted file mode 100644 index 1b21644..0000000 --- a/.env.prod.example +++ /dev/null @@ -1,66 +0,0 @@ -# ============================================== -# Mosaic Stack Production Environment -# ============================================== -# Copy to .env and configure for production deployment - -# ====================== -# PostgreSQL Database -# ====================== -# CRITICAL: Use a strong, unique password -POSTGRES_USER=mosaic -POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD -POSTGRES_DB=mosaic -POSTGRES_SHARED_BUFFERS=256MB -POSTGRES_EFFECTIVE_CACHE_SIZE=1GB -POSTGRES_MAX_CONNECTIONS=100 - -# ====================== -# Valkey Cache -# ====================== -VALKEY_MAXMEMORY=256mb - -# ====================== -# API Configuration -# ====================== -API_PORT=3001 -API_HOST=0.0.0.0 - -# ====================== -# Web Configuration -# ====================== -WEB_PORT=3000 -NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev - -# ====================== -# Authentication (Authentik OIDC) -# ====================== -OIDC_ISSUER=https://auth.diversecanvas.com/application/o/mosaic-stack/ -OIDC_CLIENT_ID=your-client-id -OIDC_CLIENT_SECRET=your-client-secret -OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik - -# ====================== -# JWT Configuration -# ====================== -# CRITICAL: Generate a random secret (openssl rand -base64 32) -JWT_SECRET=REPLACE_WITH_RANDOM_SECRET -JWT_EXPIRATION=24h - -# ====================== -# Traefik Integration -# ====================== -# Set to true if using external Traefik -TRAEFIK_ENABLE=true -TRAEFIK_ENTRYPOINT=websecure -TRAEFIK_TLS_ENABLED=true -TRAEFIK_DOCKER_NETWORK=traefik-public -TRAEFIK_CERTRESOLVER=letsencrypt - -# Domain configuration -MOSAIC_API_DOMAIN=api.mosaicstack.dev -MOSAIC_WEB_DOMAIN=app.mosaicstack.dev - -# ====================== -# Optional: Ollama -# ====================== -# OLLAMA_ENDPOINT=http://ollama.diversecanvas.com:11434 diff --git a/.env.swarm.example b/.env.swarm.example deleted file mode 100644 index efa9d8a..0000000 --- a/.env.swarm.example +++ /dev/null @@ -1,161 +0,0 @@ -# ============================================== -# 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) -# ====================== -# NOTE: Authentik services are COMMENTED OUT in docker-compose.swarm.yml by default -# Uncomment those services if you want to run Authentik internally -# Otherwise, use external Authentik by configuring OIDC_* variables below - -# External Authentik Configuration (default) -OIDC_ENABLED=true -OIDC_ISSUER=https://auth.example.com/application/o/mosaic-stack/ -OIDC_CLIENT_ID=your-client-id-here -OIDC_CLIENT_SECRET=your-client-secret-here -OIDC_REDIRECT_URI=https://api.mosaicstack.dev/auth/callback/authentik - -# Internal Authentik Configuration (only needed if uncommenting Authentik services) -# Authentik PostgreSQL Database -AUTHENTIK_POSTGRES_USER=authentik -AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD -AUTHENTIK_POSTGRES_DB=authentik - -# Authentik Server 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 - -# ====================== -# Coordinator Service -# ====================== -ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY -COORDINATOR_POLL_INTERVAL=5.0 -COORDINATOR_MAX_CONCURRENT_AGENTS=10 -COORDINATOR_ENABLED=true - -# ====================== -# 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 diff --git a/.woodpecker/api.yml b/.woodpecker/api.yml index 9918e32..18d15b2 100644 --- a/.woodpecker/api.yml +++ b/.woodpecker/api.yml @@ -112,7 +112,7 @@ steps: ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" commands: - *use_deps - - pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' + - pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' --exclude 'src/mosaic-telemetry/mosaic-telemetry.module.spec.ts' depends_on: - prisma-migrate @@ -154,7 +154,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:dev" fi - /kaniko/executor --context . --dockerfile apps/api/Dockerfile $DESTINATIONS + /kaniko/executor --context . --dockerfile apps/api/Dockerfile --snapshot-mode=redo $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] diff --git a/.woodpecker/coordinator.yml b/.woodpecker/coordinator.yml index 1af4c5f..fa1aa8d 100644 --- a/.woodpecker/coordinator.yml +++ b/.woodpecker/coordinator.yml @@ -95,7 +95,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-coordinator:dev" fi - /kaniko/executor --context apps/coordinator --dockerfile apps/coordinator/Dockerfile $DESTINATIONS + /kaniko/executor --context apps/coordinator --dockerfile apps/coordinator/Dockerfile --snapshot-mode=redo $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] diff --git a/.woodpecker/infra.yml b/.woodpecker/infra.yml index 230bfbc..881fb83 100644 --- a/.woodpecker/infra.yml +++ b/.woodpecker/infra.yml @@ -39,7 +39,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-postgres:dev" fi - /kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile $DESTINATIONS + /kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile --snapshot-mode=redo $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] @@ -64,7 +64,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-openbao:dev" fi - /kaniko/executor --context docker/openbao --dockerfile docker/openbao/Dockerfile $DESTINATIONS + /kaniko/executor --context docker/openbao --dockerfile docker/openbao/Dockerfile --snapshot-mode=redo $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] diff --git a/.woodpecker/orchestrator.yml b/.woodpecker/orchestrator.yml index 0640c7b..a3b661d 100644 --- a/.woodpecker/orchestrator.yml +++ b/.woodpecker/orchestrator.yml @@ -111,7 +111,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:dev" fi - /kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile $DESTINATIONS + /kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile --snapshot-mode=redo $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] diff --git a/.woodpecker/web.yml b/.woodpecker/web.yml index e2f51c3..5345b1f 100644 --- a/.woodpecker/web.yml +++ b/.woodpecker/web.yml @@ -122,7 +122,7 @@ steps: elif [ "$CI_COMMIT_BRANCH" = "develop" ]; then DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:dev" fi - /kaniko/executor --context . --dockerfile apps/web/Dockerfile --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS + /kaniko/executor --context . --dockerfile apps/web/Dockerfile --snapshot-mode=redo --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS when: - branch: [main, develop] event: [push, manual, tag] diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index b4ae23d..cdd1d81 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,6 +1,3 @@ -# syntax=docker/dockerfile:1 -# Enable BuildKit features for cache mounts - # Base image for all stages # Uses Debian slim (glibc) instead of Alpine (musl) because native Node.js addons # (matrix-sdk-crypto-nodejs, Prisma engines) require glibc-compatible binaries. @@ -27,9 +24,8 @@ COPY packages/ui/package.json ./packages/ui/ COPY packages/config/package.json ./packages/config/ COPY apps/api/package.json ./apps/api/ -# Install dependencies with pnpm store cache -RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ - pnpm install --frozen-lockfile +# Install dependencies (no cache mount — Kaniko builds are ephemeral in CI) +RUN pnpm install --frozen-lockfile # ====================== # Builder stage @@ -57,15 +53,14 @@ RUN pnpm turbo build --filter=@mosaic/api --force # ====================== FROM node:24-slim AS production -# Remove npm (unused in production — we use pnpm) to reduce attack surface -RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx +# Install dumb-init for proper signal handling (static binary from GitHub, +# avoids apt-get which fails under Kaniko with bookworm GPG signature errors) +ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init -# Install dumb-init for proper signal handling -RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \ - && rm -rf /var/lib/apt/lists/* - -# Create non-root user -RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs +# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot) +RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \ + && chmod 755 /usr/local/bin/dumb-init \ + && groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs WORKDIR /app diff --git a/apps/api/src/auth/auth.config.ts b/apps/api/src/auth/auth.config.ts index afaf19e..d668eb8 100644 --- a/apps/api/src/auth/auth.config.ts +++ b/apps/api/src/auth/auth.config.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { genericOAuth } from "better-auth/plugins"; @@ -216,6 +217,7 @@ export function createAuth(prisma: PrismaClient) { updateAge: 60 * 60 * 2, // 2 hours — minimum session age before BetterAuth refreshes the expiry on next request }, advanced: { + generateId: () => randomUUID(), defaultCookieAttributes: { httpOnly: true, secure: process.env.NODE_ENV === "production", diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 19d7150..647f5bd 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -49,8 +49,10 @@ async function bootstrap() { // Configure CORS for cookie-based authentication // Origin list is shared with BetterAuth trustedOrigins via getTrustedOrigins() + const trustedOrigins = getTrustedOrigins(); + console.log(`[CORS] Trusted origins: ${JSON.stringify(trustedOrigins)}`); app.enableCors({ - origin: getTrustedOrigins(), + origin: trustedOrigins, credentials: true, // Required for cookie-based authentication methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "Cookie", "X-CSRF-Token", "X-Workspace-Id"], diff --git a/apps/coordinator/Dockerfile b/apps/coordinator/Dockerfile index 04d85a2..c20f1b1 100644 --- a/apps/coordinator/Dockerfile +++ b/apps/coordinator/Dockerfile @@ -1,14 +1,10 @@ # Multi-stage build for mosaic-coordinator -FROM python:3.11-slim AS builder +# Builder uses the full Python image which already includes gcc/g++/make, +# avoiding a 336 MB build-essential install that exceeds Kaniko disk budget. +FROM python:3.11 AS builder WORKDIR /app -# Install build dependencies -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - build-essential \ - && rm -rf /var/lib/apt/lists/* - # Copy dependency files and private registry config COPY pyproject.toml . COPY pip.conf /etc/pip.conf diff --git a/apps/orchestrator/Dockerfile b/apps/orchestrator/Dockerfile index 1ec9d4e..4d0a979 100644 --- a/apps/orchestrator/Dockerfile +++ b/apps/orchestrator/Dockerfile @@ -1,6 +1,3 @@ -# syntax=docker/dockerfile:1 -# Enable BuildKit features for cache mounts - # Base image for all stages # Uses Debian slim (glibc) instead of Alpine (musl) for native addon compatibility. FROM node:24-slim AS base @@ -26,9 +23,8 @@ COPY packages/config/package.json ./packages/config/ COPY apps/orchestrator/package.json ./apps/orchestrator/ # Install ALL dependencies (not just production) -# This ensures NestJS packages and other required deps are available -RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ - pnpm install --frozen-lockfile +# No cache mount — Kaniko builds are ephemeral in CI +RUN pnpm install --frozen-lockfile # ====================== # Builder stage @@ -69,15 +65,14 @@ LABEL org.opencontainers.image.vendor="Mosaic Stack" LABEL org.opencontainers.image.title="Mosaic Orchestrator" LABEL org.opencontainers.image.description="Agent orchestration service for Mosaic Stack" -# Remove npm (unused in production — we use pnpm) to reduce attack surface -RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx +# Install dumb-init for proper signal handling (static binary from GitHub, +# avoids apt-get which fails under Kaniko with bookworm GPG signature errors) +ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init -# Install wget and dumb-init -RUN apt-get update && apt-get install -y --no-install-recommends wget dumb-init \ - && rm -rf /var/lib/apt/lists/* - -# Create non-root user -RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs +# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot) +RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \ + && chmod 755 /usr/local/bin/dumb-init \ + && groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs WORKDIR /app @@ -105,7 +100,7 @@ EXPOSE 3001 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1 + CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 7caec12..06a3299 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,6 +1,3 @@ -# syntax=docker/dockerfile:1 -# Enable BuildKit features for cache mounts - # Base image for all stages # Uses Debian slim (glibc) for consistency with API/orchestrator and to prevent # future native addon compatibility issues with Alpine's musl libc. @@ -27,9 +24,8 @@ COPY packages/ui/package.json ./packages/ui/ COPY packages/config/package.json ./packages/config/ COPY apps/web/package.json ./apps/web/ -# Install dependencies with pnpm store cache -RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ - pnpm install --frozen-lockfile +# Install dependencies (no cache mount — Kaniko builds are ephemeral in CI) +RUN pnpm install --frozen-lockfile # ====================== # Builder stage @@ -79,18 +75,15 @@ RUN mkdir -p ./apps/web/public # ====================== FROM node:24-slim AS production -# Remove npm (unused in production — we use pnpm) to reduce attack surface -RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx +# Install dumb-init for proper signal handling (static binary from GitHub, +# avoids apt-get which fails under Kaniko with bookworm GPG signature errors) +ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init -# Install pnpm (needed for pnpm start command) -RUN corepack enable && corepack prepare pnpm@10.27.0 --activate - -# Install dumb-init for proper signal handling -RUN apt-get update && apt-get install -y --no-install-recommends dumb-init \ - && rm -rf /var/lib/apt/lists/* - -# Create non-root user -RUN groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nextjs +# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot) +RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \ + && corepack enable && corepack prepare pnpm@10.27.0 --activate \ + && chmod 755 /usr/local/bin/dumb-init \ + && groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nextjs WORKDIR /app diff --git a/docker-compose.portainer.yml b/docker-compose.portainer.yml deleted file mode 100644 index 54430b4..0000000 --- a/docker-compose.portainer.yml +++ /dev/null @@ -1,95 +0,0 @@ -# ============================================== -# OpenBao Standalone Deployment - Portainer Version -# ============================================== -# -# This file is optimized for Portainer deployment: -# - No env_file directive (define variables in Portainer's environment editor) -# - Port exposed on all interfaces (Portainer limitation) -# - All environment variables explicitly defined -# -# Usage in Portainer: -# 1. Stacks -> Add Stack -# 2. Name: mosaic-openbao -# 3. Paste this file content -# 4. Add environment variables in "Environment variables" section: -# - IMAGE_TAG=dev -# - OPENBAO_PORT=8200 -# 5. Deploy -# -# SECURITY NOTE: Port 8200 will be exposed on 0.0.0.0 (all interfaces) -# Use firewall rules to restrict access if needed. -# ============================================== - -services: - # ====================== - # OpenBao Secrets Vault - # ====================== - openbao: - image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev} - container_name: mosaic-openbao - entrypoint: ["dumb-init", "--"] - command: ["bao", "server", "-config=/openbao/config/config.hcl"] - environment: - OPENBAO_ADDR: http://0.0.0.0:8200 - ports: - - "${OPENBAO_PORT:-8200}:8200" - volumes: - - openbao_data:/openbao/data - - openbao_logs:/openbao/logs - - openbao_init:/openbao/init - cap_add: - - IPC_LOCK - healthcheck: - test: - [ - "CMD-SHELL", - "wget --spider --quiet 'http://localhost:8200/v1/sys/health?standbyok=true&uninitcode=200&sealedcode=200'", - ] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - restart: unless-stopped - networks: - - mosaic_internal - - # ====================== - # OpenBao Init Sidecar - # ====================== - # Auto-initializes and unseals OpenBao on first run - openbao-init: - image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-dev} - container_name: mosaic-openbao-init - command: /openbao/init.sh - environment: - OPENBAO_ADDR: http://openbao:8200 - volumes: - - openbao_init:/openbao/init - depends_on: - - openbao - restart: "no" - networks: - - mosaic_internal - -# ====================== -# Volumes -# ====================== -volumes: - openbao_data: - name: mosaic-openbao-data - driver: local - openbao_logs: - name: mosaic-openbao-logs - driver: local - openbao_init: - name: mosaic-openbao-init - driver: local - -# ====================== -# Networks -# ====================== -# Connect to the swarm stack's internal network -networks: - mosaic_internal: - external: true - name: mosaic_internal diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index d248237..0000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,180 +0,0 @@ -# Production Docker Compose - Uses pre-built images from Gitea Packages -# -# Prerequisites: -# - Images built and pushed to git.mosaicstack.dev/mosaic/* -# - .env file configured with production values -# -# Usage: -# docker compose -f docker-compose.prod.yml up -d -# -# For Portainer: -# - Stack → Add Stack → Repository -# - Compose file: docker-compose.prod.yml - -services: - # ====================== - # PostgreSQL Database - # ====================== - postgres: - image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest} - container_name: mosaic-postgres - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-mosaic} - POSTGRES_PASSWORD: ${POSTGRES_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 - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=database" - - "com.mosaic.description=PostgreSQL 17 with pgvector" - - # ====================== - # Valkey Cache - # ====================== - valkey: - image: valkey/valkey:8-alpine - container_name: mosaic-valkey - restart: unless-stopped - command: - - valkey-server - - --maxmemory ${VALKEY_MAXMEMORY:-256mb} - - --maxmemory-policy noeviction - - --appendonly yes - volumes: - - valkey_data:/data - healthcheck: - test: ["CMD", "valkey-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 10s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=cache" - - "com.mosaic.description=Valkey Redis-compatible cache" - - # ====================== - # Mosaic API - # ====================== - api: - image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} - container_name: mosaic-api - restart: unless-stopped - environment: - NODE_ENV: production - PORT: ${API_PORT:-3001} - API_HOST: ${API_HOST:-0.0.0.0} - DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_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} - JWT_SECRET: ${JWT_SECRET} - JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} - OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} - ports: - - "${API_PORT:-3001}:${API_PORT:-3001}" - depends_on: - postgres: - condition: service_healthy - valkey: - condition: service_healthy - 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: - - mosaic-internal - - mosaic-public - labels: - - "com.mosaic.service=api" - - "com.mosaic.description=Mosaic NestJS API" - - "traefik.enable=${TRAEFIK_ENABLE:-false}" - - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)" - - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - # ====================== - # Mosaic Web - # ====================== - web: - image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} - container_name: mosaic-web - restart: unless-stopped - environment: - NODE_ENV: production - PORT: ${WEB_PORT:-3000} - NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.mosaicstack.dev} - ports: - - "${WEB_PORT:-3000}:${WEB_PORT:-3000}" - depends_on: - api: - condition: service_healthy - 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: - - mosaic-public - labels: - - "com.mosaic.service=web" - - "com.mosaic.description=Mosaic Next.js Web App" - - "traefik.enable=${TRAEFIK_ENABLE:-false}" - - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-app.mosaicstack.dev}`)" - - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - -# ====================== -# Volumes -# ====================== -volumes: - postgres_data: - name: mosaic-postgres-data - driver: local - valkey_data: - name: mosaic-valkey-data - driver: local - -# ====================== -# Networks -# ====================== -networks: - mosaic-internal: - name: mosaic-internal - driver: bridge - mosaic-public: - name: mosaic-public - driver: bridge diff --git a/docker-compose.speech.yml b/docker-compose.speech.yml deleted file mode 100644 index 855a947..0000000 --- a/docker-compose.speech.yml +++ /dev/null @@ -1,113 +0,0 @@ -# ============================================== -# Speech Services - Docker Compose Dev Overlay -# ============================================== -# -# Adds STT and TTS services for local development. -# -# Usage: -# Basic (STT + default TTS): -# docker compose -f docker-compose.yml -f docker-compose.speech.yml up -d -# -# With premium TTS (requires GPU): -# docker compose -f docker-compose.yml -f docker-compose.speech.yml --profile premium-tts up -d -# -# Or use Makefile targets: -# make speech-up # Basic speech services -# make speech-down # Stop speech services -# make speech-logs # View speech service logs -# ============================================== - -services: - # ====================== - # Speaches (STT + basic TTS) - # ====================== - speaches: - image: ghcr.io/speaches-ai/speaches:latest - container_name: mosaic-speaches - restart: unless-stopped - environment: - WHISPER__MODEL: ${SPEACHES_WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo} - ports: - - "${SPEACHES_PORT:-8090}:8000" - volumes: - - speaches_models:/root/.cache/huggingface - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=speech-stt" - - "com.mosaic.description=Speaches STT (Whisper) and basic TTS" - - # ====================== - # Kokoro TTS (Default TTS) - # ====================== - kokoro-tts: - image: ghcr.io/remsky/kokoro-fastapi:latest-cpu - container_name: mosaic-kokoro-tts - restart: unless-stopped - ports: - - "${KOKORO_TTS_PORT:-8880}:8880" - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=speech-tts" - - "com.mosaic.description=Kokoro FastAPI TTS engine" - - # ====================== - # Chatterbox TTS (Premium TTS - Optional) - # ====================== - # Only starts with: --profile premium-tts - # Requires NVIDIA GPU with docker nvidia runtime - chatterbox-tts: - image: devnen/chatterbox-tts-server:latest - container_name: mosaic-chatterbox-tts - restart: unless-stopped - ports: - - "${CHATTERBOX_TTS_PORT:-8881}:8000" - profiles: - - premium-tts - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: [gpu] - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 180s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=speech-tts-premium" - - "com.mosaic.description=Chatterbox premium TTS with voice cloning (GPU)" - -# ====================== -# Volumes -# ====================== -volumes: - speaches_models: - name: mosaic-speaches-models - driver: local - -# ====================== -# Networks -# ====================== -networks: - mosaic-internal: - external: true - name: mosaic-internal diff --git a/docker-compose.swarm.portainer.yml b/docker-compose.swarm.portainer.yml index a544963..559886c 100644 --- a/docker-compose.swarm.portainer.yml +++ b/docker-compose.swarm.portainer.yml @@ -1,48 +1,63 @@ # ============================================== -# Mosaic Stack - Docker Swarm Deployment +# Mosaic Stack — Docker Swarm / Portainer # ============================================== # -# IMPORTANT: Docker Swarm does NOT support docker-compose profiles -# To disable services (e.g., for external alternatives), manually comment them out +# The canonical deployment file for Mosaic Stack on Docker Swarm. +# Includes all services except OpenBao (standalone) and external services. # -# Current Configuration: -# - PostgreSQL: ENABLED (internal) -# - Valkey: ENABLED (internal) -# - Coordinator: ENABLED (internal) -# - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml) -# - Authentik: DISABLED (commented out - using external OIDC) -# - Ollama: DISABLED (commented out - using external Ollama) +# External services (not in this file): +# - OpenBao: Standalone container (see docker-compose.openbao.yml) +# - Authentik: External OIDC provider +# - Ollama: External AI inference # -# For detailed deployment instructions, see: -# docs/SWARM-DEPLOYMENT.md +# Usage (Portainer): +# 1. Stacks -> Add Stack -> Upload or paste +# 2. Set environment variables (see .env.example for full reference) +# 3. Deploy # -# Quick Start: -# 1. cp .env.swarm.example .env -# 2. nano .env # Configure environment -# 3. ./scripts/deploy-swarm.sh mosaic -# 4. Initialize OpenBao manually (see docs/SWARM-DEPLOYMENT.md) +# Usage (CLI): +# docker stack deploy -c docker-compose.swarm.portainer.yml mosaic +# +# Host paths required for Matrix: +# /opt/mosaic/synapse/homeserver.yaml +# /opt/mosaic/synapse/element-config.json +# /opt/mosaic/synapse/media_store/ (auto-populated) +# /opt/mosaic/synapse/keys/ (auto-populated) +# +# ============================================== +# ENVIRONMENT VARIABLE CONVENTION +# ============================================== +# +# ${VAR} — REQUIRED. Must be set in Portainer env vars. +# ${VAR:-default} — OPTIONAL. Falls back to a sensible default. +# ${VAR:-} — OPTIONAL. Empty string is acceptable. +# +# NOTE: Portainer does not support ${VAR:?msg} syntax. +# Required vars use plain ${VAR} — the app validates at startup. # # ============================================== services: + # ============================================ + # CORE INFRASTRUCTURE + # ============================================ + # ====================== # PostgreSQL Database # ====================== postgres: image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest} environment: - POSTGRES_USER: ${POSTGRES_USER:-mosaic} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password} - POSTGRES_DB: ${POSTGRES_DB:-mosaic} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} 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 - # Note: init-scripts bind mount removed for Portainer compatibility - # Init scripts are baked into the postgres image at build time healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 @@ -77,168 +92,113 @@ services: restart_policy: condition: on-failure - # ====================== - # OpenBao Secrets Vault - COMMENTED OUT - # ====================== - # IMPORTANT: OpenBao CANNOT run in swarm mode due to port binding conflicts. - # Deploy OpenBao as a standalone container instead: - # docker compose -f docker-compose.openbao.yml up -d - # - # Alternative: Use external HashiCorp Vault or managed secrets service - # - # openbao: - # image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest} - # 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 + # ============================================ + # MOSAIC APPLICATION + # ============================================ # ====================== - # Authentik - COMMENTED OUT (Using External Authentik) + # Mosaic API # ====================== - # Uncomment these services if you want to run Authentik internally - # For external Authentik, configure OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET in .env - # - # authentik-postgres: - # image: postgres:17.7-alpine3.22 - # 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: - # image: valkey/valkey:8-alpine - # 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: - # image: ghcr.io/goauthentik/server:2024.12.1 - # 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: - # image: ghcr.io/goauthentik/server:2024.12.1 - # 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 + api: + image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} + environment: + NODE_ENV: production + PORT: ${API_PORT:-3001} + API_HOST: ${API_HOST:-0.0.0.0} + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} + VALKEY_URL: redis://valkey:6379 + # Auth (external Authentik) + OIDC_ENABLED: ${OIDC_ENABLED:-false} + OIDC_ISSUER: ${OIDC_ISSUER} + OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} + OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} + OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-} + JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret} + JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + CSRF_SECRET: ${CSRF_SECRET} + # External services + OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT} + OPENBAO_ADDR: ${OPENBAO_ADDR} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + # Matrix bridge (optional — configure after Synapse is running) + MATRIX_HOMESERVER_URL: ${MATRIX_HOMESERVER_URL:-http://synapse:8008} + MATRIX_ACCESS_TOKEN: ${MATRIX_ACCESS_TOKEN:-} + MATRIX_BOT_USER_ID: ${MATRIX_BOT_USER_ID:-} + MATRIX_CONTROL_ROOM_ID: ${MATRIX_CONTROL_ROOM_ID:-} + MATRIX_WORKSPACE_ID: ${MATRIX_WORKSPACE_ID:-} + MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-} + # Speech + SPEECH_MAX_UPLOAD_SIZE: ${SPEECH_MAX_UPLOAD_SIZE:-25000000} + SPEECH_MAX_DURATION_SECONDS: ${SPEECH_MAX_DURATION_SECONDS:-600} + SPEECH_MAX_TEXT_LENGTH: ${SPEECH_MAX_TEXT_LENGTH:-4096} + # Telemetry (disabled by default) + MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false} + MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-} + MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} + MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} + MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} + # Frontend URLs (for CORS and auth redirects) + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL} + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} + TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-} + 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}`)" + - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}" + - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" + - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}" + - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" # ====================== - # Ollama (Optional AI Service) + # Mosaic Web # ====================== - # ollama: - # image: ollama/ollama:latest - # 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 + web: + image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} + environment: + NODE_ENV: production + PORT: ${WEB_PORT:-3000} + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL} + 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}`)" + - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}" + - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" + - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}" + - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" # ====================== # Mosaic Coordinator @@ -247,7 +207,7 @@ services: image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest} environment: GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET} - GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev} + GITEA_URL: ${GITEA_URL} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} LOG_LEVEL: ${LOG_LEVEL:-info} HOST: 0.0.0.0 @@ -255,9 +215,9 @@ services: COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0} COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10} COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true} - # Telemetry (task completion tracking & predictions) + # Telemetry (disabled by default) MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev} + MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-} MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} @@ -279,56 +239,6 @@ services: restart_policy: condition: on-failure - # ====================== - # Mosaic API - # ====================== - api: - image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} - 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:-} - JWT_SECRET: ${JWT_SECRET:-change-this-to-a-random-secret} - JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} - CSRF_SECRET: ${CSRF_SECRET} - OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} - OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - # Telemetry (task completion tracking & predictions) - MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev} - MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} - MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} - MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} - 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 # ====================== @@ -350,15 +260,16 @@ services: - orchestrator_workspace:/workspace healthcheck: test: - ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"] + [ + "CMD-SHELL", + 'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', + ] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - internal - # Note: security_opt not supported in swarm mode - # Security hardening done via cap_drop/cap_add cap_drop: - ALL cap_add: @@ -369,35 +280,152 @@ services: restart_policy: condition: on-failure + # ============================================ + # MATRIX (Synapse + Element Web) + # ============================================ + # ====================== - # Mosaic Web + # Synapse Database Init # ====================== - web: - image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} + # Creates the 'synapse' database in the shared PostgreSQL instance. + # Runs once and exits. Idempotent — safe to run on every deploy. + synapse-db-init: + image: postgres:17-alpine 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 + PGHOST: postgres + PGPORT: 5432 + PGUSER: ${POSTGRES_USER} + PGPASSWORD: ${POSTGRES_PASSWORD} + SYNAPSE_DB: ${SYNAPSE_POSTGRES_DB} + SYNAPSE_USER: ${SYNAPSE_POSTGRES_USER} + SYNAPSE_PASSWORD: ${SYNAPSE_POSTGRES_PASSWORD} + entrypoint: ["sh", "-c"] + command: + - | + until pg_isready -h postgres -p 5432 -U $${PGUSER}; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + echo "PostgreSQL is ready. Creating Synapse database and user..." + + psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_roles WHERE rolname='$${SYNAPSE_USER}'" | grep -q 1 || \ + psql -h postgres -U $${PGUSER} -c "CREATE USER $${SYNAPSE_USER} WITH PASSWORD '$${SYNAPSE_PASSWORD}';" + + psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_database WHERE datname='$${SYNAPSE_DB}'" | grep -q 1 || \ + psql -h postgres -U $${PGUSER} -c "CREATE DATABASE $${SYNAPSE_DB} OWNER $${SYNAPSE_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;" + + echo "Synapse database ready: $${SYNAPSE_DB}" networks: + - internal + deploy: + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 5 + + # ====================== + # Synapse (Matrix Homeserver) + # ====================== + synapse: + image: matrixdotorg/synapse:${SYNAPSE_IMAGE_TAG:-latest} + environment: + SYNAPSE_CONFIG_DIR: /data + SYNAPSE_CONFIG_PATH: /data/homeserver.yaml + volumes: + - /opt/mosaic/synapse:/data + healthcheck: + test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 30s + networks: + - internal - traefik-public deploy: restart_policy: condition: on-failure + delay: 10s + max_attempts: 10 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}" + - "traefik.http.routers.matrix.rule=Host(`${MATRIX_DOMAIN}`)" + - "traefik.http.routers.matrix.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.matrix.tls=${TRAEFIK_TLS_ENABLED:-true}" + - "traefik.http.routers.matrix.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" + - "traefik.http.services.matrix.loadbalancer.server.port=8008" + - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" + + # ====================== + # Element Web (Matrix Client) + # ====================== + element-web: + image: vectorim/element-web:${ELEMENT_IMAGE_TAG:-latest} + volumes: + - /opt/mosaic/synapse/element-config.json:/app/config.json:ro + healthcheck: + test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + networks: + - internal + - traefik-public + deploy: + restart_policy: + condition: on-failure + delay: 5s + labels: + - "traefik.enable=true" + - "traefik.http.routers.element.rule=Host(`${ELEMENT_DOMAIN}`)" + - "traefik.http.routers.element.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" + - "traefik.http.routers.element.tls=${TRAEFIK_TLS_ENABLED:-true}" + - "traefik.http.routers.element.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" + - "traefik.http.services.element.loadbalancer.server.port=80" + - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" + + # ============================================ + # SPEECH SERVICES + # ============================================ + + # ====================== + # Speaches (STT + basic TTS) + # ====================== + speaches: + image: ghcr.io/speaches-ai/speaches:latest-cpu + environment: + WHISPER__MODEL: ${SPEACHES_WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo} + volumes: + - speaches_models:/root/.cache/huggingface + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + networks: + - internal + deploy: + restart_policy: + condition: on-failure + + # ====================== + # Kokoro TTS + # ====================== + kokoro-tts: + image: ghcr.io/remsky/kokoro-fastapi-cpu:latest + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + networks: + - internal + deploy: + restart_policy: + condition: on-failure # ====================== # Volumes @@ -405,19 +433,8 @@ services: volumes: postgres_data: valkey_data: - # OpenBao volumes - commented out (using standalone deployment) - # openbao_data: - # openbao_logs: - # openbao_init: - # Authentik volumes - commented out (using external Authentik) - # authentik_postgres_data: - # authentik_redis_data: - # authentik_media: - # authentik_certs: - # authentik_templates: - # Ollama volume - commented out (using external Ollama) - # ollama_data: orchestrator_workspace: + speaches_models: # ====================== # Networks diff --git a/docker-compose.swarm.yml b/docker-compose.swarm.yml deleted file mode 100644 index 1d3b1af..0000000 --- a/docker-compose.swarm.yml +++ /dev/null @@ -1,459 +0,0 @@ -# ============================================== -# Mosaic Stack - Docker Swarm Deployment -# ============================================== -# -# IMPORTANT: Docker Swarm does NOT support docker-compose profiles -# To disable services (e.g., for external alternatives), manually comment them out -# -# Current Configuration: -# - PostgreSQL: ENABLED (internal) -# - Valkey: ENABLED (internal) -# - Coordinator: ENABLED (internal) -# - OpenBao: DISABLED (must use standalone - see docker-compose.openbao.yml) -# - Authentik: DISABLED (commented out - using external OIDC) -# - Ollama: DISABLED (commented out - using external Ollama) -# -# For detailed deployment instructions, see: -# docs/SWARM-DEPLOYMENT.md -# -# Quick Start: -# 1. cp .env.swarm.example .env -# 2. nano .env # Configure environment -# 3. ./scripts/deploy-swarm.sh mosaic -# 4. Initialize OpenBao manually (see docs/SWARM-DEPLOYMENT.md) -# -# ============================================== - -services: - # ====================== - # PostgreSQL Database - # ====================== - postgres: - image: git.mosaicstack.dev/mosaic/stack-postgres:${IMAGE_TAG:-latest} - 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 - # Note: init-scripts bind mount removed for Portainer compatibility - # Init scripts are baked into the postgres image at build time - 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 noeviction - - --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: - image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest} - entrypoint: ["dumb-init", "--"] - command: ["bao", "server", "-config=/openbao/config/config.hcl"] - env_file: .env - environment: - OPENBAO_ADDR: http://0.0.0.0:8200 - volumes: - - openbao_data:/openbao/data - - openbao_logs:/openbao/logs - - openbao_init:/openbao/init - cap_add: - - IPC_LOCK - healthcheck: - test: - [ - "CMD-SHELL", - "wget --spider --quiet 'http://localhost:8200/v1/sys/health?standbyok=true&uninitcode=200&sealedcode=200'", - ] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - internal - deploy: - restart_policy: - condition: on-failure - - # ====================== - # OpenBao Init Sidecar - # ====================== - # Auto-initializes and unseals OpenBao on first run. - # The init script has built-in retry logic (waits for OpenBao API). - openbao-init: - image: git.mosaicstack.dev/mosaic/stack-openbao:${IMAGE_TAG:-latest} - command: /openbao/init.sh - env_file: .env - environment: - VAULT_ADDR: http://openbao:8200 - volumes: - - openbao_init:/openbao/init - networks: - - internal - deploy: - restart_policy: - condition: on-failure - max_attempts: 5 - delay: 10s - - # ====================== - # Authentik - COMMENTED OUT (Using External Authentik) - # ====================== - # Uncomment these services if you want to run Authentik internally - # For external Authentik, configure OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET in .env - # - # authentik-postgres: - # image: postgres:17.7-alpine3.22 - # 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: - # 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: - # 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: - # 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 Coordinator - # ====================== - coordinator: - image: git.mosaicstack.dev/mosaic/stack-coordinator:${IMAGE_TAG:-latest} - env_file: .env - environment: - GITEA_WEBHOOK_SECRET: ${GITEA_WEBHOOK_SECRET} - GITEA_URL: ${GITEA_URL:-https://git.mosaicstack.dev} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} - LOG_LEVEL: ${LOG_LEVEL:-info} - HOST: 0.0.0.0 - PORT: 8000 - COORDINATOR_POLL_INTERVAL: ${COORDINATOR_POLL_INTERVAL:-5.0} - COORDINATOR_MAX_CONCURRENT_AGENTS: ${COORDINATOR_MAX_CONCURRENT_AGENTS:-10} - COORDINATOR_ENABLED: ${COORDINATOR_ENABLED:-true} - # Telemetry (task completion tracking & predictions) - MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev} - MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} - MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} - MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} - healthcheck: - test: - [ - "CMD", - "python", - "-c", - "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')", - ] - interval: 30s - timeout: 10s - retries: 3 - start_period: 5s - networks: - - internal - deploy: - restart_policy: - condition: on-failure - - # ====================== - # Mosaic API - # ====================== - api: - image: git.mosaicstack.dev/mosaic/stack-api:${IMAGE_TAG:-latest} - 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} - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} - OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} - OPENBAO_ADDR: ${OPENBAO_ADDR:-http://openbao:8200} - ORCHESTRATOR_URL: ${ORCHESTRATOR_URL:-http://orchestrator:3001} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - # Telemetry (task completion tracking & predictions) - MOSAIC_TELEMETRY_ENABLED: ${MOSAIC_TELEMETRY_ENABLED:-false} - MOSAIC_TELEMETRY_SERVER_URL: ${MOSAIC_TELEMETRY_SERVER_URL:-https://tel-api.mosaicstack.dev} - MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} - MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} - MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} - 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: git.mosaicstack.dev/mosaic/stack-orchestrator:${IMAGE_TAG:-latest} - 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 - # Note: security_opt not supported in swarm mode - # Security hardening done via cap_drop/cap_add - cap_drop: - - ALL - cap_add: - - NET_BIND_SERVICE - tmpfs: - - /tmp:noexec,nosuid,size=100m - deploy: - restart_policy: - condition: on-failure - - # ====================== - # Mosaic Web - # ====================== - web: - image: git.mosaicstack.dev/mosaic/stack-web:${IMAGE_TAG:-latest} - 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 volumes - commented out (using external Authentik) - # authentik_postgres_data: - # authentik_redis_data: - # authentik_media: - # authentik_certs: - # authentik_templates: - # Ollama volume - commented out (using external Ollama) - # ollama_data: - orchestrator_workspace: - -# ====================== -# Networks -# ====================== -networks: - internal: - driver: overlay - traefik-public: - external: true diff --git a/docker-compose.yml b/docker-compose.yml index 6858318..8350b37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,9 +27,6 @@ services: start_period: 30s networks: - mosaic-internal - profiles: - - database - - full labels: - "com.mosaic.service=database" - "com.mosaic.description=PostgreSQL 17 with pgvector" @@ -58,9 +55,6 @@ services: start_period: 10s networks: - mosaic-internal - profiles: - - cache - - full labels: - "com.mosaic.service=cache" - "com.mosaic.description=Valkey Redis-compatible cache" @@ -384,6 +378,10 @@ services: MOSAIC_TELEMETRY_API_KEY: ${MOSAIC_TELEMETRY_API_KEY:-} MOSAIC_TELEMETRY_INSTANCE_ID: ${MOSAIC_TELEMETRY_INSTANCE_ID:-} MOSAIC_TELEMETRY_DRY_RUN: ${MOSAIC_TELEMETRY_DRY_RUN:-false} + # Frontend URLs (for CORS and auth redirects) + NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000} + NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:3001} + TRUSTED_ORIGINS: ${TRUSTED_ORIGINS:-} volumes: - openbao_init:/openbao/init:ro ports: @@ -456,7 +454,10 @@ services: condition: service_healthy healthcheck: test: - ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"] + [ + "CMD-SHELL", + 'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', + ] interval: 30s timeout: 10s retries: 3 diff --git a/docker/docker-compose.build.yml b/docker/docker-compose.build.yml index 66a5e00..5e455d2 100644 --- a/docker/docker-compose.build.yml +++ b/docker/docker-compose.build.yml @@ -465,7 +465,10 @@ services: condition: service_healthy healthcheck: test: - ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1"] + [ + "CMD-SHELL", + 'node -e "require(''http'').get(''http://localhost:3001/health'', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"', + ] interval: 30s timeout: 10s retries: 3 diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml deleted file mode 100644 index af6dde7..0000000 --- a/docker/docker-compose.dev.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Development overrides for docker-compose.yml -# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up - -services: - postgres: - environment: - POSTGRES_USER: mosaic - POSTGRES_PASSWORD: mosaic_dev_password - POSTGRES_DB: mosaic - ports: - - "5432:5432" - # Enable query logging for development - command: - - "postgres" - - "-c" - - "log_statement=all" - - "-c" - - "log_duration=on" - - valkey: - ports: - - "6379:6379" diff --git a/docker/docker-compose.example.external.yml b/docker/docker-compose.example.external.yml deleted file mode 100644 index bbbb9e4..0000000 --- a/docker/docker-compose.example.external.yml +++ /dev/null @@ -1,126 +0,0 @@ -# ============================================== -# Mosaic Stack - External Services Deployment Example -# ============================================== -# This example shows a production deployment using external managed services. -# All infrastructure (database, cache, secrets, auth, AI) is managed externally. -# -# Usage: -# 1. Copy this file to docker-compose.override.yml -# 2. Set COMPOSE_PROFILES= (empty) in .env -# 3. Configure external service URLs in .env (see below) -# 4. Run: docker compose up -d -# -# Or run directly: -# docker compose -f docker-compose.yml -f docker-compose.example.external.yml up -d -# -# Services Included: -# - API (NestJS) - configured to use external services -# - Web (Next.js) -# - Orchestrator (Agent management) -# -# External Services (configured via .env): -# - PostgreSQL (e.g., AWS RDS, Google Cloud SQL, Azure Database) -# - Redis/Valkey (e.g., AWS ElastiCache, Google Memorystore, Azure Cache) -# - OpenBao/Vault (e.g., HashiCorp Vault Cloud, self-hosted) -# - OIDC Provider (e.g., Auth0, Okta, Google, Azure AD) -# - LLM Service (e.g., hosted Ollama, OpenAI, Anthropic) -# -# Required Environment Variables (.env): -# COMPOSE_PROFILES= # Empty - no bundled services -# IMAGE_TAG=latest -# -# # External Database -# DATABASE_URL=postgresql://user:password@rds.example.com:5432/mosaic -# -# # External Cache -# VALKEY_URL=redis://elasticache.example.com:6379 -# -# # External Secrets (OpenBao/Vault) -# OPENBAO_ADDR=https://vault.example.com:8200 -# OPENBAO_ROLE_ID=your-role-id -# OPENBAO_SECRET_ID=your-secret-id -# -# # External OIDC Authentication -# OIDC_ENABLED=true -# OIDC_ISSUER=https://auth.example.com/ -# OIDC_CLIENT_ID=your-client-id -# OIDC_CLIENT_SECRET=your-client-secret -# -# # External LLM Service -# OLLAMA_ENDPOINT=https://ollama.example.com:11434 -# # Or use OpenAI: -# # AI_PROVIDER=openai -# # OPENAI_API_KEY=sk-... -# -# ============================================== - -services: - # Disable all bundled infrastructure services - postgres: - profiles: - - disabled - - valkey: - profiles: - - disabled - - openbao: - profiles: - - disabled - - openbao-init: - profiles: - - disabled - - authentik-postgres: - profiles: - - disabled - - authentik-redis: - profiles: - - disabled - - authentik-server: - profiles: - - disabled - - authentik-worker: - profiles: - - disabled - - ollama: - profiles: - - disabled - - # Configure API to use external services - api: - environment: - # External database (e.g., AWS RDS) - DATABASE_URL: ${DATABASE_URL} - - # External cache (e.g., AWS ElastiCache) - VALKEY_URL: ${VALKEY_URL} - - # External secrets (e.g., HashiCorp Vault Cloud) - OPENBAO_ADDR: ${OPENBAO_ADDR} - OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID} - OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID} - - # External LLM (e.g., hosted Ollama or OpenAI) - OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT} - - # External OIDC (e.g., Auth0, Okta, Google) - OIDC_ENABLED: ${OIDC_ENABLED} - OIDC_ISSUER: ${OIDC_ISSUER} - OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} - OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} - - # Security - CSRF_SECRET: ${CSRF_SECRET} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - - # Web app remains unchanged - # web: (uses defaults from docker-compose.yml) - - # Orchestrator remains unchanged - # orchestrator: (uses defaults from docker-compose.yml) diff --git a/docker/docker-compose.example.hybrid.yml b/docker/docker-compose.example.hybrid.yml deleted file mode 100644 index 93de773..0000000 --- a/docker/docker-compose.example.hybrid.yml +++ /dev/null @@ -1,114 +0,0 @@ -# ============================================== -# Mosaic Stack - Hybrid Deployment Example -# ============================================== -# This example shows a hybrid deployment mixing bundled and external services. -# Common for staging environments: bundled database/cache, external auth/secrets. -# -# Usage: -# 1. Copy this file to docker-compose.override.yml -# 2. Set COMPOSE_PROFILES=database,cache,ollama in .env -# 3. Configure external service URLs in .env (see below) -# 4. Run: docker compose up -d -# -# Or run directly: -# docker compose -f docker-compose.yml -f docker-compose.example.hybrid.yml up -d -# -# Services Included (Bundled): -# - PostgreSQL 17 with pgvector -# - Valkey (Redis-compatible cache) -# - Ollama (local LLM) -# - API (NestJS) -# - Web (Next.js) -# - Orchestrator (Agent management) -# -# Services Included (External): -# - OpenBao/Vault (managed secrets) -# - Authentik/OIDC (managed authentication) -# -# Environment Variables (.env): -# COMPOSE_PROFILES=database,cache,ollama # Enable only these bundled services -# IMAGE_TAG=dev -# -# # Bundled Database (default from docker-compose.yml) -# DATABASE_URL=postgresql://mosaic:${POSTGRES_PASSWORD}@postgres:5432/mosaic -# -# # Bundled Cache (default from docker-compose.yml) -# VALKEY_URL=redis://valkey:6379 -# -# # Bundled Ollama (default from docker-compose.yml) -# OLLAMA_ENDPOINT=http://ollama:11434 -# -# # External Secrets (OpenBao/Vault) -# OPENBAO_ADDR=https://vault.example.com:8200 -# OPENBAO_ROLE_ID=your-role-id -# OPENBAO_SECRET_ID=your-secret-id -# -# # External OIDC Authentication -# OIDC_ENABLED=true -# OIDC_ISSUER=https://auth.example.com/ -# OIDC_CLIENT_ID=your-client-id -# OIDC_CLIENT_SECRET=your-client-secret -# -# ============================================== - -services: - # Use bundled PostgreSQL and Valkey (enabled via database,cache profiles) - # No overrides needed - profiles handle this - - # Disable bundled Authentik - use external OIDC - authentik-postgres: - profiles: - - disabled - - authentik-redis: - profiles: - - disabled - - authentik-server: - profiles: - - disabled - - authentik-worker: - profiles: - - disabled - - # Disable bundled OpenBao - use external vault - openbao: - profiles: - - disabled - - openbao-init: - profiles: - - disabled - - # Use bundled Ollama (enabled via ollama profile) - # No override needed - - # Configure API for hybrid deployment - api: - environment: - # Bundled database (default) - DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-mosaic} - - # Bundled cache (default) - VALKEY_URL: redis://valkey:6379 - - # External secrets - OPENBAO_ADDR: ${OPENBAO_ADDR} - OPENBAO_ROLE_ID: ${OPENBAO_ROLE_ID} - OPENBAO_SECRET_ID: ${OPENBAO_SECRET_ID} - - # Bundled Ollama (default) - OLLAMA_ENDPOINT: http://ollama:11434 - - # External OIDC - OIDC_ENABLED: ${OIDC_ENABLED} - OIDC_ISSUER: ${OIDC_ISSUER} - OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} - OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} - - # Security - CSRF_SECRET: ${CSRF_SECRET} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - - # Web and Orchestrator use defaults from docker-compose.yml diff --git a/docker/docker-compose.example.turnkey.yml b/docker/docker-compose.example.turnkey.yml deleted file mode 100644 index 9443c01..0000000 --- a/docker/docker-compose.example.turnkey.yml +++ /dev/null @@ -1,43 +0,0 @@ -# ============================================== -# Mosaic Stack - Turnkey Deployment Example -# ============================================== -# This example shows a complete all-in-one deployment with all services bundled. -# Ideal for local development, testing, and demo environments. -# -# Usage: -# 1. Copy this file to docker-compose.override.yml (optional) -# 2. Set COMPOSE_PROFILES=full in .env -# 3. Run: docker compose up -d -# -# Or run directly: -# docker compose -f docker-compose.yml -f docker-compose.example.turnkey.yml up -d -# -# Services Included: -# - PostgreSQL 17 with pgvector -# - Valkey (Redis-compatible cache) -# - OpenBao (secrets management) -# - Authentik (OIDC authentication) -# - Ollama (local LLM) -# - Traefik (reverse proxy) - optional, requires traefik-bundled profile -# - API (NestJS) -# - Web (Next.js) -# - Orchestrator (Agent management) -# -# Environment Variables (.env): -# COMPOSE_PROFILES=full -# IMAGE_TAG=dev # or latest -# -# All services run in Docker containers with no external dependencies. -# ============================================== - -services: - # No service overrides needed - the main docker-compose.yml handles everything - # This file serves as documentation for turnkey deployment - # Set COMPOSE_PROFILES=full in your .env file to enable all services - - # Placeholder to make the file valid YAML - # (Docker Compose requires at least one service definition) - _placeholder: - image: alpine:latest - profiles: - - never-used diff --git a/docker/docker-compose.matrix.yml b/docker/docker-compose.matrix.yml deleted file mode 100644 index 1062111..0000000 --- a/docker/docker-compose.matrix.yml +++ /dev/null @@ -1,123 +0,0 @@ -# ============================================== -# Matrix Dev Environment (Synapse + Element Web) -# ============================================== -# -# Development-only overlay for testing the Matrix bridge locally. -# NOT for production — use docker-compose.sample.matrix.yml for production. -# -# Usage: -# docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -d -# -# Or with Makefile: -# make matrix-up -# -# This overlay: -# - Adds Synapse homeserver (localhost:8008) using shared PostgreSQL -# - Adds Element Web client (localhost:8501) -# - Creates a separate 'synapse' database in the shared PostgreSQL instance -# - Enables open registration for easy dev testing -# -# After first startup, create the bot account: -# docker/matrix/scripts/setup-bot.sh -# -# ============================================== - -services: - # ====================== - # Synapse Database Init - # ====================== - # Creates the 'synapse' database and user in the shared PostgreSQL instance. - # Runs once and exits — idempotent, safe to run repeatedly. - synapse-db-init: - image: postgres:17-alpine - container_name: mosaic-synapse-db-init - restart: "no" - environment: - PGHOST: postgres - PGPORT: 5432 - PGUSER: ${POSTGRES_USER:-mosaic} - PGPASSWORD: ${POSTGRES_PASSWORD:-mosaic_dev_password} - SYNAPSE_DB: ${SYNAPSE_POSTGRES_DB:-synapse} - SYNAPSE_USER: ${SYNAPSE_POSTGRES_USER:-synapse} - SYNAPSE_PASSWORD: ${SYNAPSE_POSTGRES_PASSWORD:-synapse_dev_password} - entrypoint: ["sh", "-c"] - command: - - | - until pg_isready -h postgres -p 5432 -U $${PGUSER}; do - echo "Waiting for PostgreSQL..." - sleep 2 - done - echo "PostgreSQL is ready. Creating Synapse database and user..." - - psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_roles WHERE rolname='$${SYNAPSE_USER}'" | grep -q 1 || \ - psql -h postgres -U $${PGUSER} -c "CREATE USER $${SYNAPSE_USER} WITH PASSWORD '$${SYNAPSE_PASSWORD}';" - - psql -h postgres -U $${PGUSER} -tc "SELECT 1 FROM pg_database WHERE datname='$${SYNAPSE_DB}'" | grep -q 1 || \ - psql -h postgres -U $${PGUSER} -c "CREATE DATABASE $${SYNAPSE_DB} OWNER $${SYNAPSE_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;" - - echo "Synapse database ready: $${SYNAPSE_DB}" - depends_on: - postgres: - condition: service_healthy - networks: - - mosaic-internal - - # ====================== - # Synapse (Matrix Homeserver) - # ====================== - synapse: - image: matrixdotorg/synapse:latest - container_name: mosaic-synapse - restart: unless-stopped - environment: - SYNAPSE_CONFIG_DIR: /data - SYNAPSE_CONFIG_PATH: /data/homeserver.yaml - ports: - - "${SYNAPSE_CLIENT_PORT:-8008}:8008" - - "${SYNAPSE_FEDERATION_PORT:-8448}:8448" - volumes: - - /opt/mosaic/synapse/homeserver.yaml:/data/homeserver.yaml:ro - - /opt/mosaic/synapse/media_store:/data/media_store - - /opt/mosaic/synapse/keys:/data/keys - depends_on: - postgres: - condition: service_healthy - synapse-db-init: - condition: service_completed_successfully - healthcheck: - test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"] - interval: 15s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - mosaic-internal - labels: - com.mosaic.service: "matrix-synapse" - com.mosaic.description: "Matrix homeserver (dev)" - - # ====================== - # Element Web (Matrix Client) - # ====================== - element-web: - image: vectorim/element-web:latest - container_name: mosaic-element-web - restart: unless-stopped - ports: - - "${ELEMENT_PORT:-8501}:80" - volumes: - - /opt/mosaic/synapse/element-config.json:/app/config.json:ro - depends_on: - synapse: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - networks: - - mosaic-internal - labels: - com.mosaic.service: "matrix-element" - com.mosaic.description: "Element Web client (dev)" diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml deleted file mode 100644 index 01b637d..0000000 --- a/docker/docker-compose.prod.yml +++ /dev/null @@ -1,182 +0,0 @@ -# Production Docker Compose - Uses pre-built images from Gitea Packages -# -# Prerequisites: -# - Images built and pushed to git.mosaicstack.dev/mosaic/* -# - .env file configured with production values -# -# Usage: -# docker compose -f docker-compose.prod.yml up -d -# -# For Portainer: -# - Stack → Add Stack → Repository -# - Compose file: docker-compose.prod.yml - -services: - # ====================== - # PostgreSQL Database - # ====================== - postgres: - image: git.mosaicstack.dev/mosaic/postgres:latest - container_name: mosaic-postgres - restart: unless-stopped - environment: - POSTGRES_USER: ${POSTGRES_USER:-mosaic} - POSTGRES_PASSWORD: ${POSTGRES_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 - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-mosaic} -d ${POSTGRES_DB:-mosaic}"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 30s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=database" - - "com.mosaic.description=PostgreSQL 17 with pgvector" - - # ====================== - # Valkey Cache - # ====================== - valkey: - image: valkey/valkey:8-alpine - container_name: mosaic-valkey - restart: unless-stopped - command: - - valkey-server - - --maxmemory ${VALKEY_MAXMEMORY:-256mb} - - --maxmemory-policy noeviction - - --appendonly yes - volumes: - - valkey_data:/data - healthcheck: - test: ["CMD", "valkey-cli", "ping"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 10s - networks: - - mosaic-internal - labels: - - "com.mosaic.service=cache" - - "com.mosaic.description=Valkey Redis-compatible cache" - - # ====================== - # Mosaic API - # ====================== - api: - image: git.mosaicstack.dev/mosaic/api:latest - container_name: mosaic-api - restart: unless-stopped - environment: - NODE_ENV: production - PORT: ${API_PORT:-3001} - API_HOST: ${API_HOST:-0.0.0.0} - DATABASE_URL: postgresql://${POSTGRES_USER:-mosaic}:${POSTGRES_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} - JWT_SECRET: ${JWT_SECRET} - JWT_EXPIRATION: ${JWT_EXPIRATION:-24h} - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} - CSRF_SECRET: ${CSRF_SECRET} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - OLLAMA_ENDPOINT: ${OLLAMA_ENDPOINT:-http://ollama:11434} - ports: - - "${API_PORT:-3001}:${API_PORT:-3001}" - depends_on: - postgres: - condition: service_healthy - valkey: - condition: service_healthy - 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: - - mosaic-internal - - mosaic-public - labels: - - "com.mosaic.service=api" - - "com.mosaic.description=Mosaic NestJS API" - - "traefik.enable=${TRAEFIK_ENABLE:-false}" - - "traefik.http.routers.mosaic-api.rule=Host(`${MOSAIC_API_DOMAIN:-api.mosaicstack.dev}`)" - - "traefik.http.routers.mosaic-api.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mosaic-api.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.services.mosaic-api.loadbalancer.server.port=${API_PORT:-3001}" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - - "traefik.http.routers.mosaic-api.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - # ====================== - # Mosaic Web - # ====================== - web: - image: git.mosaicstack.dev/mosaic/web:latest - container_name: mosaic-web - restart: unless-stopped - environment: - NODE_ENV: production - PORT: ${WEB_PORT:-3000} - NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.mosaicstack.dev} - ports: - - "${WEB_PORT:-3000}:${WEB_PORT:-3000}" - depends_on: - api: - condition: service_healthy - 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: - - mosaic-public - labels: - - "com.mosaic.service=web" - - "com.mosaic.description=Mosaic Next.js Web App" - - "traefik.enable=${TRAEFIK_ENABLE:-false}" - - "traefik.http.routers.mosaic-web.rule=Host(`${MOSAIC_WEB_DOMAIN:-app.mosaicstack.dev}`)" - - "traefik.http.routers.mosaic-web.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.mosaic-web.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.services.mosaic-web.loadbalancer.server.port=${WEB_PORT:-3000}" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-mosaic-public}" - - "traefik.http.routers.mosaic-web.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - -# ====================== -# Volumes -# ====================== -volumes: - postgres_data: - name: mosaic-postgres-data - driver: local - valkey_data: - name: mosaic-valkey-data - driver: local - -# ====================== -# Networks -# ====================== -networks: - mosaic-internal: - name: mosaic-internal - driver: bridge - mosaic-public: - name: mosaic-public - driver: bridge diff --git a/docker/docker-compose.sample.matrix.yml b/docker/docker-compose.sample.matrix.yml deleted file mode 100644 index 5f71d48..0000000 --- a/docker/docker-compose.sample.matrix.yml +++ /dev/null @@ -1,206 +0,0 @@ -# ============================================== -# Matrix (Synapse + Element) - Sample Swarm Deployment -# ============================================== -# -# Standalone Matrix homeserver deployment for use with Mosaic Stack. -# This is SEPARATE infrastructure — not part of the Mosaic Stack itself. -# Mosaic connects to it via MATRIX_HOMESERVER_URL environment variable. -# -# Also serves: personal communications, GoToSocial bridges, other projects. -# -# Usage (Docker Swarm via Portainer): -# 1. Create a new stack in Portainer -# 2. Paste this file or point to the repo -# 3. Set environment variables in Portainer's env var section -# 4. Deploy the stack -# -# Usage (Docker Swarm CLI): -# 1. cp docker-compose.sample.matrix.env .env -# 2. nano .env # Configure -# 3. docker stack deploy -c docker-compose.sample.matrix.yml matrix -# -# Post-Deploy Setup: -# 1. Generate Synapse config (first run only): -# docker exec python -m synapse.app.homeserver \ -# --server-name ${MATRIX_DOMAIN} --report-stats no \ -# --generate-config --config-path /data/homeserver.yaml -# -# 2. Create admin account: -# docker exec -it register_new_matrix_user \ -# -u admin -a -c /data/homeserver.yaml http://localhost:8008 -# -# 3. Create Mosaic bot account: -# docker exec -it register_new_matrix_user \ -# -u mosaic-bot -c /data/homeserver.yaml http://localhost:8008 -# -# 4. Generate bot access token: -# curl -X POST http://localhost:8008/_matrix/client/v3/login \ -# -d '{"type":"m.login.password","user":"mosaic-bot","password":""}' -# -# 5. Set MATRIX_ACCESS_TOKEN in Mosaic Stack .env -# -# Required Environment Variables: -# MATRIX_DOMAIN=matrix.example.com # Synapse server name (permanent!) -# ELEMENT_DOMAIN=chat.example.com # Element Web domain -# POSTGRES_PASSWORD= # Synapse database password -# -# Optional Environment Variables: -# SYNAPSE_IMAGE_TAG=latest # Synapse version -# ELEMENT_IMAGE_TAG=latest # Element Web version -# POSTGRES_IMAGE_TAG=16-alpine # PostgreSQL version -# TRAEFIK_ENTRYPOINT=websecure # Traefik entrypoint name -# TRAEFIK_CERTRESOLVER=letsencrypt # Traefik cert resolver -# TRAEFIK_DOCKER_NETWORK=traefik-public # Traefik network name -# SYNAPSE_ENABLE_REGISTRATION=false # Public registration -# SYNAPSE_REPORT_STATS=no # Anonymous stats reporting -# SYNAPSE_MAX_UPLOAD_SIZE=50M # Max file upload size -# -# Connecting to Mosaic Stack: -# Add to your Mosaic Stack .env: -# MATRIX_HOMESERVER_URL=http://synapse:8008 (if same Docker network) -# MATRIX_HOMESERVER_URL=https://matrix.example.com (if external) -# MATRIX_ACCESS_TOKEN= -# MATRIX_BOT_USER_ID=@mosaic-bot:matrix.example.com -# -# ============================================== - -services: - # ====================== - # Synapse (Matrix Homeserver) - # ====================== - synapse: - image: matrixdotorg/synapse:${SYNAPSE_IMAGE_TAG:-latest} - environment: - SYNAPSE_SERVER_NAME: ${MATRIX_DOMAIN} - SYNAPSE_REPORT_STATS: ${SYNAPSE_REPORT_STATS:-no} - SYNAPSE_CONFIG_DIR: /data - SYNAPSE_DATA_DIR: /data - SYNAPSE_LOG_LEVEL: ${SYNAPSE_LOG_LEVEL:-WARNING} - # Database connection (external PostgreSQL or bundled) - POSTGRES_HOST: synapse-postgres - POSTGRES_PORT: 5432 - POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse} - POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - synapse-data:/data - - synapse-media:/data/media_store - depends_on: - synapse-postgres: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 30s - networks: - - internal - - traefik-public - deploy: - restart_policy: - condition: on-failure - delay: 10s - labels: - - "traefik.enable=true" - - "traefik.http.routers.matrix.rule=Host(`${MATRIX_DOMAIN}`)" - - "traefik.http.routers.matrix.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.matrix.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.routers.matrix.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - "traefik.http.services.matrix.loadbalancer.server.port=8008" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" - # Well-known delegation (optional — for .well-known/matrix/server) - # - "traefik.http.routers.matrix-wellknown.rule=Host(`${MATRIX_DOMAIN}`) && PathPrefix(`/.well-known/matrix`)" - - # ====================== - # Element Web (Matrix Client) - # ====================== - element-web: - image: vectorim/element-web:${ELEMENT_IMAGE_TAG:-latest} - volumes: - - element-config:/app/config - healthcheck: - test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:80 || exit 1"] - interval: 30s - timeout: 5s - retries: 3 - networks: - - internal - - traefik-public - deploy: - restart_policy: - condition: on-failure - labels: - - "traefik.enable=true" - - "traefik.http.routers.element.rule=Host(`${ELEMENT_DOMAIN}`)" - - "traefik.http.routers.element.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.element.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.routers.element.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - "traefik.http.services.element.loadbalancer.server.port=80" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" - - # ====================== - # PostgreSQL (Synapse Database) - # ====================== - # Separate from Mosaic's PostgreSQL — Synapse manages its own schema. - # If you prefer a shared PostgreSQL instance, remove this service and - # point POSTGRES_HOST to your existing PostgreSQL with a separate database. - synapse-postgres: - image: postgres:${POSTGRES_IMAGE_TAG:-16-alpine} - environment: - POSTGRES_DB: ${SYNAPSE_POSTGRES_DB:-synapse} - POSTGRES_USER: ${SYNAPSE_POSTGRES_USER:-synapse} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumes: - - synapse-postgres-data:/var/lib/postgresql/data - healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${SYNAPSE_POSTGRES_USER:-synapse} -d ${SYNAPSE_POSTGRES_DB:-synapse}", - ] - interval: 10s - timeout: 5s - retries: 5 - networks: - - internal - deploy: - restart_policy: - condition: on-failure - - # ====================== - # coturn (TURN/STUN for VoIP) - Optional - # ====================== - # Uncomment if you need voice/video calls through NAT. - # Requires additional DNS and port configuration. - # - # coturn: - # image: coturn/coturn:latest - # environment: - # TURN_REALM: ${MATRIX_DOMAIN} - # TURN_SECRET: ${COTURN_SECRET} - # ports: - # - "3478:3478/tcp" - # - "3478:3478/udp" - # - "5349:5349/tcp" - # - "5349:5349/udp" - # - "49152-49200:49152-49200/udp" - # networks: - # - internal - # deploy: - # restart_policy: - # condition: on-failure - -volumes: - synapse-data: - synapse-media: - synapse-postgres-data: - element-config: - -networks: - internal: - driver: overlay - traefik-public: - external: true - name: ${TRAEFIK_DOCKER_NETWORK:-traefik-public} diff --git a/docker/docker-compose.sample.speech.yml b/docker/docker-compose.sample.speech.yml deleted file mode 100644 index 983fb37..0000000 --- a/docker/docker-compose.sample.speech.yml +++ /dev/null @@ -1,164 +0,0 @@ -# ============================================== -# Speech Services - Sample Swarm Deployment -# ============================================== -# -# Standalone speech services deployment for use with Mosaic Stack. -# This is SEPARATE infrastructure — not part of the Mosaic Stack itself. -# Mosaic connects to it via SPEACHES_URL and TTS_URL environment variables. -# -# Provides: -# - Speaches: Speech-to-Text (Whisper) + basic TTS fallback -# - Kokoro TTS: Default high-quality text-to-speech -# - Chatterbox TTS: Premium TTS with voice cloning (optional, requires GPU) -# -# Usage (Docker Swarm via Portainer): -# 1. Create a new stack in Portainer -# 2. Paste this file or point to the repo -# 3. Set environment variables in Portainer's env var section -# 4. Deploy the stack -# -# Usage (Docker Swarm CLI): -# 1. Create .env file with variables below -# 2. docker stack deploy -c docker-compose.sample.speech.yml speech -# -# Required Environment Variables: -# STT_DOMAIN=stt.example.com # Domain for Speaches (STT + basic TTS) -# TTS_DOMAIN=tts.example.com # Domain for Kokoro TTS (default TTS) -# -# Optional Environment Variables: -# WHISPER_MODEL=Systran/faster-whisper-large-v3-turbo # Whisper model for STT -# CHATTERBOX_TTS_DOMAIN=tts-premium.example.com # Domain for Chatterbox (premium TTS) -# TRAEFIK_ENTRYPOINT=websecure # Traefik entrypoint name -# TRAEFIK_CERTRESOLVER=letsencrypt # Traefik cert resolver -# TRAEFIK_DOCKER_NETWORK=traefik-public # Traefik network name -# TRAEFIK_TLS_ENABLED=true # Enable TLS on Traefik routers -# -# Connecting to Mosaic Stack: -# Add to your Mosaic Stack .env: -# SPEACHES_URL=http://speaches:8000 (if same Docker network) -# SPEACHES_URL=https://stt.example.com (if external) -# TTS_URL=http://kokoro-tts:8880 (if same Docker network) -# TTS_URL=https://tts.example.com (if external) -# -# GPU Requirements (Chatterbox only): -# - NVIDIA GPU with CUDA support -# - nvidia-container-toolkit installed on Docker host -# - Docker runtime configured for GPU access -# - Note: Docker Swarm requires "generic resources" for GPU scheduling. -# See: https://docs.docker.com/engine/daemon/nvidia-gpu/#configure-gpus-for-docker-swarm -# -# ============================================== - -services: - # ====================== - # Speaches (STT + basic TTS) - # ====================== - # Primary speech-to-text service using Whisper. - # Also provides basic TTS as a fallback. - speaches: - image: ghcr.io/speaches-ai/speaches:latest - environment: - WHISPER__MODEL: ${WHISPER_MODEL:-Systran/faster-whisper-large-v3-turbo} - volumes: - - speaches-models:/root/.cache/huggingface - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s - networks: - - internal - - traefik-public - deploy: - restart_policy: - condition: on-failure - delay: 10s - labels: - - "traefik.enable=true" - - "traefik.http.routers.speech-stt.rule=Host(`${STT_DOMAIN}`)" - - "traefik.http.routers.speech-stt.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.speech-stt.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.routers.speech-stt.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - "traefik.http.services.speech-stt.loadbalancer.server.port=8000" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" - - # ====================== - # Kokoro TTS (Default TTS) - # ====================== - # High-quality text-to-speech engine. Always deployed alongside Speaches. - kokoro-tts: - image: ghcr.io/remsky/kokoro-fastapi:latest-cpu - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:8880/health || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s - networks: - - internal - - traefik-public - deploy: - restart_policy: - condition: on-failure - delay: 10s - labels: - - "traefik.enable=true" - - "traefik.http.routers.speech-tts.rule=Host(`${TTS_DOMAIN}`)" - - "traefik.http.routers.speech-tts.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - - "traefik.http.routers.speech-tts.tls=${TRAEFIK_TLS_ENABLED:-true}" - - "traefik.http.routers.speech-tts.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - - "traefik.http.services.speech-tts.loadbalancer.server.port=8880" - - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" - - # ====================== - # Chatterbox TTS (Premium TTS - Optional) - # ====================== - # Premium TTS with voice cloning capabilities. Requires NVIDIA GPU. - # - # To enable: Uncomment this service and set CHATTERBOX_TTS_DOMAIN. - # - # For Docker Swarm GPU scheduling, configure generic resources on the node: - # /etc/docker/daemon.json: - # { "runtimes": { "nvidia": { ... } }, - # "node-generic-resources": ["NVIDIA-GPU=0"] } - # - # chatterbox-tts: - # image: devnen/chatterbox-tts-server:latest - # healthcheck: - # test: ["CMD-SHELL", "curl -f http://localhost:8000/health || exit 1"] - # interval: 30s - # timeout: 10s - # retries: 5 - # start_period: 180s - # networks: - # - internal - # - traefik-public - # deploy: - # restart_policy: - # condition: on-failure - # delay: 10s - # resources: - # reservations: - # generic_resources: - # - discrete_resource_spec: - # kind: "NVIDIA-GPU" - # value: 1 - # labels: - # - "traefik.enable=true" - # - "traefik.http.routers.speech-tts-premium.rule=Host(`${CHATTERBOX_TTS_DOMAIN}`)" - # - "traefik.http.routers.speech-tts-premium.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}" - # - "traefik.http.routers.speech-tts-premium.tls=${TRAEFIK_TLS_ENABLED:-true}" - # - "traefik.http.routers.speech-tts-premium.tls.certresolver=${TRAEFIK_CERTRESOLVER:-}" - # - "traefik.http.services.speech-tts-premium.loadbalancer.server.port=8000" - # - "traefik.docker.network=${TRAEFIK_DOCKER_NETWORK:-traefik-public}" - -volumes: - speaches-models: - -networks: - internal: - driver: overlay - traefik-public: - external: true - name: ${TRAEFIK_DOCKER_NETWORK:-traefik-public} diff --git a/docker/matrix/element/config.json b/docker/matrix/element/config.json index 509f013..1cdbfbe 100644 --- a/docker/matrix/element/config.json +++ b/docker/matrix/element/config.json @@ -1,30 +1,34 @@ { "default_server_config": { "m.homeserver": { - "base_url": "http://localhost:8008", - "server_name": "localhost" + "base_url": "https://matrix.mosaicstack.dev", + "server_name": "matrix.mosaicstack.dev" + }, + "m.identity_server": { + "base_url": "https://vector.im" } }, - "brand": "Mosaic Stack Dev", + "brand": "Mosaic Chat", + "integrations_ui_url": "https://scalar.vector.im/", + "integrations_rest_url": "https://scalar.vector.im/api", + "integrations_widgets_urls": [ + "https://scalar.vector.im/_matrix/integrations/v1", + "https://scalar.vector.im/api", + "https://scalar-staging.vector.im/_matrix/integrations/v1", + "https://scalar-staging.vector.im/api", + "https://scalar-staging.riot.im/scalar/api" + ], + "disable_custom_urls": false, + "disable_guests": true, + "disable_login_language_selector": false, + "disable_3pid_login": false, + "default_country_code": "US", + "show_labs_settings": false, "default_theme": "dark", "room_directory": { - "servers": ["localhost"] + "servers": ["matrix.mosaicstack.dev"] }, - "features": { - "feature_video_rooms": false, - "feature_group_calls": false - }, - "show_labs_settings": true, - "piwik": false, - "posthog": { - "enabled": false - }, - "privacy_policy_url": null, - "terms_and_conditions_links": [], "setting_defaults": { - "breadcrumbs": true, - "custom_themes": [] - }, - "disable_guests": true, - "disable_3pid_login": true + "breadcrumbs": true + } } diff --git a/docker/matrix/synapse/homeserver.yaml b/docker/matrix/synapse/homeserver.yaml index 304387c..35e09e5 100644 --- a/docker/matrix/synapse/homeserver.yaml +++ b/docker/matrix/synapse/homeserver.yaml @@ -1,27 +1,31 @@ # ============================================== -# Synapse Homeserver Configuration — Development Only +# Synapse Homeserver Configuration — Production # ============================================== # -# This config is for LOCAL DEVELOPMENT with the Mosaic Stack docker-compose overlay. -# Do NOT use this in production. See docker-compose.sample.matrix.yml for production. +# Deploy to /opt/mosaic/synapse/homeserver.yaml on the Docker host. # -# Server name is set to 'localhost' — this is permanent and cannot be changed -# after the database has been initialized. +# IMPORTANT: server_name is PERMANENT. It becomes part of every user ID +# (@user:server_name) and room alias. It cannot be changed after the +# database has been initialized without losing all data. +# +# Before first deploy, replace ALL placeholders marked REPLACE_*. # # ============================================== -server_name: "localhost" +# REPLACE with your Matrix domain (e.g. matrix.mosaicstack.dev) +# This is permanent — cannot be changed after first startup. +server_name: "REPLACE_MATRIX_DOMAIN" pid_file: /data/homeserver.pid -public_baseurl: "http://localhost:8008/" +public_baseurl: "https://REPLACE_MATRIX_DOMAIN/" # ====================== # Network Listeners # ====================== listeners: - # Client API (used by Element Web, Mosaic bridge, etc.) - port: 8008 tls: false type: http + # Traefik terminates TLS and forwards via X-Forwarded-For x_forwarded: true bind_addresses: ["0.0.0.0"] resources: @@ -35,9 +39,11 @@ database: name: psycopg2 txn_limit: 10000 args: - user: "synapse" - password: "synapse_dev_password" - database: "synapse" + # Must match SYNAPSE_POSTGRES_USER / SYNAPSE_POSTGRES_PASSWORD + # from your Portainer environment variables + user: "REPLACE_SYNAPSE_DB_USER" + password: "REPLACE_SYNAPSE_DB_PASSWORD" + database: "REPLACE_SYNAPSE_DB_NAME" host: "postgres" port: 5432 cp_min: 5 @@ -66,20 +72,25 @@ url_preview_ip_range_blacklist: - "fec0::/10" # ====================== -# Registration (Dev Only) +# Registration # ====================== -enable_registration: true -enable_registration_without_verification: true +# Public registration disabled. Create accounts via the admin API or CLI: +# docker exec -it register_new_matrix_user \ +# -u username -c /data/homeserver.yaml http://localhost:8008 +enable_registration: false # ====================== # Signing Keys # ====================== -# Auto-generated on first startup and persisted in the signing_key volume -signing_key_path: "/data/keys/localhost.signing.key" +# Auto-generated on first startup and persisted in /opt/mosaic/synapse/keys/ +signing_key_path: "/data/keys/signing.key" -# Suppress warning about trusted key servers in dev -suppress_key_server_warning: true -trusted_key_servers: [] +# ====================== +# Trusted Key Servers +# ====================== +# matrix.org is the default. Set to [] to disable federation key trust. +trusted_key_servers: + - server_name: "matrix.org" # ====================== # Room Configuration @@ -88,44 +99,46 @@ enable_room_list_search: true allow_public_rooms_over_federation: false # ====================== -# Rate Limiting (Relaxed for Dev) +# Rate Limiting # ====================== rc_message: - per_second: 100 - burst_count: 200 - -rc_registration: per_second: 10 burst_count: 50 +rc_registration: + per_second: 1 + burst_count: 5 + rc_login: address: - per_second: 10 - burst_count: 50 + per_second: 3 + burst_count: 10 account: - per_second: 10 - burst_count: 50 + per_second: 3 + burst_count: 10 # ====================== # Logging # ====================== -log_config: "/data/localhost.log.config" - -# Inline log config — write to stdout for docker logs -# Synapse falls back to a basic console logger if the log_config file is missing, -# so we leave log_config pointing to a non-existent file intentionally. -# Override: mount a custom log config file at /data/localhost.log.config +# Synapse falls back to a basic console logger (stdout) when this file +# does not exist, which is ideal for Docker log collection. +log_config: "/data/log.config" # ====================== -# Miscellaneous +# Secrets # ====================== +# Generate with: python3 -c 'import secrets; print(secrets.token_hex(32))' report_stats: false -macaroon_secret_key: "dev-macaroon-secret-change-in-production" -form_secret: "dev-form-secret-change-in-production" +macaroon_secret_key: "REPLACE_MACAROON_SECRET" +form_secret: "REPLACE_FORM_SECRET" -# Enable presence for dev +# ====================== +# Presence & Retention +# ====================== use_presence: true -# Retention policy (optional, keep messages for 180 days in dev) retention: - enabled: false + enabled: true + default_policy: + min_lifetime: 1d + max_lifetime: 365d diff --git a/docker/openbao/config.hcl b/docker/openbao/config.hcl index d9f578c..ea42bbe 100644 --- a/docker/openbao/config.hcl +++ b/docker/openbao/config.hcl @@ -11,9 +11,6 @@ listener "tcp" { tls_disable = 1 } -# Disable memory locking for Docker compatibility -disable_mlock = true - # API address for cluster communication api_addr = "http://0.0.0.0:8200"