Files
talktime/synapse/install.sh
T
patrickandClaude Sonnet 5 90669b4568 feat: add universal Synapse install.sh for bare-metal/Proxmox-LXC
Own installer instead of relying only on the external bashclub script:
builds homeserver.yaml straight from our homeserver.yaml.sample (so it
inherits TalkTime's security defaults — no federation, no public
registration — automatically), sets up Postgres, a systemd service, and
prints the admin token ready to paste into backend/.env. Works identically
in a Proxmox LXC and on bare metal (both are just a Debian userland).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBf3yfnnHzMQ5qhLV2285A
2026-09-11 15:38:07 +02:00

222 lines
7.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Universal Synapse installer for TalkTime — Debian/Ubuntu, works in a Proxmox
# LXC or on bare metal identically (both are just a Debian userland).
#
# What it does, in order:
# 1. Install system packages + PostgreSQL
# 2. Create a dedicated Postgres role/database for Synapse
# 3. Install matrix-synapse into a venv (pip, not apt — works on any
# Debian/Ubuntu version without depending on the matrix.org apt repo)
# 4. Generate homeserver.yaml from synapse/homeserver.yaml.sample with all
# secrets filled in and TalkTime's secure defaults (registration off,
# federation off)
# 5. Install + start a systemd service
# 6. Create the admin user and fetch its access token
#
# Usage:
# sudo ./install.sh chat.example.org
# sudo SYNAPSE_SERVER_NAME=chat.example.org ADMIN_USER=admin ./install.sh
#
# Idempotent-ish: re-running skips steps whose result already exists
# (venv, database, service file) but always re-registers nothing — the
# admin user is only created once (step 6 fails loudly if it already exists,
# that's fine, just note the token from the first run).
set -euo pipefail
# ---- config (env vars override) --------------------------------------------
SERVER_NAME="${1:-${SYNAPSE_SERVER_NAME:-}}"
INSTALL_DIR="${INSTALL_DIR:-/opt/synapse}"
SYNAPSE_USER="${SYNAPSE_USER:-synapse}"
DB_NAME="${DB_NAME:-synapse}"
DB_USER="${DB_USER:-synapse}"
DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -hex 24)}"
ADMIN_USER="${ADMIN_USER:-admin}"
ADMIN_PASSWORD="${ADMIN_PASSWORD:-$(openssl rand -hex 16)}"
SYNAPSE_PORT="${SYNAPSE_PORT:-8008}"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
if [[ -z "$SERVER_NAME" ]]; then
echo "Usage: $0 <server-name> (e.g. chat.example.org)" >&2
echo "Or set SYNAPSE_SERVER_NAME=... as an env var." >&2
exit 1
fi
if [[ $EUID -ne 0 ]]; then
echo "Must run as root (sudo $0 ...)." >&2
exit 1
fi
if ! command -v apt-get &>/dev/null; then
echo "This installer only supports Debian/Ubuntu (apt-get not found)." >&2
exit 1
fi
log() { echo -e "\n== $* =="; }
# ---- 1. system packages -----------------------------------------------------
log "Installing system packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
python3-dev python3-pip python3-venv \
libpq-dev build-essential libffi-dev libssl-dev libjpeg-dev libxslt1-dev \
postgresql openssl >/dev/null
# ---- 2. postgres role + database -------------------------------------------
log "Setting up PostgreSQL database"
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" | grep -q 1; then
sudo -u postgres psql -c "CREATE ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASSWORD}';" >/dev/null
else
echo "Role ${DB_USER} already exists, updating password."
sudo -u postgres psql -c "ALTER ROLE ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >/dev/null
fi
if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${DB_NAME}'" | grep -q 1; then
sudo -u postgres createdb -O "${DB_USER}" "${DB_NAME}" -E UTF8 --locale=C --template=template0
else
echo "Database ${DB_NAME} already exists, reusing."
fi
# ---- 3. synapse into a venv --------------------------------------------------
log "Installing matrix-synapse (this can take a few minutes)"
mkdir -p "${INSTALL_DIR}"
if ! id "${SYNAPSE_USER}" &>/dev/null; then
useradd -r -d "${INSTALL_DIR}" -s /usr/sbin/nologin "${SYNAPSE_USER}"
fi
if [[ ! -d "${INSTALL_DIR}/venv" ]]; then
python3 -m venv "${INSTALL_DIR}/venv"
fi
"${INSTALL_DIR}/venv/bin/pip" install --upgrade pip -q
"${INSTALL_DIR}/venv/bin/pip" install -q "matrix-synapse[postgres]"
# ---- 4. homeserver.yaml from our template -----------------------------------
log "Writing homeserver.yaml"
if [[ ! -f "${INSTALL_DIR}/${SERVER_NAME}.signing.key" ]]; then
"${INSTALL_DIR}/venv/bin/python" -m synapse.app.homeserver \
--server-name="${SERVER_NAME}" \
--config-path="${INSTALL_DIR}/homeserver.generated.yaml" \
--generate-config \
--report-stats=no \
--data-directory="${INSTALL_DIR}" >/dev/null
rm -f "${INSTALL_DIR}/homeserver.generated.yaml" "${INSTALL_DIR}/${SERVER_NAME}.log.config.generated"
fi
REGISTRATION_SECRET="$(openssl rand -hex 32)"
MACAROON_SECRET="$(openssl rand -hex 32)"
FORM_SECRET="$(openssl rand -hex 32)"
sed \
-e "s|<chat.talktime.local>|${SERVER_NAME}|g" \
-e "s|<db-passwort>|${DB_PASSWORD}|g" \
-e "s|<langer-zufalls-string>|PLACEHOLDER|g" \
-e "s|/data/|${INSTALL_DIR}/|g" \
-e "s|host: synapse-db|host: localhost|g" \
-e "s|user: synapse|user: ${DB_USER}|g" \
-e "s|database: synapse|database: ${DB_NAME}|g" \
"${SCRIPT_DIR}/homeserver.yaml.sample" > "${INSTALL_DIR}/homeserver.yaml"
mkdir -p "${INSTALL_DIR}/media_store"
# Replace the three PLACEHOLDER secrets one at a time so each gets its own value.
python3 - "$INSTALL_DIR/homeserver.yaml" "$REGISTRATION_SECRET" "$MACAROON_SECRET" "$FORM_SECRET" <<'PYEOF'
import sys
path, reg, mac, form = sys.argv[1:5]
with open(path) as f:
content = f.read()
for value in (reg, mac, form):
content = content.replace("PLACEHOLDER", value, 1)
with open(path, "w") as f:
f.write(content)
PYEOF
# Also write log config referenced by our template (Synapse needs the file to exist).
cat > "${INSTALL_DIR}/${SERVER_NAME}.log.config" <<EOF
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse:
level: INFO
root:
level: INFO
handlers: [console]
disable_existing_loggers: false
EOF
chown -R "${SYNAPSE_USER}:${SYNAPSE_USER}" "${INSTALL_DIR}"
# ---- 5. systemd service -------------------------------------------------------
log "Installing systemd service"
cat > /etc/systemd/system/matrix-synapse.service <<EOF
[Unit]
Description=Matrix Synapse Homeserver (TalkTime)
After=network.target postgresql.service
[Service]
Type=simple
User=${SYNAPSE_USER}
WorkingDirectory=${INSTALL_DIR}
ExecStart=${INSTALL_DIR}/venv/bin/python -m synapse.app.homeserver -c ${INSTALL_DIR}/homeserver.yaml
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now matrix-synapse
log "Waiting for Synapse to come up"
for _ in $(seq 1 30); do
if curl -fs "http://localhost:${SYNAPSE_PORT}/health" >/dev/null 2>&1; then
break
fi
sleep 1
done
if ! curl -fs "http://localhost:${SYNAPSE_PORT}/health" >/dev/null 2>&1; then
echo "Synapse did not come up in time — check: journalctl -u matrix-synapse -e" >&2
exit 1
fi
# ---- 6. admin user + access token -------------------------------------------
log "Creating admin user"
"${INSTALL_DIR}/venv/bin/register_new_matrix_user" \
-c "${INSTALL_DIR}/homeserver.yaml" \
-u "${ADMIN_USER}" -p "${ADMIN_PASSWORD}" -a \
"http://localhost:${SYNAPSE_PORT}"
ADMIN_TOKEN="$(curl -fs -XPOST "http://localhost:${SYNAPSE_PORT}/_matrix/client/v3/login" \
-d "{\"type\":\"m.login.password\",\"user\":\"${ADMIN_USER}\",\"password\":\"${ADMIN_PASSWORD}\"}" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')"
cat <<EOF
== Done ==
Synapse is running at http://localhost:${SYNAPSE_PORT} (server_name: ${SERVER_NAME}).
Federation is disabled by default — this is a private homeserver for TalkTime.
Add to backend/.env:
SYNAPSE_BASE_URL=http://localhost:${SYNAPSE_PORT}
SYNAPSE_ADMIN_TOKEN=${ADMIN_TOKEN}
SYNAPSE_SERVER_NAME=${SERVER_NAME}
Admin login (keep safe, only needed if you need to log into Synapse directly):
user: ${ADMIN_USER}
password: ${ADMIN_PASSWORD}
Database password (only needed for manual DB access): ${DB_PASSWORD}
Put Synapse behind a TLS-terminating reverse proxy before exposing it publicly
— see synapse/README.md "Reverse Proxy + TLS".
EOF