#!/bin/sh DB_TYPE="${DB_TYPE:-pyscopg2}" # Default to PostgreSQL POSTGRES_DB="${POSTGRES_DB:-matrix}" POSTGRES_USER="${POSTGRES_USER:-matrix}" POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-matrix}" POSTGRES_HOST=matrixpostgres-db DB_PORT="5432" SYNAPSE_HOST="${SYNAPSE_SERVER_NAME:-localhost}" ADMIN_USERNAME="${ADMIN_USERNAME:-admin}" ADMIN_PASSWORD="${ADMIN_PASSWORD:-changeme}" HOMESERVER_URL="${HOMESERVER_URL:-https://$SYNAPSE_HOST}" prepare_postgres () { if [ -f /data/homeserver.yaml ]; then DB_TYPE=$(yq '.database.name' /data/homeserver.yaml 2>/dev/null ) if [ "$DB_TYPE" != "psycopg2" ]; then echo "Preparing PostgreSQL database..." YAML='.server_name = "'$SYNAPSE_HOST'" | .database.name = "psycopg2" | .database.args.user = "'$POSTGRES_USER'" | .database.args.password = "'$POSTGRES_PASSWORD'" | .database.args.database = "'$POSTGRES_DB'" | .database.args.host = "'$POSTGRES_HOST'" | .database.args.port = "'$DB_PORT'" | .database.args.cp_min = 5 | .database.args.cp_max = 10' yq eval "$YAML" /data/homeserver.yaml > /data/homeserver.yaml.tmp && mv /data/homeserver.yaml.tmp /data/homeserver.yaml exit 0 fi fi } # Get registration shared secret from template.json get_registration_secret() { if [ -f "/data/homeserver.yaml" ]; then SHARED_SECRET=$(yq '.registration_shared_secret' /data/homeserver.yaml 2>/dev/null) echo "$SHARED_SECRET" if [ -n "$SHARED_SECRET" ] && [ "$SHARED_SECRET" != "null" ]; then return 0 fi fi } # Wait for Synapse to be ready wait_for_synapse() { echo "Waiting for Synapse to be ready..." local max_attempts=30 local attempt=1 while [ $attempt -le $max_attempts ]; do if curl -sk "$HOMESERVER_URL/_matrix/client/versions" >/dev/null 2>&1; then echo "Synapse is ready!" return 0 fi echo "Attempt $attempt/$max_attempts - Synapse not ready yet..." sleep 2 attempt=$((attempt + 1)) done echo "Error: Synapse did not become ready within expected time" return 1 } # Register admin user using shared secret register_admin_user() { local SHARED_SECRET=$1 echo "Registering admin user: $ADMIN_USERNAME" # Derive localpart (strip leading @ and :domain) and lowercase local localpart localpart=$(printf '%s' "$ADMIN_USERNAME" | sed -e 's/^@//' -e 's/:.*$//' | tr '[:upper:]' '[:lower:]') # Fetch server-issued nonce local url nonce url="${HOMESERVER_URL%/}/_synapse/admin/v1/register" nonce=$(curl -kfsS "$url" | jq -r '.nonce') if [ -z "$nonce" ] || [ "$nonce" = "null" ]; then echo "Failed to fetch nonce from $url" return 1 fi # Admin flag and user_type local admin_word admin_json user_type user_type_json user_type="${USER_TYPE:-}" case "${ADMIN:-true}" in true|1|yes|y|True|TRUE) admin_word="admin"; admin_json=true ;; *) admin_word="notadmin"; admin_json=false ;; esac if [ -n "$user_type" ]; then user_type_json=$(printf '"%s"' "$user_type") else user_type_json=null fi # Compute MAC exactly like Python (no trailing NUL unless user_type present) local mac if [ -n "$user_type" ]; then mac=$(printf '%s\0%s\0%s\0%s\0%s' "$nonce" "$localpart" "$ADMIN_PASSWORD" "$admin_word" "$user_type" \ | openssl dgst -sha1 -hmac "$SHARED_SECRET" -binary | od -An -tx1 | tr -d ' \n') else mac=$(printf '%s\0%s\0%s\0%s' "$nonce" "$localpart" "$ADMIN_PASSWORD" "$admin_word" \ | openssl dgst -sha1 -hmac "$SHARED_SECRET" -binary | od -An -tx1 | tr -d ' \n') fi # Build payload and register local payload response payload=$(printf '{"nonce":"%s","username":"%s","password":"%s","admin":%s,"user_type":%s,"mac":"%s"}' \ "$nonce" "$localpart" "$ADMIN_PASSWORD" "$admin_json" "$user_type_json" "$mac") response=$(curl -kfsS -X POST "$url" -H 'Content-Type: application/json' -d "$payload" || true) if echo "$response" | grep -q '"user_id"\|"access_token"'; then echo "Admin user created successfully!" # renmove registration_shared_secret from homeserver.yaml for security yq eval '.registration_shared_secret = ""' /data/homeserver.yaml > /tmp/homeserver.yaml.tmp && mv /tmp/homeserver.yaml.tmp /data/homeserver.yaml # trigger a restart of synapse to reload config echo '{"NAME":"service-matrix.containers.matrixserver-app"}' | jq -r > /var/tmp/shared/input/upgrade-matrix.json echo "Matrix server app restart requested" return 0 else echo "Failed to create admin user. Response: $response" return 1 fi } # Check if admin exists by trying to login check_admin_by_login() { local response=$(curl -sk -X POST "$HOMESERVER_URL/_matrix/client/r0/login" \ -H "Content-Type: application/json" \ -d "{ \"type\": \"m.login.password\", \"user\": \"$ADMIN_USERNAME\", \"password\": \"$ADMIN_PASSWORD\" }" 2>/dev/null) if echo "$response" | grep -q access_token; then echo "Successfully logged in as admin user" return 0 # User exists and password is correct fi return 1 # User doesn't exist or wrong password } # Main execution main() { echo "Starting Synapse admin initialization..." if ! wait_for_synapse; then exit 1 fi # Get registration shared secret local SHARED_SECRET=$(get_registration_secret) if [ -z "$SHARED_SECRET" ]; then echo "Error: Could not find registration_shared_secret" echo "Make sure template.json contains the encoded configuration" exit 1 fi echo "Found registration shared secret" # Check if admin already exists by trying to login if check_admin_by_login; then echo "Admin user '$ADMIN_USERNAME' already exists and is accessible" exit 0 fi echo "No accessible admin user found. Creating admin user..." # Try to register admin user if ! register_admin_user "$SHARED_SECRET"; then echo "Primary registration method failed, trying alternative..." fi } # Validate environment if [ -z "$ADMIN_PASSWORD" ] || [ "$ADMIN_PASSWORD" = "changeme" ]; then echo "Warning: Please set a secure ADMIN_PASSWORD" fi prepare_postgres main "$@"