Files
hermes-agent/deploy/synapse/setup.sh
Alexander Whitestone cc9d7705b6
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m1s
feat(synapse): Matrix Phase 1 — Synapse homeserver deployment stack
Deploy Synapse on Ezra VPS with PostgreSQL backend, bot registration,
and management tooling.

Closes #272

Components:
- docker-compose.yml: Synapse + PostgreSQL 16 stack
- homeserver.yaml: Production config (registration disabled, rate limits, retention)
- setup.sh: One-shot deploy (generates secrets, starts stack, registers accounts, gets bot token)
- manage.sh: Day-to-day ops (status, restart, logs, backup, update, create-user, teardown)
- docs/synapse-deployment.md: Full deployment guide with Nginx TLS, DNS, troubleshooting

Security:
- Registration disabled by default
- Rate limiting on login/registration/messages
- Client API bound to localhost (Nginx proxy for public access)
- Secrets chmod 600, .gitignore'd
- Federation certificate verification enabled

Bot account auto-registered and access token acquired — credentials
written to synapse-credentials.env for hermes-agent integration.
2026-04-13 18:07:15 -04:00

212 lines
7.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Synapse Homeserver — One-Shot Setup Script
# Matrix Phase 1: Deploy Synapse on Ezra VPS
#
# Usage:
# ./setup.sh <server_name> [admin_user] [admin_password]
#
# Example:
# ./setup.sh matrix.timmy-time.xyz hermes-bot 'secure-pass-123'
#
# What it does:
# 1. Generates .env with secrets
# 2. Prepares homeserver.yaml with correct server name
# 3. Generates signing key
# 4. Starts Synapse + PostgreSQL via Docker Compose
# 5. Waits for Synapse to be healthy
# 6. Registers admin user + bot account
# 7. Outputs Matrix credentials for hermes-agent
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${GREEN}[SETUP]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
# --- Args ---
SERVER_NAME="${1:?Usage: $0 <server_name> [admin_user] [admin_password]}"
ADMIN_USER="${2:-timmy-admin}"
ADMIN_PASS="${3:-$(openssl rand -hex 16)}"
BOT_USER="${4:-hermes-bot}"
BOT_PASS="${5:-$(openssl rand -hex 16)}"
echo -e "${CYAN}"
echo "╔══════════════════════════════════════════════════╗"
echo "║ Synapse Homeserver — Matrix Phase 1 Deploy ║"
echo "╚══════════════════════════════════════════════════╝"
echo -e "${NC}"
info "Server name: $SERVER_NAME"
info "Admin user: @$ADMIN_USER:$SERVER_NAME"
info "Bot user: @$BOT_USER:$SERVER_NAME"
echo ""
# --- Preflight ---
info "Preflight checks..."
command -v docker >/dev/null 2>&1 || error "docker not found. Install Docker first."
command -v docker compose version >/dev/null 2>&1 || error "docker compose not found. Install Docker Compose plugin."
info "Docker: $(docker --version | head -1)"
info "Compose: $(docker compose version | head -1)"
# --- Generate .env ---
info "Generating .env..."
POSTGRES_PASSWORD=$(openssl rand -hex 24)
REGISTRATION_SECRET=$(openssl rand -hex 16)
cat > .env <<EOF
# Synapse deployment — generated $(date -u +%Y-%m-%dT%H:%M:%SZ)
# DO NOT COMMIT THIS FILE
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
SYNAPSE_SERVER_NAME=${SERVER_NAME}
SYNAPSE_REPORT_STATS=no
REGISTRATION_SECRET=${REGISTRATION_SECRET}
EOF
chmod 600 .env
info ".env written with secure permissions"
# --- Prepare homeserver.yaml ---
info "Preparing homeserver.yaml..."
sed -i.bak "s/SERVER_NAME_PLACEHOLDER/${SERVER_NAME}/g" homeserver.yaml
rm -f homeserver.yaml.bak
info "Server name set to: $SERVER_NAME"
# --- Generate signing key ---
info "Generating signing key..."
# Synapse will generate its own key on first run if missing
# But we pre-create the data volume structure
docker volume create synapse_data >/dev/null 2>&1 || true
docker volume create synapse_db >/dev/null 2>&1 || true
# --- Start the stack ---
info "Starting Synapse + PostgreSQL..."
docker compose up -d
# --- Wait for Synapse to be healthy ---
info "Waiting for Synapse to start (up to 120s)..."
MAX_WAIT=120
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
if curl -sfS http://127.0.0.1:8008/health >/dev/null 2>&1; then
info "Synapse is healthy!"
break
fi
sleep 3
ELAPSED=$((ELAPSED + 3))
if [ $((ELAPSED % 15)) -eq 0 ]; then
info "Still waiting... (${ELAPSED}s)"
fi
done
if [ $ELAPSED -ge $MAX_WAIT ]; then
warn "Synapse did not respond within ${MAX_WAIT}s. Check logs:"
echo " docker compose logs synapse"
error "Aborting registration."
fi
# --- Register admin user ---
info "Registering admin user @$ADMIN_USER:$SERVER_NAME..."
docker compose exec -T synapse register_new_matrix_user \
http://localhost:8008 \
-c /data/homeserver.yaml \
-u "$ADMIN_USER" \
-p "$ADMIN_PASS" \
--admin \
--no-extra-prompt 2>&1 || {
# User might already exist if re-running
warn "Admin user registration returned non-zero (may already exist)"
}
# --- Register bot user ---
info "Registering bot user @$BOT_USER:$SERVER_NAME..."
docker compose exec -T synapse register_new_matrix_user \
http://localhost:8008 \
-c /data/homeserver.yaml \
-u "$BOT_USER" \
-p "$BOT_PASS" \
--no-admin \
--no-extra-prompt 2>&1 || {
warn "Bot user registration returned non-zero (may already exist)"
}
# --- Get bot access token ---
info "Acquiring bot access token..."
BOT_TOKEN_RESPONSE=$(curl -sfS -X POST "http://127.0.0.1:8008/_matrix/client/v3/login" \
-H 'Content-Type: application/json' \
-d "{
\"type\": \"m.login.password\",
\"identifier\": {
\"type\": \"m.id.user\",
\"user\": \"${BOT_USER}\"
},
\"password\": \"${BOT_PASS}\",
\"device_name\": \"Hermes Agent\"
}")
BOT_ACCESS_TOKEN=$(echo "$BOT_TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])" 2>/dev/null || echo "FAILED_TO_EXTRACT")
BOT_DEVICE_ID=$(echo "$BOT_TOKEN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['device_id'])" 2>/dev/null || echo "UNKNOWN")
if [ "$BOT_ACCESS_TOKEN" = "FAILED_TO_EXTRACT" ]; then
warn "Could not extract bot access token automatically."
warn "Login manually: curl -X POST http://127.0.0.1:8008/_matrix/client/v3/login ..."
fi
# --- Write credentials file ---
CREDENTIALS_FILE="synapse-credentials.env"
cat > "$CREDENTIALS_FILE" <<EOF
# Synapse Credentials — generated $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Add these to hermes-agent's ~/.hermes/.env
# Matrix integration
MATRIX_HOMESERVER=http://${SERVER_NAME}:8008
MATRIX_ACCESS_TOKEN=${BOT_ACCESS_TOKEN}
MATRIX_USER_ID=@${BOT_USER}:${SERVER_NAME}
MATRIX_DEVICE_ID=${BOT_DEVICE_ID}
MATRIX_ENCRYPTION=true
# Admin credentials (for user management)
SYNAPSE_ADMIN_USER=@${ADMIN_USER}:${SERVER_NAME}
SYNAPSE_ADMIN_PASSWORD=${ADMIN_PASS}
# Bot credentials
SYNAPSE_BOT_USER=@${BOT_USER}:${SERVER_NAME}
SYNAPSE_BOT_PASSWORD=${BOT_PASS}
EOF
chmod 600 "$CREDENTIALS_FILE"
info "Credentials written to: $CREDENTIALS_FILE"
# --- Summary ---
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ Synapse Deployed Successfully! ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════╝${NC}"
echo ""
echo -e " Server: ${CYAN}https://${SERVER_NAME}${NC}"
echo -e " Client API: ${CYAN}http://127.0.0.1:8008${NC}"
echo -e " Federation: ${CYAN}https://${SERVER_NAME}:8448${NC}"
echo ""
echo -e " Admin: ${YELLOW}@${ADMIN_USER}:${SERVER_NAME}${NC}"
echo -e " Bot: ${YELLOW}@${BOT_USER}:${SERVER_NAME}${NC}"
echo -e " Bot Token: ${YELLOW}${BOT_ACCESS_TOKEN:0:20}...${NC}"
echo ""
echo -e " Credentials: ${CYAN}${SCRIPT_DIR}/${CREDENTIALS_FILE}${NC}"
echo ""
echo -e "${GREEN}Next steps:${NC}"
echo " 1. Point DNS: ${SERVER_NAME}$(curl -s ifconfig.me 2>/dev/null || echo '<VPS_IP>')"
echo " 2. Set up TLS: nginx/certbot reverse proxy for :8008 and :8448"
echo " 3. Copy credentials to hermes-agent: cp ${CREDENTIALS_FILE} ~/.hermes/.env"
echo " 4. Start hermes: hermes gateway --platform matrix"
echo ""
echo " Manage: docker compose logs -f | docker compose restart | docker compose down"
echo " Users: docker compose exec synapse register_new_matrix_user http://localhost:8008 -c /data/homeserver.yaml -u <user> -p <pass>"
echo ""