feat(#384): Add Synapse + Element Web to docker-compose for dev
All checks were successful
ci/woodpecker/push/infra Pipeline was successful

- Create docker-compose.matrix.yml as optional dev overlay
- Add Synapse homeserver config with shared PostgreSQL
- Add Element Web client config (port 8501)
- Add bot account setup script (docker/matrix/scripts/setup-bot.sh)
- Add Makefile targets: matrix-up, matrix-down, matrix-logs, matrix-setup-bot
- Document Matrix env vars in .env.example
- Synapse accessible at localhost:8008, Element at localhost:8501
- Usage: docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up

Refs #384

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 02:02:22 -06:00
parent 6e4236b359
commit 4a5cb6441e
6 changed files with 542 additions and 1 deletions

View File

@@ -0,0 +1,30 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://localhost:8008",
"server_name": "localhost"
}
},
"brand": "Mosaic Stack Dev",
"default_theme": "dark",
"room_directory": {
"servers": ["localhost"]
},
"features": {
"feature_video_rooms": false,
"feature_group_calls": false
},
"show_labs_settings": true,
"piwik": false,
"posthog": {
"enabled": false
},
"privacy_policy_url": null,
"terms_and_conditions_links": [],
"setting_defaults": {
"breadcrumbs": true,
"custom_themes": []
},
"disable_guests": true,
"disable_3pid_login": true
}

View File

@@ -0,0 +1,210 @@
#!/usr/bin/env bash
# ==============================================
# Matrix Bot Account Setup Script
# ==============================================
#
# Creates the Mosaic bot user on the local Synapse instance and retrieves
# an access token. Idempotent — safe to run multiple times.
#
# Usage:
# docker/matrix/scripts/setup-bot.sh
# docker/matrix/scripts/setup-bot.sh --username custom-bot --password custom-pass
#
# Prerequisites:
# - Synapse must be running (docker compose -f ... up synapse)
# - Synapse must be healthy (check with: curl http://localhost:8008/health)
#
# Output:
# Prints the environment variables needed for MatrixService configuration.
#
# ==============================================
set -euo pipefail
# Defaults
SYNAPSE_URL="${SYNAPSE_URL:-http://localhost:8008}"
BOT_USERNAME="${BOT_USERNAME:-mosaic-bot}"
BOT_PASSWORD="${BOT_PASSWORD:-mosaic-bot-dev-password}"
BOT_DISPLAY_NAME="${BOT_DISPLAY_NAME:-Mosaic Bot}"
ADMIN_USERNAME="${ADMIN_USERNAME:-admin}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-admin-dev-password}"
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--username) BOT_USERNAME="$2"; shift 2 ;;
--password) BOT_PASSWORD="$2"; shift 2 ;;
--synapse-url) SYNAPSE_URL="$2"; shift 2 ;;
--admin-username) ADMIN_USERNAME="$2"; shift 2 ;;
--admin-password) ADMIN_PASSWORD="$2"; shift 2 ;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --username NAME Bot username (default: mosaic-bot)"
echo " --password PASS Bot password (default: mosaic-bot-dev-password)"
echo " --synapse-url URL Synapse URL (default: http://localhost:8008)"
echo " --admin-username NAME Admin username (default: admin)"
echo " --admin-password PASS Admin password (default: admin-dev-password)"
echo " --help, -h Show this help"
exit 0
;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
echo "=== Mosaic Stack — Matrix Bot Setup ==="
echo ""
echo "Synapse URL: ${SYNAPSE_URL}"
echo "Bot username: ${BOT_USERNAME}"
echo ""
# Wait for Synapse to be ready
echo "Checking Synapse health..."
for i in $(seq 1 30); do
if curl -fsSo /dev/null "${SYNAPSE_URL}/health" 2>/dev/null; then
echo "Synapse is healthy."
break
fi
if [ "$i" -eq 30 ]; then
echo "ERROR: Synapse is not responding at ${SYNAPSE_URL}/health after 30 attempts."
echo "Make sure Synapse is running:"
echo " docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -d"
exit 1
fi
echo " Waiting for Synapse... (attempt ${i}/30)"
sleep 2
done
echo ""
# Step 1: Register admin account (if not exists)
echo "Step 1: Registering admin account '${ADMIN_USERNAME}'..."
ADMIN_REGISTER_RESPONSE=$(curl -sS -X POST "${SYNAPSE_URL}/_synapse/admin/v1/register" \
-H "Content-Type: application/json" \
-d "{}" 2>/dev/null || true)
NONCE=$(echo "${ADMIN_REGISTER_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('nonce',''))" 2>/dev/null || true)
if [ -n "${NONCE}" ]; then
# Generate HMAC for admin registration using the nonce
# For dev, we use register_new_matrix_user via docker exec instead
echo " Using docker exec to register admin via Synapse CLI..."
docker exec mosaic-synapse register_new_matrix_user \
-u "${ADMIN_USERNAME}" \
-p "${ADMIN_PASSWORD}" \
-a \
-c /data/homeserver.yaml \
http://localhost:8008 2>/dev/null && echo " Admin account created." || echo " Admin account already exists (or registration failed — continuing)."
else
echo " Attempting registration via docker exec..."
docker exec mosaic-synapse register_new_matrix_user \
-u "${ADMIN_USERNAME}" \
-p "${ADMIN_PASSWORD}" \
-a \
-c /data/homeserver.yaml \
http://localhost:8008 2>/dev/null && echo " Admin account created." || echo " Admin account already exists (or registration failed — continuing)."
fi
echo ""
# Step 2: Get admin access token
echo "Step 2: Obtaining admin access token..."
ADMIN_LOGIN_RESPONSE=$(curl -sS -X POST "${SYNAPSE_URL}/_matrix/client/v3/login" \
-H "Content-Type: application/json" \
-d "{
\"type\": \"m.login.password\",
\"identifier\": {
\"type\": \"m.id.user\",
\"user\": \"${ADMIN_USERNAME}\"
},
\"password\": \"${ADMIN_PASSWORD}\"
}" 2>/dev/null)
ADMIN_TOKEN=$(echo "${ADMIN_LOGIN_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || true)
if [ -z "${ADMIN_TOKEN}" ]; then
echo "ERROR: Could not obtain admin access token."
echo "Response: ${ADMIN_LOGIN_RESPONSE}"
echo ""
echo "Try registering the admin account manually:"
echo " docker exec -it mosaic-synapse register_new_matrix_user -u ${ADMIN_USERNAME} -p ${ADMIN_PASSWORD} -a -c /data/homeserver.yaml http://localhost:8008"
exit 1
fi
echo " Admin token obtained."
echo ""
# Step 3: Register bot account via admin API (idempotent)
echo "Step 3: Registering bot account '${BOT_USERNAME}'..."
BOT_REGISTER_RESPONSE=$(curl -sS -X PUT "${SYNAPSE_URL}/_synapse/admin/v2/users/@${BOT_USERNAME}:localhost" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"password\": \"${BOT_PASSWORD}\",
\"displayname\": \"${BOT_DISPLAY_NAME}\",
\"admin\": false,
\"deactivated\": false
}" 2>/dev/null)
BOT_EXISTS=$(echo "${BOT_REGISTER_RESPONSE}" | python3 -c "import sys,json; d=json.load(sys.stdin); print('yes' if d.get('name') else 'no')" 2>/dev/null || echo "no")
if [ "${BOT_EXISTS}" = "yes" ]; then
echo " Bot account '@${BOT_USERNAME}:localhost' is ready."
else
echo " WARNING: Bot registration response unexpected: ${BOT_REGISTER_RESPONSE}"
echo " Continuing anyway — bot may already exist."
fi
echo ""
# Step 4: Get bot access token
echo "Step 4: Obtaining bot access token..."
BOT_LOGIN_RESPONSE=$(curl -sS -X POST "${SYNAPSE_URL}/_matrix/client/v3/login" \
-H "Content-Type: application/json" \
-d "{
\"type\": \"m.login.password\",
\"identifier\": {
\"type\": \"m.id.user\",
\"user\": \"${BOT_USERNAME}\"
},
\"password\": \"${BOT_PASSWORD}\"
}" 2>/dev/null)
BOT_TOKEN=$(echo "${BOT_LOGIN_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || true)
if [ -z "${BOT_TOKEN}" ]; then
echo "ERROR: Could not obtain bot access token."
echo "Response: ${BOT_LOGIN_RESPONSE}"
exit 1
fi
echo " Bot token obtained."
echo ""
# Step 5: Output configuration
echo "============================================"
echo " Matrix Bot Setup Complete"
echo "============================================"
echo ""
echo "Add the following to your .env file:"
echo ""
echo " # Matrix Bridge Configuration"
echo " MATRIX_HOMESERVER_URL=http://localhost:8008"
echo " MATRIX_ACCESS_TOKEN=${BOT_TOKEN}"
echo " MATRIX_BOT_USER_ID=@${BOT_USERNAME}:localhost"
echo " MATRIX_SERVER_NAME=localhost"
echo ""
echo "Or, if running the API inside Docker (same compose network):"
echo ""
echo " MATRIX_HOMESERVER_URL=http://synapse:8008"
echo " MATRIX_ACCESS_TOKEN=${BOT_TOKEN}"
echo " MATRIX_BOT_USER_ID=@${BOT_USERNAME}:localhost"
echo " MATRIX_SERVER_NAME=localhost"
echo ""
echo "Element Web is available at: http://localhost:8501"
echo " Login with any registered user to test messaging."
echo ""
echo "Admin account: ${ADMIN_USERNAME} / ${ADMIN_PASSWORD}"
echo "Bot account: ${BOT_USERNAME} / ${BOT_PASSWORD}"
echo "============================================"

View File

@@ -0,0 +1,131 @@
# ==============================================
# Synapse Homeserver Configuration — Development Only
# ==============================================
#
# This config is for LOCAL DEVELOPMENT with the Mosaic Stack docker-compose overlay.
# Do NOT use this in production. See docker-compose.sample.matrix.yml for production.
#
# Server name is set to 'localhost' — this is permanent and cannot be changed
# after the database has been initialized.
#
# ==============================================
server_name: "localhost"
pid_file: /data/homeserver.pid
public_baseurl: "http://localhost:8008/"
# ======================
# Network Listeners
# ======================
listeners:
# Client API (used by Element Web, Mosaic bridge, etc.)
- port: 8008
tls: false
type: http
x_forwarded: true
bind_addresses: ["0.0.0.0"]
resources:
- names: [client, federation]
compress: false
# ======================
# Database (Shared PostgreSQL)
# ======================
database:
name: psycopg2
txn_limit: 10000
args:
user: "synapse"
password: "synapse_dev_password"
database: "synapse"
host: "postgres"
port: 5432
cp_min: 5
cp_max: 10
# ======================
# Media Storage
# ======================
media_store_path: /data/media_store
max_upload_size: 50M
url_preview_enabled: true
url_preview_ip_range_blacklist:
- "127.0.0.0/8"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
- "100.64.0.0/10"
- "192.0.0.0/24"
- "169.254.0.0/16"
- "198.18.0.0/15"
- "::1/128"
- "fe80::/10"
- "fc00::/7"
- "2001:db8::/32"
- "ff00::/8"
- "fec0::/10"
# ======================
# Registration (Dev Only)
# ======================
enable_registration: true
enable_registration_without_verification: true
# ======================
# Signing Keys
# ======================
# Auto-generated on first startup and persisted in the signing_key volume
signing_key_path: "/data/keys/localhost.signing.key"
# Suppress warning about trusted key servers in dev
suppress_key_server_warning: true
trusted_key_servers: []
# ======================
# Room Configuration
# ======================
enable_room_list_search: true
allow_public_rooms_over_federation: false
# ======================
# Rate Limiting (Relaxed for Dev)
# ======================
rc_message:
per_second: 100
burst_count: 200
rc_registration:
per_second: 10
burst_count: 50
rc_login:
address:
per_second: 10
burst_count: 50
account:
per_second: 10
burst_count: 50
# ======================
# Logging
# ======================
log_config: "/data/localhost.log.config"
# Inline log config — write to stdout for docker logs
# Synapse falls back to a basic console logger if the log_config file is missing,
# so we leave log_config pointing to a non-existent file intentionally.
# Override: mount a custom log config file at /data/localhost.log.config
# ======================
# Miscellaneous
# ======================
report_stats: false
macaroon_secret_key: "dev-macaroon-secret-change-in-production"
form_secret: "dev-form-secret-change-in-production"
# Enable presence for dev
use_presence: true
# Retention policy (optional, keep messages for 180 days in dev)
retention:
enabled: false