commit 8cb92b922663ec3095dd56b72b9bfe39146446a1 Author: wmair Date: Wed Oct 29 14:46:30 2025 +0100 Initial commit: Complete Matrix Stack with SSO and all bugfixes This is a clean, ready-to-deploy Matrix communication stack with: Features: - Matrix Synapse homeserver with PostgreSQL - Element Web client - Matrix Authentication Service (MAS) with OIDC - Authelia SSO with 2FA support - Caddy reverse proxy with automatic HTTPS - Bridges: Telegram, WhatsApp, Signal (pre-configured) Deployment Modes: - Local testing (all-in-one with self-signed certs) - Production (distributed 3-machine setup with Let's Encrypt) All Critical Bugfixes Applied: 1. Using example.test domains (not .localhost - public suffix list issue) 2. MAS assets resource enabled (fixes CSS 404 errors) 3. MAS fetch_userinfo enabled (required for Authelia claims) 4. Internal discovery URL for faster OIDC metadata fetching 5. Claims templates using preferred_username (Authelia compatible) 6. All redirect URIs configured in Authelia 7. Caddy CA certificate extraction automated 8. Correct email domains throughout Security: - All secrets generated dynamically on deployment - Cryptographically secure random generation (OpenSSL) - 4096-bit RSA keys for OIDC/JWT signing - Argon2 password hashing - No hardcoded secrets in repository Documentation: - BUGFIXES.md - Comprehensive troubleshooting guide - DEPLOYMENT_GUIDE.md - Step-by-step deployment manual - QUICK_REFERENCE.md - Command cheatsheet - README.md - Quick start guide - PRODUCTION.md - Production deployment guide Deployment: - Single command: ./deploy.sh - Fully automated configuration generation - ~10 minutes to complete setup State: Clean slate, ready for validation deployment diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95a8980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Environment and secrets +.env +*.pem +*.key +*-signing.key +authelia_private.pem +mas-signing.key + +# Generated configuration files (contain secrets) +authelia/config/configuration.yml +authelia/config/users_database.yml +authelia/config/notification.txt +authelia/config/*.backup +mas/config/config.yaml +element/config/config.json +synapse/data/homeserver.yaml +synapse/data/homeserver.yaml.bak +synapse/data/*.signing.key +synapse/data/*.log.config + +# Data directories +postgres/data/ +synapse/data/ +mas/data/ +mas/certs/ +caddy/data/ +caddy/config/ +bridges/*/config/ +bridges/*/data/ + +# Docker +docker-compose.override.yml + +# Logs +*.log + +# Backup files +*.bak +*~ +backups/ + +# Production deployment configs (contain server IPs and secrets) +caddy-server/ +authelia-server/ +caddy/Caddyfile.production + +# macOS +.DS_Store + +# IDE +.vscode/ +.idea/ +*.swp +*.swo diff --git a/BUGFIXES.md b/BUGFIXES.md new file mode 100644 index 0000000..0774752 --- /dev/null +++ b/BUGFIXES.md @@ -0,0 +1,535 @@ +# Matrix Stack Bugfixes & Lessons Learned + +This document captures all the critical issues encountered during deployment and their solutions. These are things that are **not clearly documented** in the official documentation. + +## Table of Contents +1. [Cookie Domain on Public Suffix List](#1-cookie-domain-on-public-suffix-list) +2. [MAS Missing Assets Resource](#2-mas-missing-assets-resource) +3. [MAS Not Fetching Userinfo](#3-mas-not-fetching-userinfo) +4. [SSL Certificate Trust Issues](#4-ssl-certificate-trust-issues) +5. [Authelia Redirect URI Configuration](#5-authelia-redirect-uri-configuration) +6. [Claims Template Compatibility](#6-claims-template-compatibility) +7. [MAS Database Caching](#7-mas-database-caching) +8. [MAS Discovery URL for Internal Communication](#8-mas-discovery-url-for-internal-communication) + +--- + +## 1. Cookie Domain on Public Suffix List + +### Problem +Authelia rejects cookie domains that are on the [Public Suffix List](https://publicsuffix.org/), including: +- `.localhost` +- `.local` +- `.localdev` + +### Error Message +``` +level=error msg="Configuration: session: domain config #1 (domain '.localhost'): option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list" +``` + +### Solution +Use a fake TLD that's not on the public suffix list, such as: +- `example.test` (recommended for local development) +- `example.internal` +- `example.dev` (but be aware `.dev` requires HTTPS) + +### Configuration +```yaml +# authelia/config/configuration.yml +session: + cookies: + - domain: 'example.test' # Not .example.test for subdomains! + authelia_url: 'https://authelia.example.test' +``` + +### Why Not Documented +The official Authelia docs mention the public suffix list but don't clearly list which common development TLDs are affected. + +--- + +## 2. MAS Missing Assets Resource + +### Problem +MAS serves HTML pages but CSS/JS assets return 404, causing unstyled pages. + +### Error Message +``` +WARN http.server.response GET-22 - "GET /assets/shared-CVCHz34K.css HTTP/1.1" 404 Not Found +WARN http.server.response GET-23 - "GET /assets/templates-CyDybuwN.css HTTP/1.1" 404 Not Found +``` + +### Root Cause +The MAS HTTP listener configuration is missing the `assets` resource. + +### Solution +Add the `assets` resource to the MAS configuration: + +```yaml +# mas/config/config.yaml +http: + listeners: + - name: web + resources: + - name: discovery + - name: human + - name: oauth + - name: compat + - name: graphql + playground: true + - name: assets # ← Critical: This is required! + binds: + - address: '[::]:8080' +``` + +### Verification +Test asset availability: +```bash +curl -I https://auth.example.test/assets/shared-CVCHz34K.css +# Should return: HTTP/2 200 +``` + +### Why Not Documented +The MAS documentation mentions the assets resource but doesn't emphasize it's **mandatory** for proper UI rendering. Many configuration examples omit it. + +### Assets Location +- Container path: `/usr/local/share/mas-cli/assets/` +- This path is automatically configured by MAS + +--- + +## 3. MAS Not Fetching Userinfo + +### Problem +Templates render to empty strings even though claims are configured correctly. + +### Error Message +``` +ERROR mas_handlers::upstream_oauth2::link:131 POST-102 - Template "{{ user.preferred_username }}" rendered to an empty string +``` + +### Root Cause +MAS defaults to reading claims **only from the ID token**, not from the userinfo endpoint. Authelia provides most user claims via userinfo, not in the ID token. + +### Solution +Enable userinfo fetching in MAS upstream OAuth2 provider configuration: + +```yaml +# mas/config/config.yaml +upstream_oauth2: + providers: + - id: '01HQW90Z35CMXFJWQPHC3BGZGQ' + issuer: 'https://authelia.example.test' + client_id: 'mas-client' + client_secret: 'your-secret' + scope: 'openid profile email offline_access' + token_endpoint_auth_method: 'client_secret_basic' + fetch_userinfo: true # ← Critical: Must be enabled! + claims_imports: + localpart: + action: force + template: '{{ user.preferred_username }}' +``` + +### Why Not Documented +The MAS documentation doesn't clearly state that `fetch_userinfo` defaults to `false` and that most OIDC providers (including Authelia) serve user claims via userinfo, not in the ID token. + +### Testing +Check the database to verify the setting: +```sql +SELECT upstream_oauth_provider_id, fetch_userinfo FROM upstream_oauth_providers; +``` +Should show `t` (true). + +--- + +## 4. SSL Certificate Trust Issues + +### Problem +MAS cannot fetch Authelia's OIDC metadata when using self-signed certificates behind Caddy. + +### Error Message +``` +ERROR mas_handlers::upstream_oauth2::cache - Failed to fetch provider metadata issuer=https://authelia.example.test error=invalid peer certificate: UnknownIssuer +``` + +### Root Cause +MAS doesn't trust Caddy's self-signed CA certificate. + +### Solution (Local Development) +1. Extract Caddy's CA certificate: +```bash +docker compose exec caddy cat /data/caddy/pki/authorities/local/root.crt > mas/certs/caddy-ca.crt +``` + +2. Mount the certificate in the MAS container: +```yaml +# docker-compose.local.yml +services: + mas: + environment: + SSL_CERT_FILE: /certs/caddy-ca.crt + volumes: + - ./mas/certs:/certs:ro +``` + +3. Restart MAS to apply: +```bash +docker compose restart mas +``` + +### Solution (Production with Let's Encrypt) +Not needed - production deployments use Let's Encrypt certificates which are already trusted. + +### Alternative (Local Development) +Use internal HTTP endpoint with `discovery_url`: +```yaml +upstream_oauth2: + providers: + - issuer: 'https://authelia.example.test' + discovery_url: 'http://authelia:9091/.well-known/openid-configuration' +``` +**Note:** This only works if the Authelia OIDC issuer accepts HTTP for discovery. + +### Why Not Documented +The MAS documentation doesn't mention the `SSL_CERT_FILE` environment variable or how to handle self-signed certificates in development. + +--- + +## 5. Authelia Redirect URI Configuration + +### Problem +OAuth flow fails with redirect URI mismatch error. + +### Error Message +``` +Fehler: invalid_request +Beschreibung: The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered 'redirect_uris'. +``` + +### Root Cause +Authelia requires the exact redirect URI to be pre-registered, but MAS generates different callback URIs depending on context: +- Standard: `https://auth.example.test/callback` +- OAuth2: `https://auth.example.test/oauth2/callback` +- Upstream provider: `https://auth.example.test/upstream/callback/{provider_id}` + +The provider ID in the database may differ from the config file ID. + +### Solution +Add ALL possible redirect URIs to Authelia client configuration: + +```yaml +# authelia/config/configuration.yml +identity_providers: + oidc: + clients: + - client_id: 'mas-client' + redirect_uris: + - 'https://auth.example.test/callback' + - 'https://auth.example.test/oauth2/callback' + - 'https://auth.example.test/upstream/callback/01HQW90Z35CMXFJWQPHC3BGZGQ' # Config file ID + - 'https://auth.example.test/upstream/callback/018df890-7c65-653a-f972-f68b06b87e17' # Database ID +``` + +### Finding the Provider ID +```sql +-- Connect to MAS database +docker compose exec postgres psql -U synapse -d mas + +-- Get provider ID +SELECT upstream_oauth_provider_id FROM upstream_oauth_providers; +``` + +### Why Not Documented +Neither MAS nor Authelia documentation clearly explains that MAS may generate different provider IDs between config and database, or that the upstream callback pattern requires the provider ID. + +--- + +## 6. Claims Template Compatibility + +### Problem +Claims templates render to empty strings when using Authelia as the upstream provider. + +### Root Cause +Authelia provides different claims than expected. Testing revealed: +- ❌ `{{ user.name }}` - Not provided by Authelia +- ✅ `{{ user.preferred_username }}` - Works (contains username) +- ✅ `{{ user.email }}` - Works + +### Solution +Use `preferred_username` for localpart and displayname: + +```yaml +# mas/config/config.yaml +upstream_oauth2: + providers: + - claims_imports: + localpart: + action: force + template: '{{ user.preferred_username }}' # ← Use this + displayname: + action: suggest + template: '{{ user.preferred_username }}' # ← Not {{ user.name }} + email: + action: force + template: '{{ user.email }}' + set_email_verification: always +``` + +### Testing Claims +To discover available claims, temporarily enable debug logging in MAS or check Authelia's userinfo endpoint: +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" https://authelia.example.test/api/oidc/userinfo +``` + +### Why Not Documented +The MAS documentation uses `{{ user.name }}` in examples, but this claim is not standardized in OIDC and many providers (including Authelia) don't provide it. + +--- + +## 7. MAS Database Caching + +### Problem +After updating MAS configuration, changes to upstream OAuth2 providers don't take effect even after restart. + +### Root Cause +MAS caches provider configuration in PostgreSQL. Changes to `config.yaml` are only synced when: +- MAS starts for the first time +- The provider doesn't exist in the database +- Explicit sync is forced + +### Solution +Delete the provider from the database to force a re-sync: + +```sql +-- Connect to MAS database +docker compose exec postgres psql -U synapse -d mas + +-- Find provider ID +SELECT upstream_oauth_provider_id FROM upstream_oauth_providers; + +-- Delete provider (CASCADE deletes related records) +DELETE FROM upstream_oauth_authorization_sessions WHERE upstream_oauth_provider_id = 'provider-id-here'; +DELETE FROM upstream_oauth_links WHERE upstream_oauth_provider_id = 'provider-id-here'; +DELETE FROM upstream_oauth_providers WHERE upstream_oauth_provider_id = 'provider-id-here'; + +-- Exit and restart MAS +\q +docker compose restart mas +``` + +### Verification +Check that the provider was re-created: +```bash +docker compose logs mas | grep "Adding provider" +# Should show: INFO mas_cli::sync:198 Adding provider provider.id=... +``` + +### Why Not Documented +The MAS documentation doesn't clearly explain that provider configuration is cached in the database and must be manually deleted to apply config changes. + +### Alternative +Use MAS CLI to force sync (if available): +```bash +docker compose exec mas mas-cli config sync +``` + +--- + +## 8. MAS Discovery URL for Internal Communication + +### Problem +When MAS and Authelia are in the same Docker network, MAS tries to fetch OIDC metadata over HTTPS through the external reverse proxy, adding unnecessary latency and SSL complexity. + +### Solution +Use `discovery_url` to specify an internal HTTP endpoint: + +```yaml +# mas/config/config.yaml +upstream_oauth2: + providers: + - id: '01HQW90Z35CMXFJWQPHC3BGZGQ' + issuer: 'https://authelia.example.test' # Public issuer URL + discovery_url: 'http://authelia:9091/.well-known/openid-configuration' # Internal discovery + client_id: 'mas-client' +``` + +### Benefits +- Faster metadata fetching (internal network) +- No SSL certificate trust issues +- Reduces external traffic through reverse proxy + +### Requirements +- Authelia must be accessible via Docker network (`authelia:9091`) +- The `issuer` claim in the discovery document must match the public `issuer` URL + +### Why Not Documented +The MAS documentation mentions `discovery_url` but doesn't emphasize its use for internal communication or bypassing SSL issues in development. + +--- + +## Configuration Changes Checklist + +When making changes to the stack, follow this checklist to avoid common issues: + +### Changing Domains +- [ ] Update Authelia cookie domain (no leading dot!) +- [ ] Update all service URLs in MAS config +- [ ] Update Authelia OIDC client redirect URIs +- [ ] Update Element Web config.json +- [ ] Update Synapse homeserver.yaml (MSC3861 issuer) +- [ ] Update Caddyfile domains +- [ ] Update /etc/hosts (local) or DNS records (production) +- [ ] Restart all services + +### Changing OAuth Configuration +- [ ] Update MAS config.yaml +- [ ] Delete provider from MAS database +- [ ] Restart MAS to re-sync +- [ ] Verify provider was re-created in logs +- [ ] Test authentication flow + +### Updating Claims Templates +- [ ] Ensure `fetch_userinfo: true` is set +- [ ] Use `preferred_username` not `name` for Authelia +- [ ] Delete provider from MAS database +- [ ] Restart MAS +- [ ] Test registration/login flow + +### Debugging TLS Issues +- [ ] Check if MAS has Caddy CA certificate mounted +- [ ] Verify `SSL_CERT_FILE` environment variable +- [ ] Consider using `discovery_url` with HTTP for internal calls +- [ ] Check Caddy logs for SSL errors + +--- + +## Testing Checklist + +After deployment, verify each component: + +### 1. Basic Connectivity +```bash +# Test all HTTPS endpoints +curl -I https://matrix.example.test +curl -I https://element.example.test +curl -I https://auth.example.test +curl -I https://authelia.example.test + +# All should return HTTP 200 or appropriate redirect +``` + +### 2. OIDC Discovery +```bash +curl https://auth.example.test/.well-known/openid-configuration | jq +# Should return valid OIDC discovery document +``` + +### 3. MAS Assets +```bash +curl -I https://auth.example.test/assets/shared-CVCHz34K.css +# Should return HTTP 200 +``` + +### 4. Matrix Well-Known +```bash +curl https://matrix.example.test/.well-known/matrix/client | jq +# Should return homeserver and authentication issuer +``` + +### 5. Full Authentication Flow +1. Navigate to Element: `https://element.example.test` +2. Click "Sign In" +3. Verify redirect to MAS: `https://auth.example.test` +4. Verify redirect to Authelia: `https://authelia.example.test` +5. Log in with credentials +6. Complete 2FA setup +7. Verify redirect back to Element +8. Verify successful login + +### 6. Database Verification +```bash +# Check MAS provider configuration +docker compose exec postgres psql -U synapse -d mas -c \ + "SELECT upstream_oauth_provider_id, fetch_userinfo, claims_imports FROM upstream_oauth_providers;" + +# Verify fetch_userinfo is 't' (true) +# Verify claims_imports uses preferred_username +``` + +--- + +## Common Pitfalls + +### 1. Forgetting to Add Assets Resource +**Symptom:** MAS pages load but have no styling +**Fix:** Add `- name: assets` to MAS HTTP listener resources + +### 2. Using `.localhost` Domain +**Symptom:** Authelia fails to start with cookie domain error +**Fix:** Use `example.test` or another non-public-suffix domain + +### 3. Not Enabling Userinfo Fetching +**Symptom:** Template renders to empty string error +**Fix:** Add `fetch_userinfo: true` to MAS upstream provider + +### 4. Not Restarting After Config Changes +**Symptom:** Changes don't take effect +**Fix:** Always restart the affected service: `docker compose restart service-name` + +### 5. Forgetting to Delete Cached Provider +**Symptom:** MAS still uses old configuration after restart +**Fix:** Delete provider from database before restarting + +### 6. Missing Redirect URIs in Authelia +**Symptom:** OAuth flow fails with invalid_request +**Fix:** Add all possible redirect URI patterns to Authelia client config + +### 7. Using `{{ user.name }}` Template +**Symptom:** Template renders to empty string +**Fix:** Use `{{ user.preferred_username }}` instead for Authelia + +### 8. SSL Certificate Trust Issues +**Symptom:** MAS can't fetch Authelia metadata +**Fix:** Mount Caddy CA certificate or use internal discovery_url + +--- + +## Version-Specific Notes + +### MAS v1.5.0 +- Confirmed working with all fixes applied +- Assets must be explicitly enabled in listener resources +- `fetch_userinfo` defaults to `false` + +### Authelia v4.39.13 +- Rejects public suffix list domains for cookies +- Provides `preferred_username` claim, not `name` +- Requires exact redirect URI matches +- `jwt_secret` is deprecated, use `identity_validation.reset_password.jwt_secret` + +### Synapse (latest) +- MSC3861 (OAuth delegation) is marked as experimental +- Works reliably with MAS when properly configured + +--- + +## Additional Resources + +- [Public Suffix List](https://publicsuffix.org/) +- [OIDC Core Specification](https://openid.net/specs/openid-connect-core-1_0.html) +- [MAS Configuration Reference](https://element-hq.github.io/matrix-authentication-service/) +- [Authelia Configuration](https://www.authelia.com/configuration/) + +--- + +## Contributing to This Document + +If you encounter additional issues not covered here: +1. Document the problem clearly +2. Include error messages +3. Explain the root cause +4. Provide the solution +5. Note why it's not in official docs +6. Add to the appropriate section + +This helps future deployments avoid the same pitfalls! diff --git a/CHECKLIST.md b/CHECKLIST.md new file mode 100644 index 0000000..1efabeb --- /dev/null +++ b/CHECKLIST.md @@ -0,0 +1,86 @@ +# Setup Checklist + +Use this checklist to track your setup progress. + +## Initial Setup + +- [ ] Edit `.env` file with secure passwords and secrets +- [ ] Generate secrets: `openssl rand -base64 32` (do this 4 times for different secrets) +- [ ] Set your domain name in `.env` (or keep `matrix.localhost` for local testing) + +## Synapse Setup + +- [ ] Run `./setup-synapse.sh` to generate Synapse config +- [ ] Edit `./synapse/data/homeserver.yaml`: + - [ ] Configure PostgreSQL database connection + - [ ] Set `enable_registration` as desired + - [ ] Add MAS experimental features (msc3861) + - [ ] Set `server_name` to your domain + +## Authelia Setup + +- [ ] Generate password hash: `docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'` +- [ ] Update `./authelia/config/users_database.yml` with password hash +- [ ] Generate RSA key: `openssl genrsa -out authelia_private.pem 4096` +- [ ] Copy RSA key to `./authelia/config/configuration.yml` +- [ ] Generate client secret: `docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986` +- [ ] Update client secret in both Authelia and MAS configs + +## MAS Setup + +- [ ] Generate MAS signing key: `openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt > mas-signing.key` +- [ ] Copy signing key to `./mas/config/config.yaml` +- [ ] Update database password in `./mas/config/config.yaml` +- [ ] Update client secret to match Authelia + +## Start Services + +- [ ] Start PostgreSQL: `docker compose up -d postgres` +- [ ] Wait for PostgreSQL to be ready (check logs) +- [ ] Start all services: `docker compose up -d` +- [ ] Check all services are running: `docker compose ps` +- [ ] Check logs for errors: `docker compose logs` + +## Test Basic Functionality + +- [ ] Access Element Web at http://localhost:8080 +- [ ] Try to sign in (should redirect through MAS → Authelia) +- [ ] Complete 2FA setup in Authelia +- [ ] Successfully log into Element +- [ ] Create a room +- [ ] Send a test message + +## Bridge Setup (Optional) + +- [ ] Run `./setup-bridges.sh` to generate bridge configs +- [ ] Edit each bridge config at `./bridges/{bridge}/config/config.yaml` +- [ ] Copy registration files to synapse data directory +- [ ] Add registration files to `homeserver.yaml` +- [ ] Restart Synapse: `docker compose restart synapse` + +### Test Bridges + +- [ ] **Telegram**: Start chat with `@telegrambot:matrix.localhost`, send `login` +- [ ] **WhatsApp**: Start chat with `@whatsappbot:matrix.localhost`, send `login` +- [ ] **Signal**: Start chat with `@signalbot:matrix.localhost`, send `link` + +## Production Readiness (When Moving to Production) + +- [ ] Set up reverse proxy (Caddy/nginx) with HTTPS +- [ ] Configure real domain names +- [ ] Update all URLs to use HTTPS and real domains +- [ ] Set up email server for Authelia notifications +- [ ] Configure firewall rules +- [ ] Set up automated backups +- [ ] Review security settings in all configs +- [ ] Test from external network +- [ ] Set up monitoring/alerting +- [ ] Document your specific configuration + +## Notes + +Write any issues or notes here: + +``` +[Your notes] +``` diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md new file mode 100644 index 0000000..3f95015 --- /dev/null +++ b/DEPLOYMENT_GUIDE.md @@ -0,0 +1,661 @@ +# Complete Matrix Stack Deployment Guide + +This guide provides step-by-step instructions for deploying a fully functional, secure Matrix communication stack with SSO authentication. + +## Table of Contents +1. [Overview](#overview) +2. [Prerequisites](#prerequisites) +3. [Deployment Modes](#deployment-modes) +4. [Local Testing Deployment](#local-testing-deployment) +5. [Production Deployment](#production-deployment) +6. [Post-Deployment Verification](#post-deployment-verification) +7. [Common Issues & Solutions](#common-issues--solutions) +8. [Maintenance](#maintenance) + +--- + +## Overview + +This deployment provides a complete Matrix communication stack with: + +- **Matrix Synapse** - Homeserver for federation +- **Element Web** - Modern web client +- **Matrix Authentication Service (MAS)** - OIDC-based authentication +- **Authelia** - SSO provider with 2FA support +- **Caddy** - Automatic HTTPS with self-signed certs (local) or Let's Encrypt (production) +- **PostgreSQL** - Shared database for all services +- **Redis** - Session storage for Authelia +- **Bridges** - Telegram, WhatsApp, and Signal integration (optional) + +**Key Features:** +- ✅ Full SSO authentication flow with 2FA +- ✅ Automatic HTTPS configuration +- ✅ All critical bugfixes pre-applied +- ✅ Single-command deployment +- ✅ Production-ready architecture + +--- + +## Prerequisites + +### System Requirements + +**Minimum (Local Testing):** +- 4GB RAM +- 2 CPU cores +- 20GB disk space +- Docker 20.10+ with Docker Compose +- Linux, macOS, or WSL2 + +**Recommended (Production):** +- 8GB+ RAM +- 4+ CPU cores +- 50GB+ SSD storage +- 3 separate machines (Caddy, Authelia, Matrix) + +### Software Requirements + +```bash +# Check Docker +docker --version +# Should be 20.10.0 or higher + +# Check Docker Compose +docker compose version +# Should be v2.0.0 or higher + +# Check OpenSSL (for key generation) +openssl version +``` + +### Network Requirements + +**Local Testing:** +- Ability to edit `/etc/hosts` +- Ports 80, 443, 2019 available + +**Production:** +- Valid domain name with DNS access +- SSL/TLS certificates (Let's Encrypt recommended) +- Firewall configuration capability + +--- + +## Deployment Modes + +### Mode 1: Local Testing (All-in-One) + +**Use Case:** Development, testing, proof-of-concept + +**Architecture:** +``` +Single Machine +├── Caddy (HTTPS termination) +├── Authelia (SSO) +├── MAS (OIDC) +├── Synapse (Matrix homeserver) +├── Element (Web client) +├── PostgreSQL (Database) +└── Redis (Sessions) +``` + +**Domains:** `*.example.test` (requires `/etc/hosts` entries) + +**Security:** Self-signed certificates (browser warnings expected) + +**Deployment Time:** ~10 minutes + +--- + +### Mode 2: Production (Distributed) + +**Use Case:** Production deployments, high availability + +**Architecture:** +``` +Machine 1 (Public-facing) +└── Caddy (SSL termination, Let's Encrypt) + +Machine 2 (Internal) +└── Authelia (SSO + PostgreSQL + Redis) + +Machine 3 (Internal) +├── Synapse (Matrix homeserver) +├── MAS (OIDC) +├── Element (Web client) +├── Bridges (optional) +└── PostgreSQL (dedicated) +``` + +**Domains:** Real domains with DNS (e.g., `matrix.example.com`) + +**Security:** Let's Encrypt certificates, firewall restrictions + +**Deployment Time:** ~30 minutes + +--- + +## Local Testing Deployment + +### Step 1: Update /etc/hosts + +```bash +sudo nano /etc/hosts +``` + +Add these lines: +``` +127.0.0.1 matrix.example.test +127.0.0.1 element.example.test +127.0.0.1 auth.example.test +127.0.0.1 authelia.example.test +``` + +**Important:** We use `example.test` (not `.localhost`) because `.localhost` is on the [Public Suffix List](https://publicsuffix.org/) and Authelia rejects it for cookie domains. See [BUGFIXES.md](BUGFIXES.md#1-cookie-domain-on-public-suffix-list) for details. + +### Step 2: Clone/Extract the Repository + +```bash +cd ~/Documents # or your preferred location +# If you have the repo, cd into it +cd matrix-2 +``` + +### Step 3: Run the Deployment Script + +```bash +chmod +x deploy.sh +./deploy.sh +``` + +The script will: +1. ✅ Ask for deployment mode (select **1** for local) +2. ✅ Generate all secure secrets automatically +3. ✅ Create RSA keys for Authelia and MAS +4. ✅ Generate a random admin password +5. ✅ Configure all services +6. ✅ Start the Docker stack +7. ✅ Extract Caddy CA certificate for MAS +8. ✅ Display access URLs and credentials + +**Expected Output:** +``` +╔════════════════════════════════════════════════════════════╗ +║ Matrix Stack Automated Deployment Script ║ +║ Interactive Setup ║ +╚════════════════════════════════════════════════════════════╝ + +Select Deployment Type: + 1) Local Testing (All-in-One) + 2) Production (Distributed) + +Enter choice [1 or 2]: 1 +✓ Selected: Local Testing Mode + +[... deployment progress ...] + +═══════════════════════════════════════════════════════════ +Deployment Complete! +═══════════════════════════════════════════════════════════ + +Access Points (HTTPS with self-signed certificates): + • Element Web: https://element.example.test + • Matrix API: https://matrix.example.test + • MAS (Auth): https://auth.example.test + • Authelia: https://authelia.example.test + +Authelia Login Credentials: + • Username: admin + • Password: [generated-password-here] + ⚠ SAVE THIS PASSWORD - you'll need it to log in! +``` + +**⚠️ CRITICAL:** Save the admin password displayed! You'll need it to log in. + +### Step 4: Verify Services + +```bash +# Check all containers are running +docker compose -f docker-compose.local.yml ps + +# Expected output: +# All services should show "Up" or "Up (healthy)" +``` + +### Step 5: Access Element Web + +1. Open browser: **https://element.example.test** +2. Accept the self-signed certificate warning: + - Click "Advanced" + - Click "Proceed to element.example.test" +3. Click **"Sign In"** +4. You'll be redirected through: + - MAS (auth.example.test) → + - Authelia (authelia.example.test) +5. Log in with: + - Username: `admin` + - Password: (from deployment script output) +6. Complete 2FA setup if required (use Google Authenticator or similar) +7. Confirm account linking +8. You should be redirected back to Element, logged in! + +### Step 6: Create Your First Room + +1. In Element, click the **"+" next to "Rooms"** +2. Click **"Create new room"** +3. Configure: + - Room name: "General" + - Make it public or private + - Enable encryption (recommended) +4. Click **"Create"** +5. Start chatting! + +--- + +## Production Deployment + +For production deployment with Let's Encrypt certificates and distributed architecture, see [PRODUCTION.md](PRODUCTION.md). + +**Key Differences:** +- Real domains with DNS configuration +- Let's Encrypt for SSL certificates +- Services split across 3 machines for security +- Firewall rules to restrict access +- 2FA mandatory (not optional) +- Backup strategy required + +--- + +## Post-Deployment Verification + +### Automated Tests + +Run the validation script: + +```bash +./validate-setup.sh +``` + +This checks: +- ✅ All services running +- ✅ Database connectivity +- ✅ OIDC discovery endpoints +- ✅ SSL certificates +- ✅ DNS resolution (local testing) + +### Manual Verification + +#### 1. Check Service Health + +```bash +docker compose -f docker-compose.local.yml ps +``` + +All services should show status `Up (healthy)` or `Up`. + +#### 2. Test HTTPS Endpoints + +```bash +# Matrix homeserver +curl -k https://matrix.example.test/_matrix/client/versions + +# MAS OIDC discovery +curl -k https://auth.example.test/.well-known/openid-configuration | jq + +# Authelia OIDC discovery +curl -k https://authelia.example.test/.well-known/openid-configuration | jq + +# Element Web +curl -k https://element.example.test +``` + +All should return valid responses (not 404 or 500 errors). + +#### 3. Test Matrix Well-Known + +```bash +curl -k https://matrix.example.test/.well-known/matrix/client | jq +``` + +Should return: +```json +{ + "m.homeserver": { + "base_url": "https://matrix.example.test" + }, + "m.authentication": { + "issuer": "https://auth.example.test/" + } +} +``` + +#### 4. Test MAS Assets (CSS) + +```bash +curl -I -k https://auth.example.test/assets/shared-CVCHz34K.css +``` + +Should return `HTTP/2 200` (not 404). + +#### 5. Test Authentication Flow + +Follow "Step 5: Access Element Web" above and complete the full SSO flow. + +#### 6. Test Database Connectivity + +```bash +# Connect to MAS database +docker compose -f docker-compose.local.yml exec postgres psql -U synapse -d mas + +# Check provider configuration +SELECT upstream_oauth_provider_id, fetch_userinfo, issuer FROM upstream_oauth_providers; + +# Should show: +# - fetch_userinfo: t (true) +# - issuer: https://authelia.example.test + +# Exit +\q +``` + +--- + +## Common Issues & Solutions + +See [BUGFIXES.md](BUGFIXES.md) for comprehensive troubleshooting. Here are the most common issues: + +### 1. CSS Not Loading on MAS Pages + +**Symptom:** MAS pages load but have no styling (plain HTML) + +**Cause:** Missing `assets` resource in MAS configuration + +**Solution:** +```bash +# Check MAS config +grep -A 10 "resources:" mas/config/config.yaml + +# Should include: +# - name: assets + +# If missing, add it and restart: +docker compose -f docker-compose.local.yml restart mas +``` + +See: [BUGFIXES.md#2-mas-missing-assets-resource](BUGFIXES.md#2-mas-missing-assets-resource) + +--- + +### 2. Login Fails with "Template rendered to empty string" + +**Symptom:** Error in MAS logs: `Template "{{ user.preferred_username }}" rendered to an empty string` + +**Cause:** MAS not fetching userinfo from Authelia (fetch_userinfo: false) + +**Solution:** +```bash +# Check database +docker compose exec postgres psql -U synapse -d mas -c \ + "SELECT fetch_userinfo FROM upstream_oauth_providers;" + +# If shows 'f' (false), update config: +# Edit mas/config/config.yaml and add under provider: +# fetch_userinfo: true + +# Delete provider from database to force re-sync: +docker compose exec postgres psql -U synapse -d mas +DELETE FROM upstream_oauth_authorization_sessions WHERE upstream_oauth_provider_id = (SELECT upstream_oauth_provider_id FROM upstream_oauth_providers LIMIT 1); +DELETE FROM upstream_oauth_links WHERE upstream_oauth_provider_id = (SELECT upstream_oauth_provider_id FROM upstream_oauth_providers LIMIT 1); +DELETE FROM upstream_oauth_providers; +\q + +# Restart MAS +docker compose -f docker-compose.local.yml restart mas +``` + +See: [BUGFIXES.md#3-mas-not-fetching-userinfo](BUGFIXES.md#3-mas-not-fetching-userinfo) + +--- + +### 3. Redirect URI Mismatch Error + +**Symptom:** Authelia shows "invalid_request: redirect_uri does not match" + +**Cause:** Missing upstream callback URI in Authelia client configuration + +**Solution:** +```bash +# Edit authelia/config/configuration.yml +# Under clients -> mas-client -> redirect_uris, ensure you have: +# - 'https://auth.example.test/callback' +# - 'https://auth.example.test/oauth2/callback' +# - 'https://auth.example.test/upstream/callback/01HQW90Z35CMXFJWQPHC3BGZGQ' + +# Restart Authelia +docker compose -f docker-compose.local.yml restart authelia +``` + +See: [BUGFIXES.md#5-authelia-redirect-uri-configuration](BUGFIXES.md#5-authelia-redirect-uri-configuration) + +--- + +### 4. Authelia Cookie Domain Error + +**Symptom:** Authelia fails to start with "domain is part of the special public suffix list" + +**Cause:** Using `.localhost`, `.local`, or `.localdev` domains + +**Solution:** +Use `example.test` instead: +```bash +# Edit authelia/config/configuration.yml +# Change: +# session: +# cookies: +# - domain: 'example.test' # Not .localhost! + +# Also update all URLs to use example.test +``` + +See: [BUGFIXES.md#1-cookie-domain-on-public-suffix-list](BUGFIXES.md#1-cookie-domain-on-public-suffix-list) + +--- + +### 5. SSL Certificate Trust Issues + +**Symptom:** MAS logs show "invalid peer certificate: UnknownIssuer" + +**Cause:** MAS doesn't trust Caddy's self-signed CA + +**Solution:** +```bash +# Extract Caddy CA certificate +docker compose -f docker-compose.local.yml exec caddy cat \ + /data/caddy/pki/authorities/local/root.crt > mas/certs/caddy-ca.crt + +# Restart MAS +docker compose -f docker-compose.local.yml restart mas +``` + +The deploy script should do this automatically, but if you restart Caddy, you may need to re-extract the certificate. + +See: [BUGFIXES.md#4-ssl-certificate-trust-issues](BUGFIXES.md#4-ssl-certificate-trust-issues) + +--- + +### 6. Services Not Starting + +**Symptom:** `docker compose ps` shows services as "Restarting" or "Exited" + +**Diagnosis:** +```bash +# Check logs for the failing service +docker compose -f docker-compose.local.yml logs service-name + +# Common services to check: +docker compose -f docker-compose.local.yml logs postgres +docker compose -f docker-compose.local.yml logs synapse +docker compose -f docker-compose.local.yml logs mas +docker compose -f docker-compose.local.yml logs authelia +``` + +**Common Causes:** +- PostgreSQL not ready before other services start +- Configuration syntax errors +- Port conflicts +- Insufficient memory + +**Solutions:** +- Wait 30-60 seconds for services to stabilize +- Check for YAML syntax errors in configs +- Ensure no other services using ports 80/443 +- Increase Docker memory limit if needed + +--- + +## Maintenance + +### Viewing Logs + +```bash +# All services +docker compose -f docker-compose.local.yml logs -f + +# Specific service +docker compose -f docker-compose.local.yml logs -f mas + +# Last 100 lines +docker compose -f docker-compose.local.yml logs --tail=100 synapse +``` + +### Restarting Services + +```bash +# Restart a single service +docker compose -f docker-compose.local.yml restart mas + +# Restart all services +docker compose -f docker-compose.local.yml restart + +# Stop all services +docker compose -f docker-compose.local.yml down + +# Start all services +docker compose -f docker-compose.local.yml up -d +``` + +### Updating Configurations + +After changing any configuration file: + +```bash +# Restart the affected service +docker compose -f docker-compose.local.yml restart service-name +``` + +**Note:** For MAS upstream provider changes, you must also delete the provider from the database (see issue #2 above). + +### Backing Up Data + +```bash +# Create backup directory +mkdir -p backups/$(date +%Y%m%d) + +# Backup databases +docker compose -f docker-compose.local.yml exec -T postgres pg_dumpall -U synapse \ + > backups/$(date +%Y%m%d)/databases.sql + +# Backup configuration +cp -r authelia/config backups/$(date +%Y%m%d)/authelia-config +cp -r mas/config backups/$(date +%Y%m%d)/mas-config +cp synapse/data/homeserver.yaml backups/$(date +%Y%m%d)/ + +# Backup media (can be large!) +tar -czf backups/$(date +%Y%m%d)/synapse-media.tar.gz synapse/data/media_store + +# Backup signing keys (CRITICAL - keep secure!) +cp mas/config/config.yaml backups/$(date +%Y%m%d)/ # Contains MAS signing key +cp authelia_private.pem backups/$(date +%Y%m%d)/ # Authelia RSA key +cp mas-signing.key backups/$(date +%Y%m%d)/ # MAS signing key +``` + +**Security:** Store backups in a secure location with encryption! + +### Adding Users + +To add additional Authelia users: + +```bash +# Generate password hash +docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 \ + --password 'newuserpassword' + +# Edit authelia/config/users_database.yml +nano authelia/config/users_database.yml + +# Add under 'users:': +# newuser: +# displayname: "New User" +# password: "$argon2id$..." # Paste hash from above +# email: newuser@matrix.example.test +# groups: +# - users + +# Restart Authelia +docker compose -f docker-compose.local.yml restart authelia +``` + +### Updating Containers + +```bash +# Pull latest images +docker compose -f docker-compose.local.yml pull + +# Recreate containers with new images +docker compose -f docker-compose.local.yml up -d + +# Remove old images +docker image prune -a +``` + +**Important:** Always backup before updating! + +--- + +## Next Steps + +1. ✅ **Test the full SSO flow** - Ensure authentication works +2. ✅ **Set up bridges** - Connect Telegram, WhatsApp, Signal (see [setup-bridges.sh](setup-bridges.sh)) +3. ✅ **Create more users** - Add additional Authelia accounts +4. ✅ **Configure 2FA** - Mandate two-factor authentication +5. ✅ **Review security** - Read [BUGFIXES.md](BUGFIXES.md) for important notes +6. ✅ **Plan production migration** - See [PRODUCTION.md](PRODUCTION.md) +7. ✅ **Set up backups** - Schedule regular database and media backups + +--- + +## Support Resources + +- **[BUGFIXES.md](BUGFIXES.md)** - Comprehensive troubleshooting guide +- **[PRODUCTION.md](PRODUCTION.md)** - Production deployment instructions +- **[README.md](README.md)** - Quick start guide +- **Matrix Docs:** https://matrix.org/docs/ +- **Synapse Docs:** https://matrix-org.github.io/synapse/ +- **MAS Docs:** https://element-hq.github.io/matrix-authentication-service/ +- **Authelia Docs:** https://www.authelia.com/ + +--- + +## Success Criteria + +Your deployment is successful when: + +- ✅ All services show "Up (healthy)" status +- ✅ Element Web loads with proper styling +- ✅ MAS pages load with CSS (not plain HTML) +- ✅ Authelia login works +- ✅ Full SSO flow completes successfully +- ✅ You can send messages in Element +- ✅ No errors in service logs + +**Congratulations!** You now have a fully functional Matrix communication stack with enterprise-grade SSO authentication! 🎉 diff --git a/DEPLOYMENT_SUMMARY.md b/DEPLOYMENT_SUMMARY.md new file mode 100644 index 0000000..12228e3 --- /dev/null +++ b/DEPLOYMENT_SUMMARY.md @@ -0,0 +1,237 @@ +# Deployment Summary - Matrix Stack with HTTPS & Authelia SSO + +## What Has Been Done + +### ✅ Infrastructure Setup +1. **Docker Compose Configuration** + - All services configured with proper networking + - Port mappings updated (services behind Caddy use `expose` instead of `ports`) + - Health checks configured for all critical services + - Service dependencies properly defined + +2. **Caddy Reverse Proxy** + - Added to docker-compose.yml + - Caddyfile created with comprehensive routing: + - HTTPS termination with self-signed certificates + - Proper CORS headers for Matrix API + - Well-known endpoints for client discovery + - MAS compat endpoints routing + - OIDC discovery endpoints + - Configured for local testing with *.localhost domains + +### ✅ Configuration Management +1. **Automated Deployment Script (deploy.sh)** + - Generates ALL secrets from scratch using best practices: + - Uses `openssl rand -hex 32` for MAS encryption key (hex format required) + - Uses `openssl rand -base64 32` for other secrets + - Random admin password generation (no hardcoded passwords) + - Generates RSA keys: + - 4096-bit RSA key for Authelia OIDC + - 4096-bit RSA key for MAS signing + - Auto-hashes passwords: + - Authelia client secret (PBKDF2-SHA512) + - Admin user password (Argon2id) + - Creates all configuration files with proper HTTPS URLs + - Starts services in correct order + - Provides clear output with credentials and URLs + +2. **Authelia Configuration** + - Session cookies configured for `localhost` domain + - HTTPS URLs: `https://authelia.localhost` + - Redirect URI: `https://element.localhost` + - OIDC client configured for MAS integration + - Proper scopes: `openid`, `profile`, `email`, `offline_access` + - Redirect URIs updated for HTTPS + - 2FA enforcement via `authorization_policy: 'two_factor'` + +3. **MAS Configuration** + - Public base and issuer: `https://auth.localhost/` + - Upstream OAuth2 provider enabled with Authelia + - Issuer: `https://authelia.localhost` + - Password authentication disabled (SSO only) + - Proper client redirect URIs with HTTPS + - Branding URIs updated + +4. **Synapse Configuration** + - MSC3861 (MAS integration) enabled + - Issuer: `https://auth.localhost/` + - PostgreSQL database backend + - Registration enabled but controlled through MAS/Authelia + +### ✅ Security Improvements +- All secrets generated cryptographically secure +- No hardcoded passwords +- Proper secret lengths (32 bytes minimum) +- RSA keys at 4096 bits +- Password hashing with Argon2id (best practice) +- Client secrets hashed with PBKDF2-SHA512 +- HTTPS everywhere (no HTTP except Caddy admin on localhost) + +### ✅ Documentation +- **README.md** - Updated with HTTPS setup instructions +- **Caddyfile** - Well-commented configuration +- **deploy.sh** - Extensive comments and status output + +## Current Architecture + +``` +User Browser (HTTPS) + │ + ├─→ https://element.localhost ──→ Caddy ──→ Element Web :80 + ├─→ https://matrix.localhost ───→ Caddy ──→ Synapse :8008 + ├─→ https://auth.localhost ─────→ Caddy ──→ MAS :8080 + └─→ https://authelia.localhost ─→ Caddy ──→ Authelia :9091 + +Authentication Flow: +1. User clicks "Sign In" on Element +2. Element redirects to Matrix API +3. Matrix redirects to MAS (https://auth.localhost) +4. MAS redirects to Authelia (https://authelia.localhost) +5. User logs in with Authelia credentials +6. User completes 2FA setup +7. Authelia returns to MAS +8. MAS creates Matrix account and returns to Element +9. User is logged in +``` + +## What Needs to Be Done + +### 🔲 Prerequisites (Before Running) +1. **Update /etc/hosts** - Add these lines: + ``` + 127.0.0.1 matrix.localhost + 127.0.0.1 element.localhost + 127.0.0.1 auth.localhost + 127.0.0.1 authelia.localhost + ``` + +2. **Ensure Docker is running**: + ```bash + sudo systemctl status docker + ``` + +3. **Clean up any old data** (if needed): + ```bash + sudo rm -rf synapse/data/* authelia/config/* mas/config/* postgres/data/* + sudo rm -f .env authelia_private.pem mas-signing.key + ``` + +### 🔲 Deployment +```bash +chmod +x deploy.sh +./deploy.sh +``` + +**SAVE THE OUTPUT!** The script will display: +- Your admin password (randomly generated, secure) +- All access URLs +- Instructions for accepting self-signed certificates + +### 🔲 Testing the Authentication Flow + +1. **Access Element** + - Navigate to: https://element.localhost + - Accept self-signed certificate warning (Advanced → Proceed) + +2. **You may need to accept certificates for each domain**: + - https://element.localhost + - https://matrix.localhost + - https://auth.localhost + - https://authelia.localhost + +3. **Sign In** + - Click "Sign In" on Element + - Should redirect through: Element → Matrix → MAS → Authelia + - Log in with: username `admin`, password from deploy.sh output + - Set up 2FA (Time-based OTP) + - Complete registration + +4. **Verify Everything Works** + - Create a room + - Send a message + - Check user settings + +### 🔲 Troubleshooting Commands + +```bash +# View all logs +sudo docker compose logs -f + +# View specific service logs +sudo docker compose logs -f mas +sudo docker compose logs -f authelia +sudo docker compose logs -f synapse + +# Check service status +sudo docker compose ps + +# Restart a service +sudo docker compose restart mas + +# Access PostgreSQL +sudo docker compose exec postgres psql -U synapse + +# Check Caddy config +sudo docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile + +# View Caddy certificates +sudo docker compose exec caddy ls -la /data/caddy/certificates/ +``` + +## Known Issues & Solutions + +### Issue: Certificate Errors in Browser +**Solution**: Accept certificates for each subdomain individually + +### Issue: "Can't reach homeserver" +**Solution**: +1. Check Caddy is running: `sudo docker compose ps caddy` +2. Check Synapse is healthy: `sudo docker compose ps synapse` +3. View logs: `sudo docker compose logs caddy synapse` + +### Issue: Authelia Redirects Not Working +**Solution**: +1. Check Authelia logs: `sudo docker compose logs authelia` +2. Verify all URLs use HTTPS +3. Check session cookie domain matches + +### Issue: MAS Can't Connect to Authelia +**Solution**: +1. Check network connectivity: `sudo docker compose exec mas curl -k https://authelia.localhost` +2. Verify Authelia OIDC discovery: `curl -k https://authelia.localhost/.well-known/openid-configuration` +3. Check client secret matches in both configs + +## File Permissions Note + +Some files will be created with Docker container user permissions (UID 991). You may need sudo to: +- Edit generated config files +- Remove data directories +- View certain logs + +## Next Steps After Successful Deployment + +1. **Test Bridges** - Configure Telegram, WhatsApp, Signal bridges +2. **Mobile Clients** - Test Element-X on iOS/Android +3. **Production Planning** - Review PRODUCTION.md for distributed deployment +4. **Backup Strategy** - Set up automated backups of critical directories +5. **Monitoring** - Consider adding Grafana/Prometheus for monitoring + +## Security Reminders + +- ⚠️ This is a local testing setup with self-signed certificates +- ⚠️ Admin password is randomly generated - SAVE IT! +- ⚠️ Do NOT expose these services to the internet without proper SSL certificates +- ⚠️ For production, review all secrets and configurations +- ⚠️ Change default admin password after first login +- ⚠️ Set up additional users via Authelia user database + +## References + +- Authelia OIDC: https://www.authelia.com/integration/openid-connect/introduction/ +- MAS Configuration: https://element-hq.github.io/matrix-authentication-service/ +- Synapse + MAS: https://matrix-org.github.io/synapse/latest/usage/configuration/config_documentation.html#experimental_features +- Caddy Documentation: https://caddyserver.com/docs/ + +--- + +**Ready to deploy!** Follow the steps in "What Needs to Be Done" section above. diff --git a/PRODUCTION.md b/PRODUCTION.md new file mode 100644 index 0000000..e0aaadb --- /dev/null +++ b/PRODUCTION.md @@ -0,0 +1,537 @@ +# Production Deployment Guide + +This guide covers deploying the Matrix stack in a **distributed production environment** as specified in the requirements: +- **Matrix server** (Synapse, MAS, Element, Bridges) on one machine +- **Authelia** (SSO provider) on a separate machine +- **Caddy** (SSL termination/reverse proxy) on a separate machine + +## Architecture Overview + +``` +┌─────────────────────┐ +│ Internet │ +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────────────┐ +│ Machine 1: Caddy Server │ +│ - SSL termination │ +│ - Reverse proxy │ +│ - Ports: 80, 443, 8448 │ +└──────────┬──────────────────┘ + │ + ┌──────┴──────┐ + │ │ + ▼ ▼ +┌────────────┐ ┌─────────────────────┐ +│ Machine 2: │ │ Machine 3: Matrix │ +│ Authelia │ │ - Synapse │ +│ - SSO/2FA │ │ - Element Web │ +│ - OIDC │ │ - MAS │ +│ │ │ - PostgreSQL │ +│ │ │ - Bridges │ +└────────────┘ └─────────────────────┘ +``` + +## Prerequisites + +- 3 machines (physical or virtual) +- Docker and Docker Compose on all machines +- Domain names with DNS configured: + - `matrix.yourdomain.com` → Caddy server IP + - `element.yourdomain.com` → Caddy server IP + - `mas.yourdomain.com` → Caddy server IP + - `auth.yourdomain.com` → Caddy server IP +- Firewall rules allowing communication between machines + +## Machine 1: Caddy Server (SSL Termination) + +### Setup + +1. Copy the `caddy-server/` directory to the Caddy machine +2. Edit `caddy-server/.env`: + ```bash + MATRIX_SERVER_IP= + AUTHELIA_SERVER_IP= + ``` +3. Edit `caddy-server/Caddyfile` and replace `yourdomain.com` with your actual domain +4. Start Caddy: + ```bash + cd caddy-server + docker compose up -d + ``` + +### Firewall Configuration + +**Allow incoming:** +- Port 80 (HTTP - for ACME challenge) +- Port 443 (HTTPS) +- Port 8448 (Matrix Federation) + +**Allow outgoing to:** +- Machine 2 (Authelia): Port 9091 +- Machine 3 (Matrix): Ports 8008, 8080, 8081, 8448 + +### Verify + +Check Caddy logs: +```bash +docker compose logs -f caddy +``` + +Visit `https://auth.yourdomain.com` - you should see SSL working (initially might show connection refused until backends are up). + +## Machine 2: Authelia Server (SSO) + +### Setup + +1. Copy the `authelia-server/` directory to the Authelia machine + +2. Edit `authelia-server/.env`: + ```bash + POSTGRES_PASSWORD= + AUTHELIA_JWT_SECRET= + AUTHELIA_SESSION_SECRET= + AUTHELIA_STORAGE_ENCRYPTION_KEY= + ``` + +3. Generate RSA key for OIDC: + ```bash + openssl genrsa -out authelia_private.pem 4096 + ``` + +4. Edit `authelia-server/config/configuration.yml`: + - Replace all `yourdomain.com` with your actual domain + - Paste the RSA key into `issuer_private_key` + - Configure SMTP settings for email notifications + - Generate client secret: + ```bash + docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 \ + --variant sha512 --random --random.length 72 --random.charset rfc3986 + ``` + - Update `client_secret` with the hash (keep the plain text secret for MAS config) + +5. Create user accounts in `authelia-server/config/users_database.yml`: + ```bash + # Generate password hash + docker run authelia/authelia:latest authelia crypto hash generate argon2 \ + --password 'yourpassword' + ``` + +6. Start Authelia: + ```bash + cd authelia-server + docker compose up -d + ``` + +### Firewall Configuration + +**Allow incoming from:** +- Machine 1 (Caddy): Port 9091 +- Machine 3 (Matrix): Port 9091 (for MAS OIDC communication) + +**Allow outgoing to:** +- SMTP server (for notifications) + +### Verify + +Check Authelia logs: +```bash +docker compose logs -f authelia +``` + +Test locally: +```bash +curl http://localhost:9091/api/health +``` + +## Machine 3: Matrix Server + +### Setup + +1. Copy the main directory (with docker-compose.production.yml) to the Matrix machine + +2. Use the production compose file: + ```bash + cp docker-compose.production.yml docker-compose.yml + # Or use: docker compose -f docker-compose.production.yml + ``` + +3. Edit `.env`: + ```bash + DOMAIN=yourdomain.com + SERVER_NAME=yourdomain.com + POSTGRES_PASSWORD= + MAS_SECRET_KEY= + # ... other secrets + ``` + +4. Generate Synapse configuration: + ```bash + ./setup-synapse.sh + ``` + +5. Edit `synapse/data/homeserver.yaml`: + + **Update domain:** + ```yaml + server_name: "yourdomain.com" + public_baseurl: "https://matrix.yourdomain.com" + ``` + + **Configure PostgreSQL:** + ```yaml + database: + name: psycopg2 + args: + user: synapse + password: YOUR_POSTGRES_PASSWORD + database: synapse + host: postgres + port: 5432 + cp_min: 5 + cp_max: 10 + ``` + + **Add MAS integration:** + ```yaml + experimental_features: + msc3861: + enabled: true + issuer: https://mas.yourdomain.com/ + client_id: element-web + client_auth_method: none + admin_token: YOUR_ADMIN_TOKEN + ``` + + **Configure trusted key servers:** + ```yaml + trusted_key_servers: + - server_name: "matrix.org" + ``` + +6. Update Element config `element/config/config.production.json`: + - Replace all `yourdomain.com` with your actual domain + +7. Update MAS config `mas/config/config.production.yaml`: + - Replace all `yourdomain.com` with your actual domain + - Update database password + - Generate and add RSA signing key: + ```bash + openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt > mas-signing.key + cat mas-signing.key + ``` + - Update `upstream_oauth2.providers[0].issuer` to `https://auth.yourdomain.com` + - Update `upstream_oauth2.providers[0].client_secret` with the plain text secret from Authelia + +8. Start the stack: + ```bash + # Start PostgreSQL first + docker compose up -d postgres + + # Wait 10 seconds, then start everything + docker compose up -d + ``` + +### Firewall Configuration + +**Allow incoming from:** +- Machine 1 (Caddy): Ports 8008, 8080, 8081, 8448 +- Machine 2 (Authelia): None required (Matrix initiates OIDC to Authelia) + +**Allow outgoing to:** +- Machine 2 (Authelia): Port 9091 (for OIDC) +- Internet: Port 8448 (for Matrix federation) + +### Verify + +Check all services: +```bash +docker compose ps +docker compose logs -f +``` + +## Element-X Mobile Client Setup + +Element-X is the next-generation Matrix client for iOS and Android with native support for MAS/OIDC. + +### Download + +- **iOS**: [App Store - Element X](https://apps.apple.com/app/element-x/id6451362875) +- **Android**: [Google Play - Element X](https://play.google.com/store/apps/details?id=io.element.android.x) + +### Configuration + +1. Open Element-X +2. Tap "Sign In" +3. Enter your homeserver: `yourdomain.com` or `https://matrix.yourdomain.com` +4. Element-X will auto-discover your MAS configuration via `.well-known` +5. You'll be redirected to MAS, then to Authelia +6. Log in with your Authelia credentials +7. Complete 2FA if required +8. Grant permissions and you're in! + +### Troubleshooting Element-X + +If auto-discovery fails: +1. Check `.well-known/matrix/client` is accessible at `https://yourdomain.com/.well-known/matrix/client` +2. Verify Caddy is serving the well-known endpoints correctly +3. Try using the full homeserver URL: `https://matrix.yourdomain.com` + +## Bridge Setup (Optional) + +Bridges work the same in production. After the main stack is running: + +1. Run bridge setup: + ```bash + ./setup-bridges.sh + ``` + +2. Configure each bridge with your production domain: + - homeserver: `http://synapse:8008` + - domain: `yourdomain.com` + +3. Register bridges with Synapse (see SETUP.md) + +4. Restart Synapse: + ```bash + docker compose restart synapse + ``` + +## Network Communication Matrix + +| From | To | Port | Protocol | Purpose | +|------|-----|------|----------|---------| +| Internet | Caddy | 80, 443 | HTTPS | Public access | +| Internet | Caddy | 8448 | HTTPS | Federation | +| Caddy | Authelia | 9091 | HTTP | SSO proxy | +| Caddy | Matrix | 8008 | HTTP | Synapse API proxy | +| Caddy | Matrix | 8080 | HTTP | Element proxy | +| Caddy | Matrix | 8081 | HTTP | MAS proxy | +| Matrix (MAS) | Authelia | 9091 | HTTP | OIDC provider | +| Matrix (Synapse) | Internet | 8448 | HTTPS | Federation | + +## SSL/TLS Certificates + +Caddy automatically obtains and renews Let's Encrypt certificates for all configured domains. + +**Requirements:** +- Domains must point to Caddy server IP +- Port 80 must be accessible for ACME challenge +- Email notifications: Caddy will use `admin@yourdomain.com` by default + +**Check certificates:** +```bash +# On Caddy machine +docker compose exec caddy caddy list-certificates +``` + +## Testing the Production Setup + +### 1. Test Authelia +```bash +curl https://auth.yourdomain.com/api/health +# Should return: {"status":"UP"} +``` + +### 2. Test Matrix Synapse +```bash +curl https://matrix.yourdomain.com/_matrix/client/versions +# Should return JSON with supported Matrix versions +``` + +### 3. Test MAS +```bash +curl https://mas.yourdomain.com/health +# Should return health status +``` + +### 4. Test Well-Known +```bash +curl https://yourdomain.com/.well-known/matrix/client +# Should return homeserver discovery JSON +``` + +### 5. Test Element Web +Visit `https://element.yourdomain.com` and try to sign in. + +### 6. Test Federation +```bash +# From another server, check federation +curl https://matrix.yourdomain.com:8448/_matrix/federation/v1/version +``` + +## Monitoring and Maintenance + +### Logs + +**Caddy:** +```bash +cd caddy-server && docker compose logs -f caddy +``` + +**Authelia:** +```bash +cd authelia-server && docker compose logs -f authelia +``` + +**Matrix:** +```bash +docker compose logs -f synapse +docker compose logs -f mas +``` + +### Updates + +**Update all services:** +```bash +# On each machine +docker compose pull +docker compose up -d +``` + +### Backups + +**Machine 2 (Authelia):** +```bash +# Backup Authelia database and config +docker compose exec postgres pg_dump -U authelia authelia > authelia-backup-$(date +%Y%m%d).sql +tar -czf authelia-config-backup-$(date +%Y%m%d).tar.gz config/ +``` + +**Machine 3 (Matrix):** +```bash +# Backup all Matrix data +docker compose exec postgres pg_dumpall -U synapse > matrix-backup-$(date +%Y%m%d).sql +tar -czf matrix-data-backup-$(date +%Y%m%d).tar.gz synapse/data/ mas/data/ bridges/*/config/ +``` + +## Security Checklist + +- [ ] All default passwords changed in `.env` files +- [ ] Strong passwords for Authelia users +- [ ] SSH key authentication (disable password auth) +- [ ] Firewall rules configured on all machines +- [ ] Fail2ban or similar intrusion prevention +- [ ] Regular backups scheduled +- [ ] SSL/TLS certificates valid and auto-renewing +- [ ] Monitoring and alerting set up +- [ ] Log rotation configured +- [ ] Review Authelia access control rules +- [ ] Test disaster recovery procedure + +## Troubleshooting + +### Can't access Element Web + +1. Check Caddy logs for proxy errors +2. Verify Matrix machine is accessible from Caddy: `curl http://:8080` +3. Check Element container: `docker compose logs element` + +### SSO login fails + +1. Check MAS can reach Authelia: `docker compose exec mas curl http://:9091/api/health` +2. Verify OIDC client secret matches in both configs +3. Check Authelia logs for authentication errors +4. Verify redirect URIs are correct in Authelia config + +### Federation not working + +1. Check port 8448 is open on firewall +2. Verify Caddy is proxying federation port +3. Test federation: `https://federationtester.matrix.org/` +4. Check Synapse federation settings + +### Certificate issues + +1. Verify DNS points to Caddy server +2. Check port 80 is accessible (ACME challenge) +3. View Caddy logs during certificate request +4. Manual cert check: `docker compose exec caddy caddy list-certificates` + +## Migration from Local to Production + +If you started with local testing and want to migrate: + +1. **Backup local data:** + ```bash + docker compose exec postgres pg_dumpall -U synapse > local-backup.sql + tar -czf local-data.tar.gz synapse/data/ + ``` + +2. **Update all configs** with production domains + +3. **Transfer data** to production machines + +4. **Restore database** on production: + ```bash + cat local-backup.sql | docker compose exec -T postgres psql -U synapse + ``` + +5. **Update homeserver.yaml** with new domain + +6. **Clear browser cache** and cookies + +7. **Test thoroughly** before going live + +## Performance Tuning + +### PostgreSQL + +Edit `postgres/postgresql.conf`: +```ini +shared_buffers = 256MB +effective_cache_size = 1GB +maintenance_work_mem = 64MB +max_connections = 100 +``` + +### Synapse + +Edit `synapse/data/homeserver.yaml`: +```yaml +# Increase worker processes +worker_replication_http_port: 9093 + +# Enable caching +caches: + global_factor: 2.0 + +# Media retention +media_retention: + local_media_lifetime: 90d + remote_media_lifetime: 14d +``` + +### Caddy + +For high traffic, consider: +- Multiple Caddy instances behind a load balancer +- CDN for static assets +- Dedicated machine for federation traffic + +## Cost Estimates + +Typical small deployment (100-500 users): +- **Caddy machine**: 1 CPU, 1GB RAM, 20GB disk (~$5-10/month) +- **Authelia machine**: 1 CPU, 2GB RAM, 20GB disk (~$10-15/month) +- **Matrix machine**: 2-4 CPU, 4-8GB RAM, 100GB disk (~$20-40/month) + +Plus domain registration and bandwidth costs. + +## Support Resources + +- [Matrix Homeserver Admin Guide](https://matrix-org.github.io/synapse/latest/usage/administration/) +- [Authelia Documentation](https://www.authelia.com/integration/openid-connect/introduction/) +- [Caddy Documentation](https://caddyserver.com/docs/) +- [Element-X Documentation](https://element.io/element-x) + +## Next Steps + +1. Set up monitoring (Prometheus + Grafana) +2. Configure log aggregation (ELK stack or Loki) +3. Implement automated backups +4. Set up alerting (email, Slack, etc.) +5. Document your specific configuration +6. Create runbooks for common operations +7. Plan for scaling (multiple Synapse workers) diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md new file mode 100644 index 0000000..3062015 --- /dev/null +++ b/QUICK_REFERENCE.md @@ -0,0 +1,338 @@ +# Matrix Stack Quick Reference + +Quick command reference for common operations. + +## 🚀 Deployment + +```bash +# First time deployment +chmod +x deploy.sh +./deploy.sh + +# Update /etc/hosts first! +sudo nano /etc/hosts +# Add: +# 127.0.0.1 matrix.example.test +# 127.0.0.1 element.example.test +# 127.0.0.1 auth.example.test +# 127.0.0.1 authelia.example.test +``` + +## 🌐 Access URLs + +| Service | URL | +|---------|-----| +| Element Web | https://element.example.test | +| Matrix API | https://matrix.example.test | +| MAS Auth | https://auth.example.test | +| Authelia SSO | https://authelia.example.test | +| Caddy Admin | http://localhost:2019 | + +## 📦 Service Management + +```bash +# View all services +docker compose -f docker-compose.local.yml ps + +# Start all services +docker compose -f docker-compose.local.yml up -d + +# Stop all services +docker compose -f docker-compose.local.yml down + +# Restart specific service +docker compose -f docker-compose.local.yml restart mas + +# Restart all services +docker compose -f docker-compose.local.yml restart +``` + +## 📝 Logs + +```bash +# Follow all logs +docker compose -f docker-compose.local.yml logs -f + +# Follow specific service +docker compose -f docker-compose.local.yml logs -f mas + +# Last 100 lines +docker compose -f docker-compose.local.yml logs --tail=100 synapse + +# Search logs for errors +docker compose -f docker-compose.local.yml logs mas | grep ERROR +``` + +## 🗄️ Database Operations + +```bash +# Connect to PostgreSQL +docker compose -f docker-compose.local.yml exec postgres psql -U synapse + +# Connect to specific database +docker compose -f docker-compose.local.yml exec postgres psql -U synapse -d mas + +# List databases +docker compose -f docker-compose.local.yml exec postgres psql -U synapse -c "\l" + +# MAS provider check +docker compose -f docker-compose.local.yml exec postgres psql -U synapse -d mas -c \ + "SELECT upstream_oauth_provider_id, fetch_userinfo, issuer FROM upstream_oauth_providers;" +``` + +## 🔧 Troubleshooting + +### Fix MAS Provider Cache + +```bash +# Delete provider to force config re-sync +docker compose -f docker-compose.local.yml exec postgres psql -U synapse -d mas << 'EOF' +DELETE FROM upstream_oauth_authorization_sessions WHERE upstream_oauth_provider_id = (SELECT upstream_oauth_provider_id FROM upstream_oauth_providers LIMIT 1); +DELETE FROM upstream_oauth_links WHERE upstream_oauth_provider_id = (SELECT upstream_oauth_provider_id FROM upstream_oauth_providers LIMIT 1); +DELETE FROM upstream_oauth_providers; +EOF + +# Restart MAS +docker compose -f docker-compose.local.yml restart mas +``` + +### Extract Caddy CA Certificate + +```bash +# Create certs directory +mkdir -p mas/certs + +# Extract certificate +docker compose -f docker-compose.local.yml exec caddy cat \ + /data/caddy/pki/authorities/local/root.crt > mas/certs/caddy-ca.crt + +# Restart MAS +docker compose -f docker-compose.local.yml restart mas +``` + +### Check Service Health + +```bash +# Check all container health +docker compose -f docker-compose.local.yml ps + +# Check specific service logs for errors +docker compose -f docker-compose.local.yml logs --tail=50 mas | grep ERROR + +# Test HTTPS endpoints +curl -k https://matrix.example.test/_matrix/client/versions +curl -k https://auth.example.test/.well-known/openid-configuration +curl -I -k https://auth.example.test/assets/shared-CVCHz34K.css +``` + +## 👤 User Management + +### Add Authelia User + +```bash +# Generate password hash +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 --password 'userpassword' + +# Edit users database +nano authelia/config/users_database.yml + +# Add user: +# username: +# displayname: "Full Name" +# password: "$argon2id$..." +# email: user@matrix.example.test +# groups: +# - users + +# Restart Authelia +docker compose -f docker-compose.local.yml restart authelia +``` + +### Change Admin Password + +```bash +# Generate new password hash +docker run --rm authelia/authelia:latest \ + authelia crypto hash generate argon2 --password 'newpassword' + +# Update authelia/config/users_database.yml +# Replace admin password hash + +# Restart Authelia +docker compose -f docker-compose.local.yml restart authelia +``` + +## 💾 Backup & Restore + +### Quick Backup + +```bash +# Backup databases +docker compose -f docker-compose.local.yml exec -T postgres pg_dumpall -U synapse \ + > backup_$(date +%Y%m%d).sql + +# Backup configs +tar -czf config_backup_$(date +%Y%m%d).tar.gz \ + authelia/config mas/config synapse/data/homeserver.yaml \ + authelia_private.pem mas-signing.key .env +``` + +### Full Backup + +```bash +# Create backup directory +mkdir -p backups/$(date +%Y%m%d) + +# Backup everything +docker compose -f docker-compose.local.yml exec -T postgres pg_dumpall -U synapse \ + > backups/$(date +%Y%m%d)/databases.sql + +tar -czf backups/$(date +%Y%m%d)/configs.tar.gz \ + authelia/config mas/config synapse/data/homeserver.yaml \ + element/config authelia_private.pem mas-signing.key .env + +tar -czf backups/$(date +%Y%m%d)/media.tar.gz synapse/data/media_store +``` + +### Restore from Backup + +```bash +# Restore databases +docker compose -f docker-compose.local.yml exec -T postgres psql -U synapse < backup.sql + +# Restore configs +tar -xzf configs.tar.gz + +# Restart all services +docker compose -f docker-compose.local.yml restart +``` + +## 🔄 Updates + +```bash +# Pull latest images +docker compose -f docker-compose.local.yml pull + +# Recreate containers +docker compose -f docker-compose.local.yml up -d + +# Remove old images +docker image prune -a +``` + +## 🧹 Cleanup + +```bash +# Stop and remove containers (keeps data) +docker compose -f docker-compose.local.yml down + +# Stop and remove everything INCLUDING DATA (⚠️ DESTRUCTIVE!) +docker compose -f docker-compose.local.yml down -v +rm -rf postgres/data synapse/data mas/data + +# Remove unused Docker resources +docker system prune -a +``` + +## 🔍 Verification Tests + +```bash +# Test Matrix API +curl -k https://matrix.example.test/_matrix/client/versions | jq + +# Test Matrix well-known +curl -k https://matrix.example.test/.well-known/matrix/client | jq + +# Test MAS OIDC discovery +curl -k https://auth.example.test/.well-known/openid-configuration | jq + +# Test Authelia OIDC discovery +curl -k https://authelia.example.test/.well-known/openid-configuration | jq + +# Test MAS assets +curl -I -k https://auth.example.test/assets/shared-CVCHz34K.css + +# Check PostgreSQL health +docker compose -f docker-compose.local.yml exec postgres pg_isready -U synapse +``` + +## 🚨 Emergency Commands + +### Services Won't Start + +```bash +# Check what's failing +docker compose -f docker-compose.local.yml ps + +# View logs +docker compose -f docker-compose.local.yml logs --tail=50 + +# Force recreate +docker compose -f docker-compose.local.yml up -d --force-recreate + +# Nuclear option (stops all, removes containers, restart fresh) +docker compose -f docker-compose.local.yml down +docker compose -f docker-compose.local.yml up -d +``` + +### Database Issues + +```bash +# Stop all services +docker compose -f docker-compose.local.yml down + +# Start only PostgreSQL +docker compose -f docker-compose.local.yml up -d postgres + +# Wait and check +sleep 10 +docker compose -f docker-compose.local.yml logs postgres + +# If working, start everything else +docker compose -f docker-compose.local.yml up -d +``` + +### Port Conflicts + +```bash +# Find what's using port 443 +sudo lsof -i :443 + +# Kill process if needed +sudo kill -9 + +# Or change ports in docker-compose.local.yml +nano docker-compose.local.yml +# Change "443:443" to "8443:443" +``` + +## 📚 Documentation Quick Links + +- **[DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md)** - Complete deployment guide +- **[BUGFIXES.md](BUGFIXES.md)** - Critical fixes and troubleshooting +- **[README.md](README.md)** - Quick start overview +- **[PRODUCTION.md](PRODUCTION.md)** - Production deployment guide + +## 🆘 Common Error Solutions + +| Error | Fix | +|-------|-----| +| CSS not loading | Check MAS has `assets` resource, restart MAS | +| Empty string template error | Verify `fetch_userinfo: true`, delete provider from DB | +| Redirect URI mismatch | Add all redirect URIs to Authelia config | +| Cookie domain error | Use `example.test` not `.localhost` | +| SSL certificate error | Extract Caddy CA cert, mount in MAS | +| Database connection failed | Restart postgres first, wait, then start others | + +## 📞 Getting Help + +1. Check service logs: `docker compose logs -f service-name` +2. Review [BUGFIXES.md](BUGFIXES.md) for detailed solutions +3. Run validation: `./validate-setup.sh` +4. Check database state with queries above +5. Try clean restart: `down` then `up -d` + +--- + +**💡 Tip:** Bookmark this page for quick access to common commands! diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1c0b46 --- /dev/null +++ b/README.md @@ -0,0 +1,298 @@ +# Matrix Communication Stack with HTTPS & SSO + +A complete, self-hosted Matrix homeserver setup with Authelia SSO authentication, automated HTTPS, and messaging bridges. + +## What's Included + +- **Matrix Synapse** - The homeserver +- **Element Web** - Web client interface +- **Matrix Authentication Service (MAS)** - Modern authentication with OIDC +- **Authelia** - SSO provider with 2FA +- **Caddy** - Automatic HTTPS with self-signed certificates for local testing +- **PostgreSQL** - Database backend +- **Redis** - Session storage +- **Bridges** - Telegram, WhatsApp, and Signal integration (pre-configured) +- **Element-X** - Mobile client support (iOS & Android) + +## Deployment Modes + +This setup supports two deployment modes: + +### 🏠 Local Testing (All-in-One) +All services run on a single machine via `docker-compose.yml`. Perfect for development and testing. +- **Guide**: Follow the Quick Start below + +### 🏢 Production (Distributed) +Services distributed across 3 machines for security and scalability: +- Machine 1: Caddy (SSL termination) +- Machine 2: Authelia (SSO) +- Machine 3: Matrix stack (Synapse, Element, MAS, Bridges) + +- **Guide**: See [PRODUCTION.md](PRODUCTION.md) + +## Quick Start (Local Testing with HTTPS) + +### 1. Update Your Hosts File + +Add these entries to `/etc/hosts`: + +```bash +sudo nano /etc/hosts +``` + +Add the following lines: +``` +127.0.0.1 matrix.example.test +127.0.0.1 element.example.test +127.0.0.1 auth.example.test +127.0.0.1 authelia.example.test +``` + +**Note:** We use `example.test` (not `.localhost`) to avoid [public suffix list](https://publicsuffix.org/) issues with Authelia cookie domains. See [BUGFIXES.md](BUGFIXES.md) for details. + +### 2. Run the Automated Deployment Script + +```bash +chmod +x deploy.sh +./deploy.sh +``` + +The script will automatically: +- ✅ Generate all secure secrets (passwords, keys, tokens) +- ✅ Create RSA keys for Authelia and MAS +- ✅ Generate and hash a random admin password +- ✅ Configure all services with HTTPS +- ✅ Start the entire Docker stack +- ✅ Display your admin password and access URLs + +**⚠️ IMPORTANT:** Save the admin password shown at the end! + +### 3. Access Services (via HTTPS) + +| Service | URL | Purpose | +|---------|-----|---------| +| Element Web | https://element.example.test | Main web interface | +| Matrix API | https://matrix.example.test | Homeserver API | +| MAS | https://auth.example.test | Authentication service | +| Authelia | https://authelia.example.test | SSO portal | +| Caddy Admin | http://localhost:2019 | Reverse proxy admin | + +**Note:** You'll see a security warning about self-signed certificates. This is expected for local development. Click "Advanced" → "Proceed to site". + +### 4. Sign In + +1. Go to **https://element.example.test** +2. Accept the self-signed certificate warning +3. Click "Sign In" +4. You'll be redirected through MAS → Authelia for SSO +5. Log in with: + - Username: `admin` + - Password: (shown by deploy.sh) +6. Set up 2FA (Time-based OTP) using your authenticator app if required +7. Complete registration +8. Start chatting! + +## Documentation + +- **[SETUP.md](SETUP.md)** - Comprehensive setup guide with all details +- **[BUGFIXES.md](BUGFIXES.md)** - Critical bugfixes and lessons learned (undocumented issues) +- **[CHECKLIST.md](CHECKLIST.md)** - Step-by-step checklist to track progress +- **[PRODUCTION.md](PRODUCTION.md)** - Production deployment guide (distributed setup) +- **[requirements.md](requirements.md)** - Original requirements +- **[research.md](research.md)** - Research resources + +## Helper Scripts + +| Script | Purpose | +|--------|---------| +| `setup-synapse.sh` | Generate Synapse configuration | +| `setup-bridges.sh` | Generate bridge configurations | +| `validate-setup.sh` | Validate configuration before starting | + +## Architecture + +``` + Browser (HTTPS) + │ + ▼ + ┌────────────────────────────────────┐ + │ Caddy (Port 443) │ + │ Automatic HTTPS with self-signed │ + │ certificates │ + └───┬──────────┬─────────┬──────────┘ + │ │ │ + element.│ matrix. │ auth. │ authelia. + example. │ example. │example. │ example. + test │ test │ test │ test + │ │ │ + ┌──────▼───┐ ┌──▼────┐ ┌─▼────┐ ┌────▼─────┐ + │ Element │ │Synapse│ │ MAS │ │ Authelia │ + │ Web │ │ :8008│ │ :8080│ │ :9091 │ + └──────────┘ └───┬───┘ └──┬───┘ └────┬─────┘ + │ │ │ + │ │ ┌────▼────┐ + │ │ │ Redis │ + │ │ └─────────┘ + │ │ + ┌───▼────────▼────┐ + │ PostgreSQL │ + │ (synapse, mas, │ + │ authelia) │ + └─────────────────┘ + +Bridges (Internal Network): +├── Telegram (mautrix-telegram) +├── WhatsApp (mautrix-whatsapp) +└── Signal (mautrix-signal) +``` + +## Configuration Files + +### Auto-Generated by deploy.sh +- `.env` - Environment variables and all secrets (auto-generated) +- `authelia_private.pem` - Authelia RSA private key +- `mas-signing.key` - MAS signing key +- `synapse/data/homeserver.yaml` - Synapse configuration +- `authelia/config/configuration.yml` - Authelia SSO configuration +- `authelia/config/users_database.yml` - Default admin user +- `mas/config/config.yaml` - MAS configuration with Authelia integration +- `caddy/Caddyfile` - Reverse proxy configuration (pre-created) + +### Pre-configured +- `docker-compose.yml` - Service orchestration +- `caddy/Caddyfile` - HTTPS termination and routing +- `postgres/init/01-init-databases.sql` - Database initialization + +## Exposed Ports + +| Port | Service | Purpose | +|------|---------|---------| +| 443 | Caddy | HTTPS for all services | +| 80 | Caddy | HTTP (redirects to HTTPS) | +| 2019 | Caddy | Admin API | + +All other services (Synapse, Element, MAS, Authelia) are only exposed internally within the Docker network and accessed via Caddy. + +## Data Persistence + +All data is stored in local directories: + +``` +postgres/data/ - Database +synapse/data/ - Synapse data & media +mas/data/ - MAS data +bridges/*/config/ - Bridge configurations +authelia/config/ - Authelia config & users +``` + +**Important:** These directories are in `.gitignore` to protect sensitive data. + +## Troubleshooting + +### Quick Diagnostics + +```bash +# Check all services +docker compose ps + +# View logs +docker compose logs -f + +# Restart a service +docker compose restart synapse + +# Stop everything +docker compose down + +# Start fresh (⚠️ deletes all data) +docker compose down -v +rm -rf postgres/data synapse/data +``` + +### Common Issues + +1. **Port already in use** - Edit `docker-compose.yml` and change port mappings +2. **Permission denied** - Check directory permissions +3. **Service won't start** - Check logs with `docker compose logs service-name` +4. **Can't login** - Verify Authelia user configuration and password hash + +See [SETUP.md](SETUP.md) for detailed troubleshooting. + +## Security + +⚠️ **This setup is for local testing by default** + +For production use: +- Change all passwords and secrets in `.env` +- Set up HTTPS with a reverse proxy (Caddy/nginx) +- Use real domain names +- Configure firewall +- Enable regular backups +- Review all configuration files +- Keep containers updated + +See the "Security Notes" section in [SETUP.md](SETUP.md). + +## Backup + +Essential directories to backup: +```bash +tar -czf matrix-backup-$(date +%Y%m%d).tar.gz \ + postgres/data \ + synapse/data \ + mas/data \ + authelia/config \ + bridges/*/config +``` + +## Useful Commands + +```bash +# View real-time logs +docker compose logs -f + +# Restart specific service +docker compose restart synapse + +# Access Postgres shell +docker compose exec postgres psql -U synapse + +# Generate Authelia password hash +docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' + +# Generate secure secret +openssl rand -base64 32 + +# Check disk usage +docker compose exec postgres du -sh /var/lib/postgresql/data +``` + +## Resources + +- [Matrix.org](https://matrix.org/) - Matrix protocol +- [Synapse Docs](https://matrix-org.github.io/synapse/latest/) +- [Element Docs](https://element.io/help) +- [MAS Docs](https://element-hq.github.io/matrix-authentication-service/) +- [Authelia Docs](https://www.authelia.com/) +- [mautrix Docs](https://docs.mau.fi/) + +## Getting Help + +1. Check the logs: `docker compose logs -f` +2. Read [SETUP.md](SETUP.md) for detailed instructions +3. Use [CHECKLIST.md](CHECKLIST.md) to ensure all steps are complete +4. Run `./validate-setup.sh` to check configuration +5. Check the official documentation links above + +## License + +This configuration is provided as-is for your use. Individual components have their own licenses: +- Matrix Synapse: Apache 2.0 +- Element: Apache 2.0 +- MAS: Apache 2.0 +- Authelia: Apache 2.0 +- mautrix bridges: AGPL-3.0 + +## Contributing + +Feel free to customize this setup for your needs. If you find issues or improvements, document them for your own reference. diff --git a/REQUIREMENTS-CHECK.md b/REQUIREMENTS-CHECK.md new file mode 100644 index 0000000..a9ad4f1 --- /dev/null +++ b/REQUIREMENTS-CHECK.md @@ -0,0 +1,223 @@ +# Requirements Verification + +This document verifies that all requirements from `requirements.md` have been addressed. + +## ✅ Core Requirements + +### 1. Matrix Synapse with Element Web +**Status**: ✅ **COMPLETE** + +- **Synapse**: Configured via `docker-compose.yml` using official `matrixdotorg/synapse:latest` image +- **Element Web**: Configured via `docker-compose.yml` using `vectorim/element-web:latest` +- **Config files**: + - Local: `element/config/config.json` + - Production: `element/config/config.production.json` +- **Documentation**: SETUP.md sections 2-3 + +### 2. Matrix Authentication Service (MAS) with SSO auth via Authelia +**Status**: ✅ **COMPLETE** + +- **MAS**: Configured in `docker-compose.yml` using `ghcr.io/element-hq/matrix-authentication-service:latest` +- **Authelia**: Configured in `docker-compose.yml` using `authelia/authelia:latest` +- **OIDC Integration**: Fully configured + - Authelia acts as OIDC provider + - MAS consumes Authelia OIDC + - Users authenticate through Authelia with 2FA +- **Config files**: + - Local MAS: `mas/config/config.yaml` + - Production MAS: `mas/config/config.production.yaml` + - Local Authelia: `authelia/config/configuration.yml` + - Production Authelia: `authelia-server/config/configuration.yml` +- **Documentation**: SETUP.md sections 7-8 + +### 3. Postgres DB +**Status**: ✅ **COMPLETE** + +- **Image**: `postgres:16-alpine` +- **Databases created**: + - `synapse` - Main Matrix database + - `mas` - MAS database + - `authelia` - Authelia database +- **Init script**: `postgres/init/01-init-databases.sql` +- **Documentation**: SETUP.md section 4 + +### 4. Bridges for Telegram, WhatsApp, Signal +**Status**: ✅ **COMPLETE** + +All three bridges configured using latest mautrix images: +- **Telegram**: `dock.mau.dev/mautrix/telegram:latest` +- **WhatsApp**: `dock.mau.dev/mautrix/whatsapp:latest` +- **Signal**: `dock.mau.dev/mautrix/signal:latest` + +**Setup script**: `setup-bridges.sh` +- Generates configuration for all bridges +- Creates registration files for Synapse +- Documentation**: SETUP.md section 9, "Setting Up Bridges" + +### 5. All running on a single machine with docker compose +**Status**: ✅ **COMPLETE** + +- **Local deployment**: `docker-compose.yml` runs all services on one machine +- **Single command start**: `docker compose up -d` +- **Networks**: Single `matrix-network` bridge network +- **Data persistence**: All data in local directories +- **Documentation**: README.md "Quick Start" section + +### 6. Element-X as mobile client +**Status**: ✅ **COMPLETE** + +- **Configuration**: MAS configured with Element-X redirect URIs + - iOS: `io.element.app:/callback` + - Android: `io.element.android:/callback` +- **Client definitions**: Present in `mas/config/config.production.yaml` +- **Setup instructions**: + - SETUP.md "Mobile Client (Element-X)" section + - PRODUCTION.md "Element-X Mobile Client Setup" section +- **Well-known endpoints**: Configured in Caddy for auto-discovery + +## ✅ Production Deployment Requirements + +### Target deployment: Authelia runs on a separate machine +**Status**: ✅ **COMPLETE** + +- **Separate compose file**: `authelia-server/docker-compose.yml` +- **Independent setup**: Authelia can run standalone with its own PostgreSQL and Redis +- **Configuration**: `authelia-server/config/configuration.yml` for production +- **Documentation**: PRODUCTION.md "Machine 2: Authelia Server (SSO)" section + +### SSL termination via Caddy on a separate machine +**Status**: ✅ **COMPLETE** + +- **Caddy compose file**: `caddy-server/docker-compose.yml` +- **Caddyfile**: Complete reverse proxy configuration with SSL +- **Features**: + - Automatic SSL certificate generation (Let's Encrypt) + - Reverse proxy to both Authelia and Matrix servers + - Well-known endpoints for Matrix auto-discovery + - Security headers + - HTTP/3 support +- **Documentation**: PRODUCTION.md "Machine 1: Caddy Server (SSL Termination)" section + +### We can run both locally for testing, but keep this in mind for the production setup +**Status**: ✅ **COMPLETE** + +**Two deployment modes fully supported:** + +1. **Local Testing** (All-in-one): + - File: `docker-compose.yml` + - All services on one machine + - HTTP (no SSL) + - Uses `matrix.localhost` domain + - Guide: README.md, SETUP.md, CHECKLIST.md + +2. **Production** (Distributed): + - File: `docker-compose.production.yml` + - 3 separate machines + - HTTPS with SSL + - Real domains + - Guide: PRODUCTION.md + +**Migration path**: PRODUCTION.md includes section on migrating from local to production + +## 📁 File Structure + +``` +matrix-2/ +├── docker-compose.yml # Local all-in-one deployment +├── docker-compose.production.yml # Production Matrix server +├── .env # Environment variables +├── README.md # Main documentation +├── SETUP.md # Comprehensive setup guide +├── PRODUCTION.md # Production deployment guide +├── CHECKLIST.md # Setup checklist +├── requirements.md # Original requirements +├── REQUIREMENTS-CHECK.md # This file +│ +├── setup-synapse.sh # Synapse config generator +├── setup-bridges.sh # Bridge setup script +├── validate-setup.sh # Configuration validator +│ +├── synapse/ # Synapse data (generated) +├── element/ +│ └── config/ +│ ├── config.json # Local Element config +│ └── config.production.json # Production Element config +│ +├── mas/ +│ ├── config/ +│ │ ├── config.yaml # Local MAS config +│ │ └── config.production.yaml # Production MAS config +│ └── data/ # MAS data +│ +├── authelia/ +│ └── config/ +│ ├── configuration.yml # Local Authelia config +│ └── users_database.yml # User accounts +│ +├── postgres/ +│ ├── init/ +│ │ └── 01-init-databases.sql # Database initialization +│ └── data/ # PostgreSQL data +│ +├── bridges/ +│ ├── telegram/config/ # Telegram bridge config +│ ├── whatsapp/config/ # WhatsApp bridge config +│ └── signal/config/ # Signal bridge config +│ +├── authelia-server/ # Standalone Authelia deployment +│ ├── docker-compose.yml +│ ├── .env +│ └── config/ +│ └── configuration.yml +│ +└── caddy-server/ # Standalone Caddy deployment + ├── docker-compose.yml + ├── .env + └── Caddyfile +``` + +## 📚 Documentation Coverage + +| Document | Purpose | Audience | +|----------|---------|----------| +| README.md | Overview and quick start | All users | +| SETUP.md | Comprehensive local setup | Local testing | +| PRODUCTION.md | Distributed deployment | Production deployment | +| CHECKLIST.md | Step-by-step task list | Setup tracking | +| requirements.md | Original requirements | Reference | +| REQUIREMENTS-CHECK.md | Verification (this file) | Validation | + +## 🔍 Additional Features Implemented + +Beyond the basic requirements, we also included: + +1. **Health checks**: All services have health checks configured +2. **Validation script**: `validate-setup.sh` for pre-flight checks +3. **Setup scripts**: Automated generation of configs +4. **Security**: Proper environment variable handling, .gitignore +5. **Redis**: For Authelia session storage +6. **Well-known endpoints**: For Matrix client auto-discovery +7. **Federation support**: Port 8448 properly configured +8. **Multiple PostgreSQL databases**: Separate DBs for each service +9. **Comprehensive documentation**: Step-by-step guides with examples +10. **Migration path**: Local to production migration guide +11. **Monitoring guidance**: Log access and health check endpoints +12. **Backup procedures**: Documented in SETUP.md and PRODUCTION.md +13. **Security headers**: Configured in Caddy +14. **HTTP/3 support**: Enabled in Caddy + +## ✅ Final Verification + +**All requirements have been successfully implemented:** + +- ✅ Matrix Synapse with Element Web +- ✅ MAS with SSO auth via Authelia +- ✅ Postgres DB (with multiple databases) +- ✅ Bridges for Telegram, WhatsApp, Signal +- ✅ All running on a single machine with docker compose +- ✅ Element-X mobile client support +- ✅ Authelia can run on a separate machine +- ✅ SSL termination via Caddy on a separate machine +- ✅ Works both locally and in production + +**Status: READY FOR DEPLOYMENT** 🚀 diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..3cfbe8a --- /dev/null +++ b/SETUP.md @@ -0,0 +1,338 @@ +# Matrix Stack Setup Guide + +This guide will help you set up a complete Matrix communication stack with: +- Matrix Synapse homeserver +- Element Web client +- Matrix Authentication Service (MAS) with SSO via Authelia +- PostgreSQL database +- Bridges for Telegram, WhatsApp, and Signal + +## Prerequisites + +- Docker and Docker Compose installed +- At least 4GB RAM available +- Ports 8008, 8080, 8081, 8082, 8448, 9091 available + +## Quick Start + +### 1. Configure Environment Variables + +Edit the `.env` file and change all passwords and secrets: + +```bash +nano .env +``` + +**Important:** Change these values: +- `POSTGRES_PASSWORD` - PostgreSQL password +- `AUTHELIA_JWT_SECRET` - At least 32 characters +- `AUTHELIA_SESSION_SECRET` - At least 32 characters +- `AUTHELIA_STORAGE_ENCRYPTION_KEY` - At least 32 characters +- `MAS_SECRET_KEY` - At least 32 characters +- `DOMAIN` and `SERVER_NAME` - Your domain (use `matrix.localhost` for local testing) + +You can generate secure secrets with: +```bash +openssl rand -base64 32 +``` + +### 2. Generate Synapse Configuration + +Run the setup script to generate the initial Synapse configuration: + +```bash +./setup-synapse.sh +``` + +This will create `./synapse/data/homeserver.yaml`. + +### 3. Configure Synapse for PostgreSQL and MAS + +Edit `./synapse/data/homeserver.yaml`: + +#### Database Configuration +Find the `database:` section and replace it with: + +```yaml +database: + name: psycopg2 + args: + user: synapse + password: changeme # Use your POSTGRES_PASSWORD from .env + database: synapse + host: postgres + port: 5432 + cp_min: 5 + cp_max: 10 +``` + +#### Enable Registration (Optional) +Find `enable_registration:` and set it to `true` if you want to allow registration: + +```yaml +enable_registration: true +enable_registration_without_verification: false +``` + +#### Configure MAS Integration +Add this section to enable Matrix Authentication Service: + +```yaml +# Experimental features for MAS +experimental_features: + # Enable support for MAS issuing OIDC-based access tokens + msc3861: + enabled: true + issuer: http://mas:8080/ + client_id: 01HQW90Z35CMXFJWQPHC3BGZGQ + client_auth_method: none + admin_token: changeme_admin_token +``` + +### 4. Setup Authelia Users + +Generate a password hash for your admin user: + +```bash +docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword' +``` + +Edit `./authelia/config/users_database.yml` and replace the password hash. + +### 5. Generate Authelia Secrets + +#### Generate RSA key for OIDC: +```bash +openssl genrsa -out authelia_private.pem 4096 +cat authelia_private.pem +``` + +Copy the key content and paste it into `./authelia/config/configuration.yml` under `identity_providers.oidc.issuer_private_key`. + +#### Generate client secret hash: +```bash +docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986 +``` + +Copy the hash and update `./authelia/config/configuration.yml` under `identity_providers.oidc.clients[0].client_secret`. + +Also update the plain text secret in `./mas/config/config.yaml` under `upstream_oauth2.providers[0].client_secret`. + +### 6. Generate MAS Signing Key + +Generate an RSA key for MAS: + +```bash +openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt > mas-signing.key +cat mas-signing.key +``` + +Copy the content and paste it into `./mas/config/config.yaml` under `secrets.keys[0].data`. + +### 7. Update MAS Database Password + +Edit `./mas/config/config.yaml` and update the database URI: + +```yaml +database: + uri: 'postgresql://synapse:YOUR_POSTGRES_PASSWORD@postgres/mas' +``` + +Replace `YOUR_POSTGRES_PASSWORD` with the value from your `.env` file. + +### 8. Start the Stack + +Start PostgreSQL and wait for it to initialize: + +```bash +docker compose up -d postgres +docker compose logs -f postgres +``` + +Wait until you see "database system is ready to accept connections", then press Ctrl+C. + +Start the remaining services: + +```bash +docker compose up -d +``` + +Check that everything is running: + +```bash +docker compose ps +``` + +### 9. Access the Services + +- **Element Web**: http://localhost:8080 +- **Synapse**: http://localhost:8008 +- **MAS**: http://localhost:8081 +- **Authelia**: http://localhost:9091 + +### 10. Create Your First User + +Since we're using SSO via Authelia, you first need to create a user in Authelia (see step 4), then: + +1. Go to Element Web at http://localhost:8080 +2. Click "Sign In" +3. You should be redirected to MAS +4. MAS will redirect you to Authelia +5. Log in with your Authelia credentials +6. Complete 2FA setup if required +7. You'll be redirected back to Element Web + +## Setting Up Bridges + +### Generate Bridge Configurations + +Run the bridge setup script: + +```bash +./setup-bridges.sh +``` + +This will generate configuration files for all three bridges. + +### Configure Each Bridge + +For each bridge (telegram, whatsapp, signal): + +1. Edit the config file at `./bridges/{bridge}/config/config.yaml` +2. Set the homeserver address to `http://synapse:8008` +3. Set the domain to `matrix.localhost` (or your domain) +4. Note the `as_token` and `hs_token` values + +### Register Bridges with Synapse + +Each bridge generates a registration file. Copy them to Synapse: + +```bash +cp ./bridges/telegram/config/registration.yaml ./synapse/data/telegram-registration.yaml +cp ./bridges/whatsapp/config/registration.yaml ./synapse/data/whatsapp-registration.yaml +cp ./bridges/signal/config/registration.yaml ./synapse/data/signal-registration.yaml +``` + +Edit `./synapse/data/homeserver.yaml` and add: + +```yaml +app_service_config_files: + - /data/telegram-registration.yaml + - /data/whatsapp-registration.yaml + - /data/signal-registration.yaml +``` + +Restart Synapse: + +```bash +docker compose restart synapse +``` + +### Using the Bridges + +#### Telegram Bridge +1. Start a chat with `@telegrambot:matrix.localhost` +2. Send `login` +3. Follow the authentication steps + +#### WhatsApp Bridge +1. Start a chat with `@whatsappbot:matrix.localhost` +2. Send `login` +3. Scan the QR code with your phone + +#### Signal Bridge +1. Start a chat with `@signalbot:matrix.localhost` +2. Send `link` +3. Scan the QR code or link your device + +## Mobile Client (Element-X) + +Element-X is available for iOS and Android. Configure it with: + +- **Homeserver URL**: http://your-server-ip:8008 (or your public domain if accessible) +- Use your Authelia credentials to log in via SSO + +## Troubleshooting + +### Check Service Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f synapse +docker compose logs -f mas +docker compose logs -f authelia +``` + +### Service Not Starting + +Check the logs for errors: +```bash +docker compose logs service-name +``` + +### Database Connection Issues + +Ensure PostgreSQL is running and healthy: +```bash +docker compose exec postgres pg_isready -U synapse +``` + +### Port Conflicts + +If ports are already in use, edit `docker-compose.yml` and change the port mappings: +```yaml +ports: + - "8008:8008" # Change left side to available port +``` + +### Reset Everything + +To start fresh: +```bash +docker compose down -v +rm -rf postgres/data synapse/data mas/data bridges/*/config +``` + +Then start from step 2. + +## Security Notes + +⚠️ **For Production Use:** + +1. Use strong, unique passwords and secrets +2. Set up proper TLS/SSL certificates (use Caddy or nginx reverse proxy) +3. Use a real domain name +4. Configure firewall rules +5. Regular backups of PostgreSQL data +6. Keep all containers updated +7. Review and harden all configuration files +8. Consider using Docker secrets instead of environment variables + +## Backup + +Important directories to backup: +- `./postgres/data` - All database data +- `./synapse/data` - Synapse configuration and media +- `./mas/data` - MAS data +- `./authelia/config` - Authelia configuration +- `./bridges/*/config` - Bridge configurations + +## Next Steps + +1. Configure reverse proxy (Caddy/nginx) for HTTPS +2. Set up proper domain names +3. Configure email for Authelia notifications +4. Customize Element Web branding +5. Set up media repository size limits +6. Configure backup automation + +## Resources + +- [Matrix Synapse Documentation](https://matrix-org.github.io/synapse/latest/) +- [Element Documentation](https://element.io/help) +- [MAS Documentation](https://element-hq.github.io/matrix-authentication-service/) +- [Authelia Documentation](https://www.authelia.com/) +- [mautrix bridges Documentation](https://docs.mau.fi/) diff --git a/caddy/Caddyfile b/caddy/Caddyfile new file mode 100644 index 0000000..c51ea6a --- /dev/null +++ b/caddy/Caddyfile @@ -0,0 +1,245 @@ +# Local Development Caddyfile for Matrix Stack +# Uses self-signed certificates for local HTTPS testing + +{ + # Use local CA for self-signed certificates + local_certs + # Enable admin API + admin 0.0.0.0:2019 +} + +# ========================= +# Matrix Homeserver (Synapse) +# ========================= +matrix.example.test:443 { + # TLS with self-signed cert + tls internal + + # Well-known client endpoint + @wk path /.well-known/matrix/client + handle @wk { + header Content-Type application/json + respond `{"m.homeserver":{"base_url":"https://matrix.example.test"},"m.authentication":{"issuer":"https://auth.example.test/"}}` 200 + } + + # Well-known server endpoint (federation) + @wk_server path /.well-known/matrix/server + handle @wk_server { + header Content-Type application/json + respond `{"m.server":"matrix.example.test:443"}` 200 + } + + # Client versions endpoint with CORS + @versions path /_matrix/client/versions + handle @versions { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy synapse:8008 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + } + } + + # CORS preflight for auth metadata + @auth_preflight { + method OPTIONS + path /_matrix/client/unstable/org.matrix.msc2965/auth_metadata + } + handle @auth_preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Access-Control-Max-Age "86400" + respond 204 + } + + # CORS preflight for all Matrix API + @preflight { + method OPTIONS + path_regexp matrix ^/_matrix/.*$ + } + handle @preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Access-Control-Max-Age "86400" + respond 204 + } + + # MAS compat endpoints (login/logout/refresh) with CORS + @compat path \ + /_matrix/client/v3/login* \ + /_matrix/client/v3/logout* \ + /_matrix/client/v3/refresh* \ + /_matrix/client/r0/login* \ + /_matrix/client/r0/logout* \ + /_matrix/client/r0/refresh* + handle @compat { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy mas:8080 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + } + } + + # Everything else under /_matrix → Synapse with CORS + @matrix_rest path_regexp matrix ^/_matrix/.*$ + handle @matrix_rest { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy synapse:8008 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + } + } + + # Default: everything else → Synapse + handle { + reverse_proxy synapse:8008 + } +} + +# ========================= +# Matrix Authentication Service (MAS) +# ========================= +auth.example.test:443 { + tls internal + + # OIDC Discovery + @disco path /.well-known/openid-configuration + handle @disco { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + reverse_proxy mas:8080 + } + + # Dynamic Client Registration: CORS preflight + @reg_opts { + method OPTIONS + path /oauth2/registration + } + handle @reg_opts { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "POST, OPTIONS" + header ?Access-Control-Allow-Headers "*" + respond 204 + } + + # Dynamic Client Registration (POST) + @reg path /oauth2/registration + route @reg { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "POST, OPTIONS" + header ?Access-Control-Allow-Headers "*" + reverse_proxy mas:8080 + } + + # JWKS preflight + @jwks_opts { + method OPTIONS + path /oauth2/keys.json + } + handle @jwks_opts { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + respond 204 + } + + # Map keys.json → /oauth2/jwks (MAS naming) + @jwksjson path /oauth2/keys.json + route @jwksjson { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + uri replace /oauth2/keys.json /oauth2/jwks + reverse_proxy mas:8080 + } + + # Generic OAuth2 endpoints + @oauth path /oauth2/* + route @oauth { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS, POST" + header ?Access-Control-Allow-Headers "*" + reverse_proxy mas:8080 + } + + # Account portal + handle_path /account/* { + reverse_proxy mas:8080 + } + + # Authelia endpoints (proxy to authelia) + handle_path /authelia/* { + reverse_proxy authelia:9091 + } + + # Fallback: everything else to MAS + handle { + reverse_proxy mas:8080 + } + + # Add CORS on error responses + handle_errors { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Headers "*" + header ?Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + } +} + +# ========================= +# Authelia SSO +# ========================= +authelia.example.test:443 { + tls internal + + reverse_proxy authelia:9091 +} + +# ========================= +# Element Web Client +# ========================= +element.example.test:443 { + tls internal + + # Serve config.json with proper settings + @cfg path /config.json + handle @cfg { + header Content-Type application/json + header Cache-Control no-store + respond `{ + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.example.test", + "server_name": "matrix.example.test" + } + }, + "default_server_name": "matrix.example.test", + "disable_custom_urls": false, + "disable_guests": true, + "features": { + "feature_oidc_aware_navigation": true + } + }` 200 + } + + # Everything else to Element container + handle { + reverse_proxy element:80 + } +} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..c23ac5e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,967 @@ +#!/bin/bash +# Automated Matrix Stack Deployment Script +# This script handles the complete deployment from scratch +# Supports both local testing and production deployments + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +NC='\033[0m' # No Color + +# Use sudo for docker commands +DOCKER_CMD="sudo docker" +DOCKER_COMPOSE_CMD="sudo docker compose" + +echo -e "${YELLOW}Using sudo for docker commands.${NC}" +echo "" + +# Test docker access +if ! sudo docker ps &> /dev/null; then + echo -e "${RED}Error: Cannot access Docker. Please ensure Docker is running.${NC}" + exit 1 +fi + +clear +echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ Matrix Stack Automated Deployment Script ║${NC}" +echo -e "${BLUE}║ Interactive Setup ║${NC}" +echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" +echo "" + +# ============================================================================ +# DEPLOYMENT TYPE SELECTION +# ============================================================================ +echo -e "${CYAN}Select Deployment Type:${NC}" +echo "" +echo -e " ${GREEN}1)${NC} Local Testing (All-in-One)" +echo -e " → Everything on one machine with self-signed certificates" +echo -e " → Uses *.localhost domains" +echo -e " → Caddy + Authelia + Matrix stack together" +echo "" +echo -e " ${GREEN}2)${NC} Production (Distributed)" +echo -e " → Services on separate machines for security" +echo -e " → Machine 1: Caddy (SSL termination)" +echo -e " → Machine 2: Authelia (SSO)" +echo -e " → Machine 3: Matrix stack (Synapse, Element, MAS, bridges)" +echo -e " → Real domains with Let's Encrypt certificates" +echo "" +read -p "Enter choice [1 or 2]: " DEPLOYMENT_TYPE + +if [[ "$DEPLOYMENT_TYPE" == "1" ]]; then + DEPLOYMENT_MODE="local" + COMPOSE_FILE="docker-compose.local.yml" + echo -e "${GREEN}✓${NC} Selected: Local Testing Mode" +elif [[ "$DEPLOYMENT_TYPE" == "2" ]]; then + DEPLOYMENT_MODE="production" + COMPOSE_FILE="docker-compose.production.yml" + echo -e "${GREEN}✓${NC} Selected: Production Mode" +else + echo -e "${RED}✗${NC} Invalid choice. Exiting." + exit 1 +fi +echo "" + +# Function to generate secure random string (32 bytes base64) +generate_secret() { + openssl rand -base64 32 | tr -d "=+/" | cut -c1-32 +} + +# Function to generate secure hex string (for MAS encryption) +generate_hex_secret() { + openssl rand -hex 32 +} + +# Function to print status +print_status() { + echo -e "${GREEN}✓${NC} $1" +} + +print_error() { + echo -e "${RED}✗${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}⚠${NC} $1" +} + +print_info() { + echo -e "${BLUE}ℹ${NC} $1" +} + +# ============================================================================ +# DOMAIN AND CONFIGURATION PROMPTS +# ============================================================================ +if [[ "$DEPLOYMENT_MODE" == "local" ]]; then + # Local testing with example.test domains (not .localhost - it's on the public suffix list!) + DOMAIN_BASE="example.test" + AUTHELIA_COOKIE_DOMAIN="example.test" # No leading dot for cookie domain + MATRIX_DOMAIN="matrix.example.test" + ELEMENT_DOMAIN="element.example.test" + AUTH_DOMAIN="auth.example.test" + AUTHELIA_DOMAIN="authelia.example.test" + + echo -e "${CYAN}Local Testing Configuration:${NC}" + echo -e " Matrix API: https://${MATRIX_DOMAIN}" + echo -e " Element Web: https://${ELEMENT_DOMAIN}" + echo -e " MAS Auth: https://${AUTH_DOMAIN}" + echo -e " Authelia: https://${AUTHELIA_DOMAIN}" + echo "" + echo -e "${YELLOW}⚠ Remember to add these to /etc/hosts:${NC}" + echo -e " 127.0.0.1 ${MATRIX_DOMAIN}" + echo -e " 127.0.0.1 ${ELEMENT_DOMAIN}" + echo -e " 127.0.0.1 ${AUTH_DOMAIN}" + echo -e " 127.0.0.1 ${AUTHELIA_DOMAIN}" + echo "" + echo -e "${BLUE}ℹ Note: Using example.test (not .localhost) to avoid public suffix list issues${NC}" + echo "" + read -p "Press Enter to continue..." + echo "" + +else + # Production deployment + echo -e "${CYAN}Production Deployment Configuration${NC}" + echo "" + + # Base domain + read -p "Enter your base domain (e.g., example.com): " DOMAIN_BASE + AUTHELIA_COOKIE_DOMAIN="${DOMAIN_BASE}" # Production uses the base domain + + # Matrix subdomain + read -p "Enter Matrix subdomain [default: matrix]: " MATRIX_SUBDOMAIN + MATRIX_SUBDOMAIN=${MATRIX_SUBDOMAIN:-matrix} + MATRIX_DOMAIN="${MATRIX_SUBDOMAIN}.${DOMAIN_BASE}" + + # Element subdomain + read -p "Enter Element subdomain [default: element]: " ELEMENT_SUBDOMAIN + ELEMENT_SUBDOMAIN=${ELEMENT_SUBDOMAIN:-element} + ELEMENT_DOMAIN="${ELEMENT_SUBDOMAIN}.${DOMAIN_BASE}" + + # MAS subdomain + read -p "Enter MAS/Auth subdomain [default: auth]: " AUTH_SUBDOMAIN + AUTH_SUBDOMAIN=${AUTH_SUBDOMAIN:-auth} + AUTH_DOMAIN="${AUTH_SUBDOMAIN}.${DOMAIN_BASE}" + + # Authelia subdomain + read -p "Enter Authelia subdomain [default: authelia]: " AUTHELIA_SUBDOMAIN + AUTHELIA_SUBDOMAIN=${AUTHELIA_SUBDOMAIN:-authelia} + AUTHELIA_DOMAIN="${AUTHELIA_SUBDOMAIN}.${DOMAIN_BASE}" + + echo "" + echo -e "${CYAN}Backend Server IPs:${NC}" + echo "" + + # Matrix server IP (this machine) + read -p "Enter Matrix server IP (this machine): " MATRIX_SERVER_IP + + # Authelia server IP + read -p "Enter Authelia server IP: " AUTHELIA_SERVER_IP + + # Email for Let's Encrypt + read -p "Enter email for Let's Encrypt certificates: " LETSENCRYPT_EMAIL + + echo "" + echo -e "${GREEN}✓${NC} Configuration Summary:" + echo -e " Base Domain: ${DOMAIN_BASE}" + echo -e " Matrix: https://${MATRIX_DOMAIN} (Backend: ${MATRIX_SERVER_IP})" + echo -e " Element: https://${ELEMENT_DOMAIN} (Backend: ${MATRIX_SERVER_IP})" + echo -e " MAS: https://${AUTH_DOMAIN} (Backend: ${MATRIX_SERVER_IP})" + echo -e " Authelia: https://${AUTHELIA_DOMAIN} (Backend: ${AUTHELIA_SERVER_IP})" + echo -e " Let's Encrypt: ${LETSENCRYPT_EMAIL}" + echo "" + read -p "Is this correct? [y/N]: " CONFIRM + if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then + echo -e "${RED}✗${NC} Aborted by user" + exit 1 + fi + echo "" +fi + +# Step 1: Check prerequisites +echo -e "${BLUE}[1/13] Checking prerequisites...${NC}" +if ! command -v openssl &> /dev/null; then + print_error "openssl is not installed" + exit 1 +fi +if ! $DOCKER_CMD --version &> /dev/null; then + print_error "Docker is not accessible" + exit 1 +fi +print_status "Prerequisites OK" +echo "" + +# Step 2: Generate secure secrets +echo -e "${BLUE}[2/12] Generating secure secrets...${NC}" +POSTGRES_PASSWORD=$(generate_secret) +AUTHELIA_JWT_SECRET=$(generate_secret) +AUTHELIA_SESSION_SECRET=$(generate_secret) +AUTHELIA_STORAGE_ENCRYPTION_KEY=$(generate_secret) +MAS_SECRET_KEY=$(generate_hex_secret) # MAS requires hex format +SYNAPSE_SHARED_SECRET=$(generate_secret) +print_status "Secrets generated" +echo "" + +# Step 3: Update .env file +echo -e "${BLUE}[3/13] Updating .env file with generated secrets...${NC}" +cat > .env << EOF +# Matrix Stack Environment Variables +# Auto-generated by deploy.sh on $(date) +# Deployment Mode: ${DEPLOYMENT_MODE} + +# Domain Configuration +DOMAIN_BASE=${DOMAIN_BASE} +MATRIX_DOMAIN=${MATRIX_DOMAIN} +ELEMENT_DOMAIN=${ELEMENT_DOMAIN} +AUTH_DOMAIN=${AUTH_DOMAIN} +AUTHELIA_DOMAIN=${AUTHELIA_DOMAIN} +SERVER_NAME=${MATRIX_DOMAIN} + +# PostgreSQL +POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + +# Synapse +SYNAPSE_REPORT_STATS=no +SYNAPSE_SHARED_SECRET=${SYNAPSE_SHARED_SECRET} + +# Authelia +AUTHELIA_JWT_SECRET=${AUTHELIA_JWT_SECRET} +AUTHELIA_SESSION_SECRET=${AUTHELIA_SESSION_SECRET} +AUTHELIA_STORAGE_ENCRYPTION_KEY=${AUTHELIA_STORAGE_ENCRYPTION_KEY} +AUTHELIA_POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + +# MAS +MAS_DATABASE_URL=postgresql://synapse:${POSTGRES_PASSWORD}@postgres/mas +MAS_SECRET_KEY=${MAS_SECRET_KEY} + +# Timezone +TZ=${TZ:-Europe/Berlin} +EOF + +# Add production-specific variables +if [[ "$DEPLOYMENT_MODE" == "production" ]]; then + cat >> .env << EOF + +# Production Configuration +MATRIX_SERVER_IP=${MATRIX_SERVER_IP} +AUTHELIA_SERVER_IP=${AUTHELIA_SERVER_IP} +LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} +EOF +fi + +print_status ".env file updated" +echo "" + +# Step 4: Generate RSA key for Authelia +echo -e "${BLUE}[4/12] Generating RSA key for Authelia OIDC...${NC}" +openssl genrsa -out authelia_private.pem 4096 2>/dev/null +AUTHELIA_RSA_KEY=$(cat authelia_private.pem) +print_status "Authelia RSA key generated" +echo "" + +# Step 5: Generate client secret for Authelia +echo -e "${BLUE}[5/12] Generating Authelia client secret...${NC}" +CLIENT_SECRET_PLAIN=$(generate_secret) +# Generate hash for Authelia config +CLIENT_SECRET_HASH=$($DOCKER_CMD run --rm authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --password "${CLIENT_SECRET_PLAIN}" 2>/dev/null | grep "Digest:" | awk '{print $2}') +print_status "Client secret generated" +echo "" + +# Step 6: Generate password hash for default admin user +echo -e "${BLUE}[6/12] Generating default admin user...${NC}" +ADMIN_PASSWORD=$(generate_secret) # Generate secure random password +ADMIN_PASSWORD_HASH=$($DOCKER_CMD run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "${ADMIN_PASSWORD}" 2>/dev/null | grep "Digest:" | awk '{print $2}') +print_status "Admin user password hash generated" +print_warning "Default admin password: ${ADMIN_PASSWORD} (SAVE THIS - you'll need it to log in!)" +echo "" + +# Step 7: Update Authelia configuration +echo -e "${BLUE}[7/12] Configuring Authelia...${NC}" +cat > authelia/config/configuration.yml << EOF +--- +# Authelia Configuration for Matrix Stack + +theme: auto + +server: + address: 'tcp://0.0.0.0:9091' + +log: + level: 'info' + format: 'text' + +authentication_backend: + file: + path: '/config/users_database.yml' + password: + algorithm: 'argon2' + argon2: + variant: 'argon2id' + iterations: 3 + memory: 65536 + parallelism: 4 + key_length: 32 + salt_length: 16 + +session: + secret: '${AUTHELIA_SESSION_SECRET}' + cookies: + - domain: '${AUTHELIA_COOKIE_DOMAIN}' + authelia_url: 'https://${AUTHELIA_DOMAIN}' + default_redirection_url: 'https://${ELEMENT_DOMAIN}' + + redis: + host: 'redis' + port: 6379 + +storage: + encryption_key: '${AUTHELIA_STORAGE_ENCRYPTION_KEY}' + postgres: + address: 'tcp://postgres:5432' + database: 'authelia' + username: 'synapse' + password: '${POSTGRES_PASSWORD}' + +notifier: + filesystem: + filename: '/config/notification.txt' + +identity_validation: + reset_password: + jwt_secret: '${AUTHELIA_JWT_SECRET}' + +access_control: + default_policy: 'deny' + rules: + - domain: + - 'matrix.localhost' + policy: 'two_factor' + - domain: + - 'element.matrix.localhost' + policy: 'two_factor' + +identity_providers: + oidc: + hmac_secret: '${AUTHELIA_JWT_SECRET}' + jwks: + - key_id: 'main' + algorithm: 'RS256' + use: 'sig' + key: | +EOF + +# Add the RSA key with proper indentation +echo "$AUTHELIA_RSA_KEY" | sed 's/^/ /' >> authelia/config/configuration.yml + +# Continue with the rest of the config +cat >> authelia/config/configuration.yml << EOF + clients: + - client_id: 'mas-client' + client_name: 'Matrix Authentication Service' + client_secret: '${CLIENT_SECRET_HASH}' + public: false + authorization_policy: 'one_factor' # Change to two_factor in production! + redirect_uris: + - 'https://${AUTH_DOMAIN}/callback' + - 'https://${AUTH_DOMAIN}/oauth2/callback' + - 'https://${AUTH_DOMAIN}/upstream/callback/01HQW90Z35CMXFJWQPHC3BGZGQ' # MAS upstream callback + scopes: + - 'openid' + - 'profile' + - 'email' + - 'offline_access' + grant_types: + - 'authorization_code' + - 'refresh_token' + response_types: + - 'code' + token_endpoint_auth_method: 'client_secret_basic' +EOF + +print_status "Authelia configuration updated" +echo "" + +# Step 8: Create Authelia users database +echo -e "${BLUE}[8/12] Creating Authelia users database...${NC}" +cat > authelia/config/users_database.yml << EOF +--- +# Authelia Users Database + +users: + admin: + displayname: "Admin User" + password: "${ADMIN_PASSWORD_HASH}" + email: admin@${MATRIX_DOMAIN} + groups: + - admins + - users +EOF + +print_status "Authelia users database created" +echo "" + +# Step 9: Generate MAS signing key and Synapse client secret +echo -e "${BLUE}[9/12] Generating MAS signing key and Synapse client secret...${NC}" +openssl genrsa 4096 2>/dev/null | openssl pkcs8 -topk8 -nocrypt > mas-signing.key 2>/dev/null +MAS_SIGNING_KEY=$(cat mas-signing.key) +SYNAPSE_CLIENT_SECRET=$(generate_secret) +print_status "MAS signing key and Synapse client secret generated" +echo "" + +# Step 10: Configure MAS +echo -e "${BLUE}[10/12] Configuring MAS...${NC}" +cat > mas/config/config.yaml << EOF +--- +# Matrix Authentication Service (MAS) Configuration + +http: + listeners: + - name: web + resources: + - name: discovery + - name: human + - name: oauth + - name: compat + - name: graphql + playground: true + - name: assets # Required for CSS/JS files + binds: + - address: '[::]:8080' + - name: internal + resources: + - name: health + binds: + - address: '127.0.0.1:8081' + + public_base: 'https://${AUTH_DOMAIN}/' + issuer: 'https://${AUTH_DOMAIN}/' + +database: + uri: 'postgresql://synapse:${POSTGRES_PASSWORD}@postgres/mas' + auto_migrate: true + +secrets: + encryption: '${MAS_SECRET_KEY}' + keys: + - kid: 'key-1' + algorithm: rs256 + key: | +EOF + +# Add the MAS signing key with proper indentation +echo "$MAS_SIGNING_KEY" | sed 's/^/ /' >> mas/config/config.yaml + +# Continue with the rest of the MAS config +cat >> mas/config/config.yaml << EOF + +upstream_oauth2: + providers: + - id: '01HQW90Z35CMXFJWQPHC3BGZGQ' + issuer: 'https://${AUTHELIA_DOMAIN}' + discovery_url: 'http://authelia:9091/.well-known/openid-configuration' # Internal HTTP for faster access + client_id: 'mas-client' + client_secret: '${CLIENT_SECRET_PLAIN}' + scope: 'openid profile email offline_access' + token_endpoint_auth_method: 'client_secret_basic' + fetch_userinfo: true # Critical: Must fetch userinfo for Authelia claims + claims_imports: + localpart: + action: force + template: '{{ user.preferred_username }}' # Works with Authelia + displayname: + action: suggest + template: '{{ user.preferred_username }}' # Authelia provides preferred_username, not name + email: + action: force + template: '{{ user.email }}' + set_email_verification: always + +matrix: + homeserver: '${MATRIX_DOMAIN}' + endpoint: 'http://synapse:8008' + secret: '${SYNAPSE_SHARED_SECRET}' + +passwords: + enabled: false # Using Authelia SSO instead + +email: + from: '"Matrix Authentication Service" ' + reply_to: '"Matrix Support" ' + transport: smtp + hostname: 'localhost' + port: 25 + mode: plain + +branding: + service_name: 'Matrix' + policy_uri: 'https://${AUTH_DOMAIN}/privacy' + tos_uri: 'https://${AUTH_DOMAIN}/terms' + +policy: + registration: + enabled: true + require_email: true + +clients: + # Element Web client (public) + - client_id: '01HQW90Z35CMXFJWQPHC3BGZGQ' + client_auth_method: none + redirect_uris: + - 'https://${ELEMENT_DOMAIN}' + - 'https://${ELEMENT_DOMAIN}/mobile_guide/' + - 'io.element.app:/callback' + + # Synapse client (confidential - for backend integration) + - client_id: '0000000000000000000SYNAPSE' + client_auth_method: client_secret_basic + client_secret: '${SYNAPSE_CLIENT_SECRET}' +EOF + +print_status "MAS configuration created" +echo "" + +# Step 11: Create Element Web configuration +echo -e "${BLUE}[11/13] Creating Element Web configuration...${NC}" +cat > element/config/config.json << EOF +{ + "default_server_config": { + "m.homeserver": { + "base_url": "https://${MATRIX_DOMAIN}", + "server_name": "${MATRIX_DOMAIN}" + } + }, + "brand": "Element", + "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" + ], + "show_labs_settings": true, + "piwik": false, + "room_directory": { + "servers": [ + "matrix.org", + "${MATRIX_DOMAIN}" + ] + }, + "features": { + "feature_oidc_aware_navigation": true + }, + "default_server_name": "${MATRIX_DOMAIN}", + "disable_custom_urls": false, + "disable_guests": true +} +EOF +print_status "Element Web configuration created" +echo "" + +# Step 12: Generate Synapse configuration +echo -e "${BLUE}[12/13] Generating Synapse configuration...${NC}" +if [ ! -f "synapse/data/homeserver.yaml" ]; then + $DOCKER_CMD run -it --rm \ + -v $(pwd)/synapse/data:/data \ + -e SYNAPSE_SERVER_NAME=${MATRIX_DOMAIN} \ + -e SYNAPSE_REPORT_STATS=no \ + matrixdotorg/synapse:latest generate + + # Update Synapse config for PostgreSQL + print_info "Configuring Synapse for PostgreSQL..." + + # Backup original config + cp synapse/data/homeserver.yaml synapse/data/homeserver.yaml.bak + + # Add PostgreSQL config + cat >> synapse/data/homeserver.yaml << EOF + +# PostgreSQL Database Configuration (added by deploy.sh) +database: + name: psycopg2 + args: + user: synapse + password: ${POSTGRES_PASSWORD} + database: synapse + host: postgres + port: 5432 + cp_min: 5 + cp_max: 10 + +# Enable registration (disabled when using MAS/OAuth delegation) +enable_registration: false + +# MAS Integration (MSC3861 - OAuth delegation) +experimental_features: + msc3861: + enabled: true + issuer: https://${AUTH_DOMAIN}/ + client_id: 0000000000000000000SYNAPSE + client_auth_method: client_secret_basic + client_secret: ${SYNAPSE_CLIENT_SECRET} + admin_token: ${SYNAPSE_SHARED_SECRET} +EOF + + print_status "Synapse configuration generated and updated" +else + print_warning "Synapse config already exists, skipping generation" +fi +echo "" + +# Step 13: Fix directory permissions +echo -e "${BLUE}[13/14] Fixing directory permissions...${NC}" +chmod 755 postgres/init postgres/config 2>/dev/null || true +chmod 644 postgres/init/*.sql 2>/dev/null || true +chmod 755 authelia/config mas/config element/config 2>/dev/null || true +print_status "Permissions fixed" +echo "" + +# Step 14: Start the stack +echo -e "${BLUE}[14/14] Starting the Matrix stack...${NC}" +print_info "Using compose file: ${COMPOSE_FILE}" +print_info "This may take a few minutes on first run..." +echo "" + +# Start PostgreSQL first +print_info "Starting PostgreSQL..." +$DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} up -d postgres +sleep 10 + +# Wait for PostgreSQL to be ready +print_info "Waiting for PostgreSQL to be ready..." +for i in {1..60}; do + if $DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} exec -T postgres pg_isready -U synapse &> /dev/null; then + print_status "PostgreSQL is ready" + break + fi + if [ $i -eq 60 ]; then + print_error "PostgreSQL failed to start in time" + echo "Checking logs..." + $DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} logs postgres | tail -20 + exit 1 + fi + sleep 2 +done +echo "" + +# Start Redis (if in local mode, it's part of the stack) +if [[ "$DEPLOYMENT_MODE" == "local" ]] || [[ "$DEPLOYMENT_MODE" == "production" ]]; then + print_info "Starting Redis..." + $DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} up -d redis + sleep 3 + echo "" +fi + +# Start remaining services +print_info "Starting all services..." +$DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} up -d +echo "" + +# Wait for services to be ready +print_info "Waiting for services to be ready..." +sleep 10 +echo "" + +# ============================================================================ +# LOCAL: Extract Caddy CA Certificate for MAS +# ============================================================================ +if [[ "$DEPLOYMENT_MODE" == "local" ]]; then + echo -e "${BLUE}[Post-Deployment] Extracting Caddy CA certificate for MAS...${NC}" + + # Create certs directory + mkdir -p mas/certs + + # Wait for Caddy to generate CA + print_info "Waiting for Caddy to generate local CA..." + sleep 5 + + # Extract CA certificate + if $DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} exec -T caddy cat /data/caddy/pki/authorities/local/root.crt > mas/certs/caddy-ca.crt 2>/dev/null; then + print_status "Caddy CA certificate extracted to mas/certs/caddy-ca.crt" + + # Restart MAS to pick up the certificate + print_info "Restarting MAS to load CA certificate..." + $DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} restart mas + sleep 5 + print_status "MAS restarted with trusted CA certificate" + else + print_warning "Could not extract Caddy CA certificate (Caddy may still be initializing)" + print_info "If you see SSL errors, run: docker compose -f ${COMPOSE_FILE} exec caddy cat /data/caddy/pki/authorities/local/root.crt > mas/certs/caddy-ca.crt" + print_info "Then restart MAS: docker compose -f ${COMPOSE_FILE} restart mas" + fi + echo "" +fi + +# ============================================================================ +# PRODUCTION: Generate Caddy and Authelia configs for separate machines +# ============================================================================ +if [[ "$DEPLOYMENT_MODE" == "production" ]]; then + echo "" + echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" + echo -e "${MAGENTA}Generating Production Deployment Configs...${NC}" + echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" + echo "" + + # Generate production Caddyfile + print_info "Generating Caddyfile for Caddy machine..." + cat > caddy/Caddyfile.production << EOF +# Production Caddyfile for Matrix Stack +# Deploy this on your SSL termination machine +# Email for Let's Encrypt: ${LETSENCRYPT_EMAIL} + +{ + email ${LETSENCRYPT_EMAIL} + # Enable admin API (restrict access in firewall) + admin 0.0.0.0:2019 +} + +# ========================= +# Matrix Homeserver +# ========================= +${MATRIX_DOMAIN} { + # Well-known client endpoint + @wk path /.well-known/matrix/client + handle @wk { + header Content-Type application/json + respond \`{"m.homeserver":{"base_url":"https://${MATRIX_DOMAIN}"},"m.authentication":{"issuer":"https://${AUTH_DOMAIN}/"}}\` 200 + } + + # Well-known server endpoint (federation) + @wk_server path /.well-known/matrix/server + handle @wk_server { + header Content-Type application/json + respond \`{"m.server":"${MATRIX_DOMAIN}:443"}\` 200 + } + + # Client versions with CORS + @versions path /_matrix/client/versions + handle @versions { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + reverse_proxy ${MATRIX_SERVER_IP}:8008 + } + + # CORS preflight + @preflight { + method OPTIONS + path_regexp matrix ^/_matrix/.*$ + } + handle @preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Access-Control-Max-Age "86400" + respond 204 + } + + # MAS compat endpoints + @compat path /_matrix/client/v3/login* /_matrix/client/v3/logout* /_matrix/client/v3/refresh* /_matrix/client/r0/login* /_matrix/client/r0/logout* /_matrix/client/r0/refresh* + handle @compat { + header Access-Control-Allow-Origin "*" + reverse_proxy ${MATRIX_SERVER_IP}:8080 + } + + # Everything else to Synapse + @matrix_rest path_regexp matrix ^/_matrix/.*$ + handle @matrix_rest { + header Access-Control-Allow-Origin "*" + reverse_proxy ${MATRIX_SERVER_IP}:8008 + } + + handle { + reverse_proxy ${MATRIX_SERVER_IP}:8008 + } +} + +# ========================= +# MAS (OIDC) +# ========================= +${AUTH_DOMAIN} { + # OIDC Discovery + @disco path /.well-known/openid-configuration + handle @disco { + header ?Access-Control-Allow-Origin "*" + reverse_proxy ${MATRIX_SERVER_IP}:8080 + } + + # OAuth2 endpoints + @oauth path /oauth2/* + route @oauth { + header ?Access-Control-Allow-Origin "*" + reverse_proxy ${MATRIX_SERVER_IP}:8080 + } + + # Account portal + handle_path /account/* { + reverse_proxy ${MATRIX_SERVER_IP}:8080 + } + + handle { + reverse_proxy ${MATRIX_SERVER_IP}:8080 + } + + handle_errors { + header ?Access-Control-Allow-Origin "*" + } +} + +# ========================= +# Authelia SSO +# ========================= +${AUTHELIA_DOMAIN} { + reverse_proxy ${AUTHELIA_SERVER_IP}:9091 +} + +# ========================= +# Element Web +# ========================= +${ELEMENT_DOMAIN} { + # Serve config with proper settings + @cfg path /config.json + handle @cfg { + header Content-Type application/json + header Cache-Control no-store + respond \`{"default_server_config":{"m.homeserver":{"base_url":"https://${MATRIX_DOMAIN}","server_name":"${MATRIX_DOMAIN}"}},"default_server_name":"${MATRIX_DOMAIN}","disable_custom_urls":false,"disable_guests":true,"features":{"feature_oidc_aware_navigation":true}}\` 200 + } + + handle { + reverse_proxy ${MATRIX_SERVER_IP}:8090 + } +} +EOF + + print_status "Production Caddyfile created: caddy/Caddyfile.production" + echo "" + + print_info "Production configs generated successfully!" + print_status "Authelia config: authelia/config/configuration.yml" + print_status "Authelia users: authelia/config/users_database.yml" + print_status "Caddy config: caddy/Caddyfile.production" + print_status "Caddy compose: docker-compose.caddy.yml" + print_status "Authelia compose: docker-compose.authelia.yml" + echo "" +fi + +# Check service status +echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" +echo -e "${GREEN}Deployment Complete!${NC}" +echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}" +echo "" + +# Show service status +echo -e "${BLUE}Service Status:${NC}" +$DOCKER_COMPOSE_CMD -f ${COMPOSE_FILE} ps +echo "" + +echo -e "${GREEN}✓ Matrix stack is now running!${NC}" +echo "" + +if [[ "$DEPLOYMENT_MODE" == "local" ]]; then + echo -e "${BLUE}Access Points (HTTPS with self-signed certificates):${NC}" + echo -e " • Element Web: https://${ELEMENT_DOMAIN}" + echo -e " • Matrix API: https://${MATRIX_DOMAIN}" + echo -e " • MAS (Auth): https://${AUTH_DOMAIN}" + echo -e " • Authelia: https://${AUTHELIA_DOMAIN}" + echo -e " • Caddy Admin: http://localhost:2019" + echo "" + echo -e "${YELLOW}⚠ Self-Signed Certificate Warning:${NC}" + echo -e " Your browser will show a security warning because we're using" + echo -e " self-signed certificates for local testing. This is expected!" + echo -e " Click 'Advanced' and 'Proceed to site' to continue." + echo "" + echo -e "${BLUE}Authelia Login Credentials:${NC}" + echo -e " • Username: admin" + echo -e " • Password: ${ADMIN_PASSWORD}" + echo -e " ${RED}⚠ SAVE THIS PASSWORD - you'll need it to log in!${NC}" + echo "" + echo -e "${BLUE}Next Steps:${NC}" + echo -e " 1. Go to https://${ELEMENT_DOMAIN}" + echo -e " 2. Accept the self-signed certificate warning" + echo -e " 3. Click 'Sign In'" + echo -e " 4. You'll be redirected through MAS → Authelia for SSO" + echo -e " 5. Log in with the Authelia credentials above" + echo -e " 6. Set up 2FA (Time-based OTP) for additional security" + echo -e " 7. Complete registration and start chatting!" + echo "" +else + # Production mode + echo -e "${BLUE}Matrix Server Deployed!${NC}" + echo "" + echo -e "${MAGENTA}Production Deployment - Next Steps:${NC}" + echo "" + echo -e "${CYAN}1. Deploy Caddy on your SSL termination machine:${NC}" + echo -e " Generated files:" + echo -e " • caddy/Caddyfile.production" + echo -e " • docker-compose.caddy.yml" + echo -e " • Copy these files to your Caddy machine" + echo -e " • Run: docker compose -f docker-compose.caddy.yml up -d" + echo "" + echo -e "${CYAN}2. Deploy Authelia on your SSO machine:${NC}" + echo -e " Generated files:" + echo -e " • authelia/config/configuration.yml" + echo -e " • authelia/config/users_database.yml" + echo -e " • docker-compose.authelia.yml" + echo -e " • Copy these files to your Authelia machine" + echo -e " • Run: docker compose -f docker-compose.authelia.yml up -d" + echo "" + echo -e "${CYAN}3. Configure DNS:${NC}" + echo -e " Point these domains to your Caddy machine (${MATRIX_SERVER_IP}):" + echo -e " • ${MATRIX_DOMAIN}" + echo -e " • ${ELEMENT_DOMAIN}" + echo -e " • ${AUTH_DOMAIN}" + echo -e " • ${AUTHELIA_DOMAIN}" + echo "" + echo -e "${CYAN}4. Configure Firewall:${NC}" + echo -e " Matrix server (${MATRIX_SERVER_IP}): Allow from Caddy" + echo -e " Authelia server (${AUTHELIA_SERVER_IP}): Allow from Caddy and Matrix" + echo -e " Caddy: Allow ports 80/443 from internet" + echo "" + echo -e "${BLUE}Authelia Login Credentials:${NC}" + echo -e " • Username: admin" + echo -e " • Password: ${ADMIN_PASSWORD}" + echo -e " ${RED}⚠ SAVE THIS PASSWORD!${NC}" + echo "" + echo -e "${BLUE}Access URLs (after DNS and Caddy setup):${NC}" + echo -e " • Element Web: https://${ELEMENT_DOMAIN}" + echo -e " • Matrix API: https://${MATRIX_DOMAIN}" + echo -e " • MAS (Auth): https://${AUTH_DOMAIN}" + echo -e " • Authelia: https://${AUTHELIA_DOMAIN}" + echo "" +fi +echo -e "${BLUE}Useful Commands:${NC}" +echo -e " • View logs: $DOCKER_COMPOSE_CMD logs -f" +echo -e " • Stop stack: $DOCKER_COMPOSE_CMD down" +echo -e " • Restart service: $DOCKER_COMPOSE_CMD restart " +echo -e " • View status: $DOCKER_COMPOSE_CMD ps" +echo "" +echo -e "${BLUE}Generated Files:${NC}" +echo -e " • .env - Environment variables" +echo -e " • authelia_private.pem - Authelia RSA key" +echo -e " • mas-signing.key - MAS signing key" +echo -e " • authelia/config/configuration.yml - Authelia config" +echo -e " • authelia/config/users_database.yml - User accounts" +echo -e " • mas/config/config.yaml - MAS config" +echo -e " • synapse/data/homeserver.yaml - Synapse config" +echo "" +echo -e "${YELLOW}Important Notes:${NC}" +echo -e " • Using example.test domains (not .localhost) to avoid public suffix list issues" +echo -e " • All critical bugfixes have been applied (see BUGFIXES.md for details)" +echo -e " • MAS configured with assets resource, fetch_userinfo enabled, and internal discovery" +echo -e " • Authelia using preferred_username claim (compatible with MAS)" +echo -e " • SSL certificate trust configured for local development" +echo "" +echo -e "${BLUE}Troubleshooting:${NC}" +echo -e " • If CSS is missing: Check that MAS has 'assets' resource in config" +echo -e " • If login fails with empty string error: Verify fetch_userinfo: true in MAS" +echo -e " • If redirect URI error: Check Authelia client redirect_uris include upstream callback" +echo -e " • If SSL errors: Ensure mas/certs/caddy-ca.crt exists and MAS was restarted" +echo -e " • For detailed troubleshooting: See BUGFIXES.md" +echo "" +echo -e "${YELLOW}Security Note:${NC}" +echo -e " This is a local testing deployment with self-signed certificates." +echo -e " For production: Use Let's Encrypt, enable 2FA, and review all configs." +echo -e " See PRODUCTION.md for the distributed deployment guide." +echo "" diff --git a/docker-compose.authelia.yml b/docker-compose.authelia.yml new file mode 100644 index 0000000..fa74a24 --- /dev/null +++ b/docker-compose.authelia.yml @@ -0,0 +1,77 @@ +# Standalone Authelia deployment for production +# Deploy this on a separate machine from Matrix stack for security +# This machine needs: +# - Network access to/from Caddy machine (for SSO redirects) +# - Network access to PostgreSQL on Matrix machine (or local DB) +# - Redis for session storage +# +# Configuration: +# - Update authelia/config/configuration.yml with production domains +# - Configure PostgreSQL connection (local or remote) +# - Set up proper firewall rules + +services: + # Redis for Authelia session storage + redis: + image: redis:7-alpine + container_name: authelia-redis + restart: unless-stopped + networks: + - authelia-network + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # PostgreSQL for Authelia (optional - can use remote DB on Matrix machine) + postgres: + image: postgres:16-alpine + container_name: authelia-postgres + restart: unless-stopped + environment: + POSTGRES_DB: authelia + POSTGRES_USER: authelia + POSTGRES_PASSWORD: ${AUTHELIA_POSTGRES_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - authelia-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U authelia"] + interval: 10s + timeout: 5s + retries: 5 + + # Authelia SSO + authelia: + image: authelia/authelia:latest + container_name: authelia-sso + restart: unless-stopped + environment: + TZ: ${TZ:-Europe/Berlin} + AUTHELIA_SESSION_SECRET: ${AUTHELIA_SESSION_SECRET} + AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} + AUTHELIA_JWT_SECRET: ${AUTHELIA_JWT_SECRET} + POSTGRES_PASSWORD: ${AUTHELIA_POSTGRES_PASSWORD} + volumes: + - ./authelia/config:/config + ports: + - "9091:9091" # Exposed for Caddy reverse proxy + networks: + - authelia-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + +networks: + authelia-network: + driver: bridge + +volumes: + redis-data: + postgres-data: diff --git a/docker-compose.caddy.yml b/docker-compose.caddy.yml new file mode 100644 index 0000000..0811b49 --- /dev/null +++ b/docker-compose.caddy.yml @@ -0,0 +1,35 @@ +# Standalone Caddy deployment for production +# Deploy this on your SSL termination/reverse proxy machine +# This machine should have: +# - Public IP with ports 80/443 accessible +# - DNS A records pointing to this machine +# - Network access to Matrix server machine +# +# Configuration: +# - Update caddy/Caddyfile.production with your domains and backend IPs +# - Ensure proper firewall rules between this and Matrix server + +services: + caddy: + image: caddy:2-alpine + container_name: caddy-proxy + restart: unless-stopped + ports: + - "80:80" # HTTP (redirects to HTTPS) + - "443:443" # HTTPS + - "443:443/udp" # HTTP/3 + - "2019:2019" # Admin API (restrict access!) + volumes: + - ./caddy/Caddyfile.production:/etc/caddy/Caddyfile:ro + - ./caddy/data:/data + - ./caddy/config:/config + networks: + - caddy-network + environment: + - ACME_AGREE=true + # Optional: restrict admin API to localhost only + # command: caddy run --config /etc/caddy/Caddyfile --adapter caddyfile --admin localhost:2019 + +networks: + caddy-network: + driver: bridge diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..73a3e1c --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,196 @@ +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: matrix-postgres + restart: unless-stopped + environment: + POSTGRES_DB: synapse + POSTGRES_USER: synapse + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C + volumes: + - ./postgres/data:/var/lib/postgresql/data + - ./postgres/init:/docker-entrypoint-initdb.d + networks: + - matrix-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U synapse"] + interval: 10s + timeout: 5s + retries: 5 + + # Matrix Synapse Server + synapse: + image: matrixdotorg/synapse:latest + container_name: matrix-synapse + restart: unless-stopped + environment: + SYNAPSE_CONFIG_PATH: /data/homeserver.yaml + volumes: + - ./synapse/data:/data + # Ports exposed only to internal network - access via Caddy + expose: + - "8008" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8008/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Element Web Client + element: + image: vectorim/element-web:latest + container_name: matrix-element + restart: unless-stopped + volumes: + - ./element/config/config.json:/app/config.json:ro + # Accessed via Caddy + expose: + - "80" + networks: + - matrix-network + depends_on: + - synapse + + # Redis for Authelia session storage + redis: + image: redis:7-alpine + container_name: matrix-redis + restart: unless-stopped + networks: + - matrix-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Authelia SSO + authelia: + image: authelia/authelia:latest + container_name: matrix-authelia + restart: unless-stopped + environment: + TZ: Europe/Berlin + AUTHELIA_SESSION_SECRET: ${AUTHELIA_SESSION_SECRET} + AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} + AUTHELIA_JWT_SECRET: ${AUTHELIA_JWT_SECRET} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - ./authelia/config:/config + # Accessed via Caddy + expose: + - "9091" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + # Matrix Authentication Service (MAS) + mas: + image: ghcr.io/element-hq/matrix-authentication-service:latest + container_name: matrix-mas + restart: unless-stopped + environment: + MAS_CONFIG: /config/config.yaml + SSL_CERT_FILE: /certs/caddy-ca.crt + volumes: + - ./mas/config:/config:ro + - ./mas/data:/data + - ./mas/certs:/certs:ro + # Accessed via Caddy + expose: + - "8080" + - "8081" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + authelia: + condition: service_started + extra_hosts: + - "authelia.example.test:host-gateway" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Caddy Reverse Proxy (HTTPS termination) + caddy: + image: caddy:2-alpine + container_name: matrix-caddy + restart: unless-stopped + ports: + - "443:443" + - "80:80" + - "2019:2019" # Admin API + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - ./caddy/data:/data + - ./caddy/config:/config + networks: + - matrix-network + depends_on: + - synapse + - element + - mas + - authelia + + # mautrix-telegram Bridge + mautrix-telegram: + image: dock.mau.dev/mautrix/telegram:latest + container_name: matrix-bridge-telegram + restart: unless-stopped + volumes: + - ./bridges/telegram/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-whatsapp Bridge + mautrix-whatsapp: + image: dock.mau.dev/mautrix/whatsapp:latest + container_name: matrix-bridge-whatsapp + restart: unless-stopped + volumes: + - ./bridges/whatsapp/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-signal Bridge + mautrix-signal: + image: dock.mau.dev/mautrix/signal:latest + container_name: matrix-bridge-signal + restart: unless-stopped + volumes: + - ./bridges/signal/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + +networks: + matrix-network: + driver: bridge + +volumes: + postgres-data: + synapse-data: + mas-data: diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 0000000..b57e19f --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,152 @@ +# Production docker-compose file for Matrix stack +# This assumes: +# - Authelia is running on a separate machine +# - Caddy is handling SSL termination on a separate machine +# - This file runs the Matrix core services only + +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: matrix-postgres + restart: unless-stopped + environment: + POSTGRES_DB: synapse + POSTGRES_USER: synapse + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C + volumes: + - ./postgres/data:/var/lib/postgresql/data + - ./postgres/init:/docker-entrypoint-initdb.d + networks: + - matrix-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U synapse"] + interval: 10s + timeout: 5s + retries: 5 + + # Matrix Synapse Server + synapse: + image: matrixdotorg/synapse:latest + container_name: matrix-synapse + restart: unless-stopped + environment: + SYNAPSE_CONFIG_PATH: /data/homeserver.yaml + volumes: + - ./synapse/data:/data + ports: + # Expose ports for Caddy reverse proxy + - "127.0.0.1:8008:8008" + - "8448:8448" # Federation port (needs to be public or proxied) + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8008/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Element Web Client + element: + image: vectorim/element-web:latest + container_name: matrix-element + restart: unless-stopped + volumes: + - ./element/config/config.json:/app/config.json:ro + ports: + - "127.0.0.1:8090:80" + networks: + - matrix-network + depends_on: + - synapse + + # Redis for MAS sessions + redis: + image: redis:7-alpine + container_name: matrix-redis + restart: unless-stopped + networks: + - matrix-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Matrix Authentication Service (MAS) + mas: + image: ghcr.io/element-hq/matrix-authentication-service:latest + container_name: matrix-mas + restart: unless-stopped + environment: + MAS_CONFIG: /config/config.yaml + volumes: + - ./mas/config:/config:ro + - ./mas/data:/data + ports: + - "127.0.0.1:8080:8080" + - "127.0.0.1:8081:8081" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/health"] + interval: 30s + timeout: 10s + retries: 3 + + # mautrix-telegram Bridge + mautrix-telegram: + image: dock.mau.dev/mautrix/telegram:latest + container_name: matrix-bridge-telegram + restart: unless-stopped + volumes: + - ./bridges/telegram/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-whatsapp Bridge + mautrix-whatsapp: + image: dock.mau.dev/mautrix/whatsapp:latest + container_name: matrix-bridge-whatsapp + restart: unless-stopped + volumes: + - ./bridges/whatsapp/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-signal Bridge + mautrix-signal: + image: dock.mau.dev/mautrix/signal:latest + container_name: matrix-bridge-signal + restart: unless-stopped + volumes: + - ./bridges/signal/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + +networks: + matrix-network: + driver: bridge + +volumes: + postgres-data: + synapse-data: + mas-data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cf9c843 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,192 @@ +services: + # PostgreSQL Database + postgres: + image: postgres:16-alpine + container_name: matrix-postgres + restart: unless-stopped + environment: + POSTGRES_DB: synapse + POSTGRES_USER: synapse + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C + volumes: + - ./postgres/data:/var/lib/postgresql/data + - ./postgres/init:/docker-entrypoint-initdb.d + networks: + - matrix-network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U synapse"] + interval: 10s + timeout: 5s + retries: 5 + + # Matrix Synapse Server + synapse: + image: matrixdotorg/synapse:latest + container_name: matrix-synapse + restart: unless-stopped + environment: + SYNAPSE_CONFIG_PATH: /data/homeserver.yaml + volumes: + - ./synapse/data:/data + # Ports exposed only to internal network - access via Caddy + expose: + - "8008" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8008/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Element Web Client + element: + image: vectorim/element-web:latest + container_name: matrix-element + restart: unless-stopped + volumes: + - ./element/config/config.json:/app/config.json:ro + # Accessed via Caddy + expose: + - "80" + networks: + - matrix-network + depends_on: + - synapse + + # Redis for Authelia session storage + redis: + image: redis:7-alpine + container_name: matrix-redis + restart: unless-stopped + networks: + - matrix-network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # Authelia SSO + authelia: + image: authelia/authelia:latest + container_name: matrix-authelia + restart: unless-stopped + environment: + TZ: Europe/Berlin + AUTHELIA_SESSION_SECRET: ${AUTHELIA_SESSION_SECRET} + AUTHELIA_STORAGE_ENCRYPTION_KEY: ${AUTHELIA_STORAGE_ENCRYPTION_KEY} + AUTHELIA_JWT_SECRET: ${AUTHELIA_JWT_SECRET} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - ./authelia/config:/config + # Accessed via Caddy + expose: + - "9091" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + # Matrix Authentication Service (MAS) + mas: + image: ghcr.io/element-hq/matrix-authentication-service:latest + container_name: matrix-mas + restart: unless-stopped + environment: + MAS_CONFIG: /config/config.yaml + volumes: + - ./mas/config:/config:ro + - ./mas/data:/data + # Accessed via Caddy + expose: + - "8080" + - "8081" + networks: + - matrix-network + depends_on: + postgres: + condition: service_healthy + authelia: + condition: service_started + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8081/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Caddy Reverse Proxy (HTTPS termination) + caddy: + image: caddy:2-alpine + container_name: matrix-caddy + restart: unless-stopped + ports: + - "443:443" + - "80:80" + - "2019:2019" # Admin API + volumes: + - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro + - ./caddy/data:/data + - ./caddy/config:/config + networks: + - matrix-network + depends_on: + - synapse + - element + - mas + - authelia + + # mautrix-telegram Bridge + mautrix-telegram: + image: dock.mau.dev/mautrix/telegram:latest + container_name: matrix-bridge-telegram + restart: unless-stopped + volumes: + - ./bridges/telegram/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-whatsapp Bridge + mautrix-whatsapp: + image: dock.mau.dev/mautrix/whatsapp:latest + container_name: matrix-bridge-whatsapp + restart: unless-stopped + volumes: + - ./bridges/whatsapp/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + + # mautrix-signal Bridge + mautrix-signal: + image: dock.mau.dev/mautrix/signal:latest + container_name: matrix-bridge-signal + restart: unless-stopped + volumes: + - ./bridges/signal/config:/data + networks: + - matrix-network + depends_on: + synapse: + condition: service_healthy + +networks: + matrix-network: + driver: bridge + +volumes: + postgres-data: + synapse-data: + mas-data: diff --git a/postgres/init/01-init-databases.sql b/postgres/init/01-init-databases.sql new file mode 100644 index 0000000..82ed7a4 --- /dev/null +++ b/postgres/init/01-init-databases.sql @@ -0,0 +1,15 @@ +-- Create additional databases for Matrix Authentication Service and Authelia +-- The main 'synapse' database is already created via POSTGRES_DB env var + +-- Create database for Matrix Authentication Service (MAS) +CREATE DATABASE mas; + +-- Create database for Authelia +CREATE DATABASE authelia; + +-- Grant privileges to the synapse user for all databases +GRANT ALL PRIVILEGES ON DATABASE mas TO synapse; +GRANT ALL PRIVILEGES ON DATABASE authelia TO synapse; + +-- Display confirmation +\echo 'Additional databases created: mas, authelia' diff --git a/requirements.md b/requirements.md new file mode 100644 index 0000000..853250b --- /dev/null +++ b/requirements.md @@ -0,0 +1,11 @@ +* matrix synapse with element web +* matrix authentication service (MAS) with SSO auth via Authelia +* Postgres DB +* Bridges for telegram, whatsapp, signal +* All running on a single machine with docker compose +* element-x as mobile client + +Target deployment: +* authelia runs on a separate machine +* SSL termination via caddy on a separate machine +* We can run both locally for testing, but keep this in mind for the production setup \ No newline at end of file diff --git a/research.md b/research.md new file mode 100644 index 0000000..b1cf4dd --- /dev/null +++ b/research.md @@ -0,0 +1,297 @@ +https://github.com/spantaleev/matrix-docker-ansible-deploy +https://github.com/element-hq/ess-helm + + +Caddyfile inspiration: +# ========================= +# Matrix Services (client + federation) +# ========================= +matrix.mair.io, matrix.mair.io:8448, matrix.mair.is, matrix.mair.is:8448 { + + # Well-known (public) + @wk path /.well-known/matrix/client + handle @wk { + header Content-Type application/json + respond `{"m.homeserver":{"base_url":"https://matrix.mair.io"},"m.authentication":{"m.oauth2":{"issuer":"https://account.matrix.mair.io> + } + # Client versions endpoint (add CORS headers) + @versions path /_matrix/client/versions + handle @versions { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy http://matrix.horn:8008 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + header_down X-Routed-By SYNAPSE-VERSIONS + } + } + + + +root@caddy:/etc/caddy/conf.d# cat matrix.caddyfile +# ========================= +# Matrix Services (client + federation) +# ========================= +matrix.mair.io, matrix.mair.io:8448, matrix.mair.is, matrix.mair.is:8448 { + + # Well-known (public) + @wk path /.well-known/matrix/client + handle @wk { + header Content-Type application/json + respond `{"m.homeserver":{"base_url":"https://matrix.mair.io"},"m.authentication":{"m.oauth2":{"issuer":"https://account.matrix.mair.io"}}}` + } + # Client versions endpoint (add CORS headers) + @versions path /_matrix/client/versions + handle @versions { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy http://matrix.horn:8008 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + header_down X-Routed-By SYNAPSE-VERSIONS + } + } + + # CORS preflight for auth metadata + @auth_preflight { + method OPTIONS + path /_matrix/client/unstable/org.matrix.msc2965/auth_metadata + } + handle @auth_preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Access-Control-Max-Age "86400" + respond 204 + } + + # CORS preflight for all other Matrix API + @preflight { + method OPTIONS + path_regexp matrix ^/_matrix/.*$ + } + handle @preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Access-Control-Max-Age "86400" + respond 204 + } + + # Authentication metadata endpoint - handle locally since MAS doesn't support it + @auth_metadata path /_matrix/client/unstable/org.matrix.msc2965/auth_metadata + handle @auth_metadata { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Content-Type "application/json" + respond `{"issuer":"https://account.matrix.mair.io/","authorization_endpoint":"https://account.matrix.mair.io/oauth2/authorize","token_endpoint":"https://account.matrix.mair.io/oauth2/token","userinfo_endpoint":"https://account.matrix.mair.io/oauth2/userinfo","jwks_uri":"https://account.matrix.mair.io/oauth2/keys.json","registration_endpoint":"https://account.matrix.mair.io/oauth2/registration","scopes_supported":["openid","profile","email"],"response_types_supported":["code"],"grant_types_supported":["authorization_code","refresh_token"],"code_challenge_methods_supported":["S256"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","none"],"revocation_endpoint":"https://account.matrix.mair.io/oauth2/revoke","account_management_uri":"https://account.matrix.mair.io/account/","account_management_actions_supported":["org.matrix.profile","org.matrix.sessions_list","org.matrix.session_view","org.matrix.session_end","org.matrix.cross_signing_reset"]}` 200 + } + + # MAS compat endpoints (login/logout/refresh + subpaths) - add CORS headers + @compat path \ + /_matrix/client/v3/login* \ + /_matrix/client/v3/logout* \ + /_matrix/client/v3/refresh* \ + /_matrix/client/r0/login* \ + /_matrix/client/r0/logout* \ + /_matrix/client/r0/refresh* + handle @compat { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy http://matrix.horn:8080 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + header_down X-Routed-By MAS + } + } + + # MSC2965 SSO redirect (add CORS headers) + @msc2965 path /_matrix/client/unstable/org.matrix.msc2965/login/sso/* + handle @msc2965 { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy http://matrix.horn:8080 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + header_down X-Routed-By MAS-MSC2965 + } + } + + # Everything else under /_matrix → Synapse (add CORS headers) + @matrix_rest path_regexp matrix ^/_matrix/.*$ + handle @matrix_rest { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + header Access-Control-Allow-Headers "Authorization, Content-Type, Accept" + header Vary "Origin, Access-Control-Request-Method, Access-Control-Request-Headers" + reverse_proxy http://matrix.horn:8008 { + header_down -Access-Control-Allow-Origin + header_down -Access-Control-Allow-Methods + header_down -Access-Control-Allow-Headers + header_down -Vary + header_down X-Routed-By SYNAPSE + } + } + + # Anything not /_matrix/* -> Synapse + handle { + reverse_proxy http://matrix.horn:8008 + } + + import common_logging "matrix" +} + +# ========================= +# MAS (OIDC) service +# ========================= +account.matrix.mair.io account.matrix.mair.is { + import common_security + import common_logging "matrix-mas" + + # === OIDC Discovery === + @disco path /.well-known/openid-configuration + handle @disco { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + reverse_proxy matrix.horn:8080 + } + + # === Dynamic Client Registration: CORS preflight === + @reg_opts { + method OPTIONS + path /oauth2/registration + } + handle @reg_opts { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "POST, OPTIONS" + header ?Access-Control-Allow-Headers "*" + respond 204 + } + + # === Dynamic Client Registration (POST) === + @reg path /oauth2/registration + route @reg { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "POST, OPTIONS" + header ?Access-Control-Allow-Headers "*" + reverse_proxy matrix.horn:8080 + } + + # === JWKS preflight === + @jwks_opts { + method OPTIONS + path /oauth2/keys.json + } + handle @jwks_opts { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + respond 204 + } + + # === Map keys.json → /oauth2/jwks (MAS) === + @jwksjson path /oauth2/keys.json + route @jwksjson { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS" + header ?Access-Control-Allow-Headers "*" + uri replace /oauth2/keys.json /oauth2/jwks + reverse_proxy matrix.horn:8080 + } + + # === Generic OAuth2 endpoints === + @oauth path /oauth2/* + route @oauth { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Methods "GET, OPTIONS, POST" + header ?Access-Control-Allow-Headers "*" + reverse_proxy matrix.horn:8080 + } + + # Account portal + handle_path /account/* { + reverse_proxy matrix.horn:8080 + } + + # Fallback: everything else to MAS + handle { + reverse_proxy matrix.horn:8080 + } + + # Helpful: add CORS even on error responses so the browser console isn't misleading + handle_errors { + header ?Access-Control-Allow-Origin "*" + header ?Access-Control-Allow-Headers "*" + header ?Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" + } +} + +# ========================= +# Element Web Client +# ========================= +element.mair.io, element.mair.is { + @cfg1 path /config.json + handle @cfg1 { + header Content-Type application/json + header Cache-Control no-store + respond `{ + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.mair.io", + "server_name": "mair.io" + } + }, + "default_server_name": "mair.io", + "disable_custom_urls": true, + "disable_guests": true, + "features": { + "feature_oidc_aware_navigation": true + } + }` 200 + } + + @cfg2 path /config.element.mair.io.json + handle @cfg2 { + header Content-Type application/json + header Cache-Control no-store + respond `{ + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.mair.io", + "server_name": "mair.io" + } + }, + "default_server_name": "mair.io", + "disable_custom_urls": true, + "disable_guests": true, + "features": { + "feature_oidc_aware_navigation": true + } + }` 200 + } + + # Your app (add auth back if you want; keep config paths public) + handle { + reverse_proxy http://matrix.horn:80 + } + + import common_logging "element" +} \ No newline at end of file diff --git a/setup-bridges.sh b/setup-bridges.sh new file mode 100755 index 0000000..d9a836c --- /dev/null +++ b/setup-bridges.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Setup script for Matrix bridges + +set -e + +echo "Setting up Matrix bridges..." +echo "" + +# Function to setup a bridge +setup_bridge() { + local bridge_name=$1 + local bridge_image=$2 + local config_dir=$3 + + echo "Setting up $bridge_name bridge..." + + # Generate config if it doesn't exist + if [ ! -f "$config_dir/config.yaml" ]; then + echo " Generating config for $bridge_name..." + docker run --rm \ + -v $(pwd)/$config_dir:/data \ + $bridge_image + + echo " ✓ Config generated at $config_dir/config.yaml" + echo " ! Please edit the config file before starting the bridge" + echo "" + else + echo " Config already exists at $config_dir/config.yaml" + echo "" + fi +} + +# Check if Synapse is configured +if [ ! -f "./synapse/data/homeserver.yaml" ]; then + echo "Error: Synapse must be configured first. Run ./setup-synapse.sh" + exit 1 +fi + +# Setup each bridge +echo "=== Setting up Telegram Bridge ===" +setup_bridge "Telegram" "dock.mau.dev/mautrix/telegram:latest" "bridges/telegram/config" + +echo "=== Setting up WhatsApp Bridge ===" +setup_bridge "WhatsApp" "dock.mau.dev/mautrix/whatsapp:latest" "bridges/whatsapp/config" + +echo "=== Setting up Signal Bridge ===" +setup_bridge "Signal" "dock.mau.dev/mautrix/signal:latest" "bridges/signal/config" + +echo "" +echo "Bridge setup complete!" +echo "" +echo "Next steps:" +echo "1. Edit each bridge config file to set:" +echo " - homeserver address: http://synapse:8008" +echo " - homeserver domain: matrix.localhost" +echo " - as_token and hs_token (generated in configs)" +echo "2. Copy the registration YAML files to synapse/data/" +echo "3. Add them to synapse homeserver.yaml app_service_config_files section" +echo "4. Restart the stack" +echo "" diff --git a/setup-synapse.sh b/setup-synapse.sh new file mode 100755 index 0000000..cd1b0e7 --- /dev/null +++ b/setup-synapse.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Generate initial Synapse configuration + +set -e + +echo "Generating Matrix Synapse configuration..." + +# Source environment variables +if [ -f .env ]; then + export $(cat .env | grep -v '^#' | xargs) +fi + +# Generate config using Docker +docker run -it --rm \ + -v $(pwd)/synapse/data:/data \ + -e SYNAPSE_SERVER_NAME=${SERVER_NAME:-matrix.localhost} \ + -e SYNAPSE_REPORT_STATS=${SYNAPSE_REPORT_STATS:-no} \ + matrixdotorg/synapse:latest generate + +echo "" +echo "Synapse configuration generated in ./synapse/data/" +echo "You now need to edit ./synapse/data/homeserver.yaml to:" +echo " 1. Configure PostgreSQL database connection" +echo " 2. Enable registration (if desired)" +echo " 3. Configure MAS integration" +echo "" diff --git a/validate-setup.sh b/validate-setup.sh new file mode 100755 index 0000000..a4cc73a --- /dev/null +++ b/validate-setup.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# Validation script to check if the setup is ready + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "=== Matrix Stack Setup Validation ===" +echo "" + +# Track overall status +ERRORS=0 +WARNINGS=0 + +check_file() { + if [ -f "$1" ]; then + echo -e "${GREEN}✓${NC} $1 exists" + return 0 + else + echo -e "${RED}✗${NC} $1 missing" + ERRORS=$((ERRORS+1)) + return 1 + fi +} + +check_dir() { + if [ -d "$1" ]; then + echo -e "${GREEN}✓${NC} $1 exists" + return 0 + else + echo -e "${RED}✗${NC} $1 missing" + ERRORS=$((ERRORS+1)) + return 1 + fi +} + +warn() { + echo -e "${YELLOW}⚠${NC} $1" + WARNINGS=$((WARNINGS+1)) +} + +# Check essential files +echo "Checking essential files..." +check_file ".env" +check_file "docker-compose.yml" +check_file "element/config/config.json" +check_file "authelia/config/configuration.yml" +check_file "authelia/config/users_database.yml" +check_file "mas/config/config.yaml" +check_file "postgres/init/01-init-databases.sql" +echo "" + +# Check if Synapse is configured +echo "Checking Synapse configuration..." +if check_file "synapse/data/homeserver.yaml"; then + # Check for PostgreSQL config + if grep -q "psycopg2" synapse/data/homeserver.yaml; then + echo -e "${GREEN}✓${NC} Synapse configured for PostgreSQL" + else + warn "Synapse may not be configured for PostgreSQL" + echo " Check database section in synapse/data/homeserver.yaml" + fi + + # Check for MAS config + if grep -q "msc3861" synapse/data/homeserver.yaml; then + echo -e "${GREEN}✓${NC} Synapse configured for MAS" + else + warn "Synapse may not be configured for MAS" + echo " Add experimental_features.msc3861 to homeserver.yaml" + fi +else + echo -e "${YELLOW}⚠${NC} Run ./setup-synapse.sh to generate Synapse config" +fi +echo "" + +# Check .env for default values +echo "Checking .env for default/insecure values..." +if [ -f ".env" ]; then + if grep -q "changeme" .env; then + warn "Found 'changeme' in .env - update with secure values" + fi + + if grep -q "matrix.localhost" .env; then + echo -e "${GREEN}ℹ${NC} Using matrix.localhost (OK for local testing)" + fi +fi +echo "" + +# Check Authelia users +echo "Checking Authelia users configuration..." +if [ -f "authelia/config/users_database.yml" ]; then + if grep -q "..." authelia/config/users_database.yml; then + warn "Authelia users database contains placeholder password hashes" + echo " Generate password hash with:" + echo " docker run authelia/authelia:latest authelia crypto hash generate argon2 --password 'yourpassword'" + else + echo -e "${GREEN}✓${NC} Authelia users appear to be configured" + fi +fi +echo "" + +# Check Authelia RSA key +echo "Checking Authelia OIDC configuration..." +if grep -q "BEGIN RSA PRIVATE KEY" authelia/config/configuration.yml; then + if grep -q "# Generate with:" authelia/config/configuration.yml; then + warn "Authelia OIDC key is placeholder - generate real RSA key" + echo " Generate with: openssl genrsa -out authelia_private.pem 4096" + else + echo -e "${GREEN}✓${NC} Authelia OIDC key appears to be configured" + fi +else + warn "Authelia OIDC key missing or invalid" +fi +echo "" + +# Check MAS signing key +echo "Checking MAS signing key..." +if [ -f "mas/config/config.yaml" ]; then + if grep -q "BEGIN PRIVATE KEY" mas/config/config.yaml; then + if grep -q "# Generate your own" mas/config/config.yaml; then + warn "MAS signing key is placeholder - generate real key" + echo " Generate with: openssl genrsa 4096 | openssl pkcs8 -topk8 -nocrypt" + else + echo -e "${GREEN}✓${NC} MAS signing key appears to be configured" + fi + else + warn "MAS signing key missing or invalid" + fi +fi +echo "" + +# Check if Docker is running +echo "Checking Docker..." +if docker info > /dev/null 2>&1; then + echo -e "${GREEN}✓${NC} Docker is running" +else + echo -e "${RED}✗${NC} Docker is not running or not accessible" + ERRORS=$((ERRORS+1)) +fi +echo "" + +# Check if containers are running +echo "Checking running containers..." +if docker compose ps > /dev/null 2>&1; then + RUNNING=$(docker compose ps --services --filter "status=running" | wc -l) + TOTAL=$(docker compose ps --services | wc -l) + + if [ $RUNNING -eq $TOTAL ] && [ $TOTAL -gt 0 ]; then + echo -e "${GREEN}✓${NC} All containers are running ($RUNNING/$TOTAL)" + elif [ $RUNNING -gt 0 ]; then + warn "Some containers are not running ($RUNNING/$TOTAL)" + echo " Run: docker compose ps" + else + echo -e "${YELLOW}ℹ${NC} No containers running yet" + echo " Start with: docker compose up -d" + fi +fi +echo "" + +# Summary +echo "===================================" +if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then + echo -e "${GREEN}✓ Setup validation passed!${NC}" + echo "You should be ready to start the stack." +elif [ $ERRORS -eq 0 ]; then + echo -e "${YELLOW}⚠ Setup has $WARNINGS warning(s)${NC}" + echo "Review the warnings above before proceeding." +else + echo -e "${RED}✗ Setup has $ERRORS error(s) and $WARNINGS warning(s)${NC}" + echo "Fix the errors above before starting the stack." + exit 1 +fi +echo "" + +echo "Next steps:" +echo "1. Review CHECKLIST.md for detailed setup steps" +echo "2. Read SETUP.md for comprehensive instructions" +echo "3. Start the stack: docker compose up -d" +echo "4. Check logs: docker compose logs -f"