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
222 lines
7.7 KiB
Bash
Executable File
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
|