355 lines
12 KiB
Bash
355 lines
12 KiB
Bash
export DISCORD_BRIDGE_YAML_VERSION=v2
|
|
export ENTRYPOINT_CONF_VERSION=v3
|
|
export HOMESERVER_YAML_VERSION=v37
|
|
export LOG_CONFIG_VERSION=v2
|
|
export SHARED_SECRET_AUTH_VERSION=v2
|
|
export SIGNAL_BRIDGE_YAML_VERSION=v6
|
|
export TELEGRAM_BRIDGE_YAML_VERSION=v6
|
|
export NGINX_CONFIG_VERSION=v13
|
|
export WK_SERVER_VERSION=v1
|
|
export WK_CLIENT_VERSION=v2
|
|
export MAS_CONFIG_VERSION=v2
|
|
export PG_BACKUP_VERSION=v2
|
|
export ADMIN_CONFIG_VERSION=v1
|
|
export COMPRESS_STATE_ENTRYPOINT_VERSION=v5
|
|
|
|
###############################################################################
|
|
# Database maintenance — shrink a bloated Synapse database
|
|
#
|
|
# See https://levans.fr/shrink-synapse-database.html
|
|
#
|
|
# Recommended steps to reclaim disk space:
|
|
# 1. abra app cmd <domain> compress-state run_compressor 500 10000
|
|
# (compress redundant state — safe while Synapse is running)
|
|
# 2. abra app cmd <domain> db reindex
|
|
# (rebuild indexes — stop Synapse first)
|
|
# 3. abra app cmd <domain> db vacuum_full
|
|
# (rewrite tables and reclaim disk — stop Synapse first)
|
|
#
|
|
# Diagnostic commands (safe to run anytime):
|
|
# abra app cmd <domain> db db_size
|
|
# abra app cmd <domain> db state_bloat
|
|
# abra app cmd <domain> db empty_rooms
|
|
#
|
|
# Purge commands (require an admin token):
|
|
# abra app cmd <domain> app register_admin <user> <pass>
|
|
# abra app cmd <domain> app get_token <user> <pass>
|
|
# abra app cmd <domain> app purge_remote_media <days> <token>
|
|
# abra app cmd <domain> app purge_empty_rooms <token>
|
|
# abra app cmd <domain> app purge_room <room_id> <token>
|
|
# abra app cmd <domain> app purge_history <room_id> <days> <token>
|
|
###############################################################################
|
|
|
|
# --- Diagnostics (db) ---
|
|
|
|
db_size() {
|
|
echo "=== Database size ==="
|
|
psql -U synapse -d synapse -c "SELECT pg_size_pretty(pg_database_size('synapse')) AS db_size;"
|
|
echo ""
|
|
echo "=== Top 10 largest tables ==="
|
|
psql -U synapse -d synapse -c "
|
|
SELECT nspname || '.' || relname AS table,
|
|
pg_size_pretty(pg_total_relation_size(C.oid)) AS total_size
|
|
FROM pg_class C
|
|
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
|
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
|
ORDER BY pg_total_relation_size(C.oid) DESC
|
|
LIMIT 10;"
|
|
}
|
|
|
|
state_bloat() {
|
|
echo "=== Rooms with most state bloat ==="
|
|
psql -U synapse -d synapse -c "
|
|
SELECT room_id, count(*) AS state_entries
|
|
FROM state_groups_state
|
|
GROUP BY room_id
|
|
ORDER BY state_entries DESC
|
|
LIMIT 20;"
|
|
}
|
|
|
|
empty_rooms() {
|
|
echo "=== Rooms with no local members ==="
|
|
psql -U synapse -d synapse -c "
|
|
SELECT room_id, room_version
|
|
FROM rooms
|
|
WHERE room_id NOT IN (
|
|
SELECT room_id FROM local_current_membership WHERE membership = 'join'
|
|
);"
|
|
}
|
|
|
|
# --- Compression (compress-state) ---
|
|
|
|
run_compressor() {
|
|
CHUNK_SIZE="${1:-${STATE_COMPRESS_CHUNK_SIZE:-500}}"
|
|
CHUNKS="${2:-${STATE_COMPRESS_CHUNKS:-100}}"
|
|
DB_PASS=$(cat /run/secrets/db_password)
|
|
echo "Running synapse_auto_compressor (chunk_size=$CHUNK_SIZE, chunks=$CHUNKS)..."
|
|
/build/synapse_auto_compressor \
|
|
-p "postgresql://synapse:${DB_PASS}@db:5432/synapse" \
|
|
-c "$CHUNK_SIZE" -n "$CHUNKS"
|
|
}
|
|
|
|
# --- Maintenance (db) — stop Synapse before running these ---
|
|
|
|
reindex() {
|
|
echo "WARNING: REINDEX locks tables. Synapse should be stopped before running this."
|
|
echo "Running REINDEX on synapse database..."
|
|
psql -U synapse -d synapse -c "REINDEX (VERBOSE) DATABASE synapse;"
|
|
echo "REINDEX complete."
|
|
psql -U synapse -d synapse -c "SELECT pg_size_pretty(pg_database_size('synapse')) AS db_size;"
|
|
}
|
|
|
|
vacuum_full() {
|
|
echo "WARNING: VACUUM FULL locks tables and requires temporary disk space."
|
|
echo "Synapse should be stopped before running this."
|
|
echo "Running VACUUM FULL on synapse database..."
|
|
psql -U synapse -d synapse -c "VACUUM FULL;"
|
|
echo "VACUUM FULL complete."
|
|
psql -U synapse -d synapse -c "SELECT pg_size_pretty(pg_database_size('synapse')) AS db_size;"
|
|
}
|
|
|
|
# --- Purge commands (app) — require an admin access token ---
|
|
|
|
register_admin() {
|
|
USER="${1}"
|
|
PASS="${2}"
|
|
if [ -z "$USER" ] || [ -z "$PASS" ]; then
|
|
echo "Usage: register_admin <username> <password>"
|
|
return 1
|
|
fi
|
|
register_new_matrix_user -u "$USER" -p "$PASS" -a -c /data/homeserver.yaml http://localhost:8008
|
|
}
|
|
|
|
get_token() {
|
|
USER="${1}"
|
|
PASS="${2}"
|
|
if [ -z "$USER" ] || [ -z "$PASS" ]; then
|
|
echo "Usage: get_token <username> <password>"
|
|
echo "Returns an admin access token for use with purge commands."
|
|
return 1
|
|
fi
|
|
curl -s -X POST "http://localhost:8008/_matrix/client/r0/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"type\":\"m.login.password\",\"user\":\"$USER\",\"password\":\"$PASS\"}" \
|
|
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('access_token', d.get('error', 'unknown error')))"
|
|
}
|
|
|
|
purge_remote_media() {
|
|
DAYS="${1:-30}"
|
|
TOKEN="${2}"
|
|
if [ -z "$TOKEN" ]; then
|
|
echo "Usage: purge_remote_media <days> <admin_token>"
|
|
return 1
|
|
fi
|
|
BEFORE_TS=$(( $(date +%s) * 1000 - DAYS * 86400000 ))
|
|
echo "Purging remote media older than $DAYS days..."
|
|
curl -s -X POST "http://localhost:8008/_synapse/admin/v1/purge_media_cache?before_ts=$BEFORE_TS" \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
echo ""
|
|
}
|
|
|
|
purge_room() {
|
|
ROOM_ID="${1}"
|
|
TOKEN="${2}"
|
|
if [ -z "$ROOM_ID" ] || [ -z "$TOKEN" ]; then
|
|
echo "Usage: purge_room <room_id> <admin_token>"
|
|
return 1
|
|
fi
|
|
echo "Purging room $ROOM_ID..."
|
|
curl -s -X DELETE "http://localhost:8008/_synapse/admin/v1/rooms/$ROOM_ID" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"purge": true}'
|
|
echo ""
|
|
}
|
|
|
|
purge_history() {
|
|
ROOM_ID="${1}"
|
|
DAYS="${2:-90}"
|
|
TOKEN="${3}"
|
|
if [ -z "$ROOM_ID" ] || [ -z "$TOKEN" ]; then
|
|
echo "Usage: purge_history <room_id> <days> <admin_token>"
|
|
return 1
|
|
fi
|
|
BEFORE_TS=$(( $(date +%s) * 1000 - DAYS * 86400000 ))
|
|
echo "Purging history older than $DAYS days from $ROOM_ID..."
|
|
curl -s -X POST "http://localhost:8008/_synapse/admin/v1/purge_history/$ROOM_ID" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"purge_up_to_ts\": $BEFORE_TS}"
|
|
echo ""
|
|
}
|
|
|
|
purge_empty_rooms() {
|
|
TOKEN="${1}"
|
|
if [ -z "$TOKEN" ]; then
|
|
echo "Usage: purge_empty_rooms <admin_token>"
|
|
return 1
|
|
fi
|
|
echo "Fetching rooms with no local members..."
|
|
ROOMS=$(curl -s "http://localhost:8008/_synapse/admin/v1/rooms?limit=1000" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
| python3 -c "
|
|
import sys, json
|
|
data = json.load(sys.stdin)
|
|
for r in data.get('rooms', []):
|
|
if r.get('joined_local_members', 0) == 0:
|
|
print(r['room_id'])
|
|
")
|
|
COUNT=$(echo "$ROOMS" | grep -c '.' || true)
|
|
echo "Found $COUNT empty rooms."
|
|
if [ "$COUNT" -eq 0 ]; then
|
|
echo "Nothing to purge."
|
|
return 0
|
|
fi
|
|
echo "$ROOMS"
|
|
echo ""
|
|
echo "Purging..."
|
|
for ROOM_ID in $ROOMS; do
|
|
echo " Purging $ROOM_ID"
|
|
curl -s -X DELETE "http://localhost:8008/_synapse/admin/v1/rooms/$ROOM_ID" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"purge": true}' > /dev/null
|
|
done
|
|
echo "Done."
|
|
}
|
|
|
|
###############################################################################
|
|
# Other commands
|
|
###############################################################################
|
|
|
|
ensure_mas_database () {
|
|
if ! psql -U synapse -d postgres -v ON_ERROR_STOP=1 -Atqc "SELECT 1 FROM pg_database WHERE datname = 'mas'" | grep -qx 1
|
|
then
|
|
psql -U synapse -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE mas OWNER synapse"
|
|
fi
|
|
}
|
|
|
|
# Generate a PEM RSA private key and insert it as the MAS signing secret.
|
|
# `abra app secret generate` can only produce random hex/charset strings, so this
|
|
# secret is marked `generate=false` in .env.sample and handled here instead.
|
|
generate_mas_signing_rsa() {
|
|
if ! command -v openssl &> /dev/null; then
|
|
echo "openssl is required on your local machine to generate the MAS signing key."
|
|
echo "It could not be found in your PATH, please install openssl to proceed."
|
|
exit 1
|
|
fi
|
|
|
|
KEY=$(openssl genrsa 2048 2>/dev/null)
|
|
if [ -z "$KEY" ]; then
|
|
echo "Failed to generate RSA private key with openssl."
|
|
exit 1
|
|
fi
|
|
|
|
if printf '%s\n' "$KEY" | abra app secret insert -C "$APP_NAME" mas_signing_rsa v1; then
|
|
echo "MAS signing RSA key generated and inserted as v1."
|
|
else
|
|
echo "Failed to insert MAS signing RSA key."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Local helper: fetch homeserver.yaml from app, push to mas, then syn2mas check + dry-run.
|
|
prepare_mas_migration () {
|
|
local syn_cfg
|
|
|
|
syn_cfg=/tmp/homeserver.yaml
|
|
|
|
cleanup_prepare_mas_migration() {
|
|
rm -f "homeserver.yaml"
|
|
}
|
|
trap cleanup_prepare_mas_migration EXIT
|
|
|
|
echo "Fetching /data/homeserver.yaml from app to homeserver.yaml (abra app run … cat)..."
|
|
if ! abra app run -t "$DOMAIN" app cat /data/homeserver.yaml > "homeserver.yaml"
|
|
then
|
|
return 1
|
|
fi
|
|
if [ ! -s "homeserver.yaml" ]; then
|
|
echo "Error: fetched homeserver.yaml is empty." >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "Copying into mas:/tmp"
|
|
abra app cp "$DOMAIN" "homeserver.yaml" "mas:/tmp" || return 1
|
|
|
|
echo "Running mas-cli syn2mas check..."
|
|
abra app run -t "$DOMAIN" mas -- mas-cli syn2mas check \
|
|
--config /etc/mas/config.yaml \
|
|
--synapse-config "$syn_cfg" || return 1
|
|
|
|
echo "Running mas-cli syn2mas migrate --dry-run..."
|
|
abra app run -t "$DOMAIN" mas -- mas-cli syn2mas migrate \
|
|
--config /etc/mas/config.yaml \
|
|
--synapse-config "$syn_cfg" \
|
|
--dry-run || return 1
|
|
|
|
trap - EXIT
|
|
cleanup_prepare_mas_migration
|
|
|
|
echo ""
|
|
echo "=== Next migration step: stop Synapse (downtime) ==="
|
|
echo "Run on a host whose Docker CLI targets this Swarm (same machine you use for 'abra app deploy')."
|
|
if [ -n "${STACK_NAME:-}" ]; then
|
|
echo " docker service scale ${STACK_NAME}_app=0"
|
|
else
|
|
echo "STACK_NAME is not set here; resolve the Synapse service name with 'docker service ls' on that host, then:"
|
|
echo "docker service scale <STACK_NAME>_app=0"
|
|
fi
|
|
}
|
|
|
|
# Run syn2mas migrate for real (writes MAS data). Run from your operator machine as MAS image is distroless.
|
|
# Requires /tmp/homeserver.yaml in the mas container (e.g. from prepare_mas_migration) and
|
|
# Synapse scaled down before migrate.
|
|
run_mas_migration () {
|
|
local syn_cfg=/tmp/homeserver.yaml
|
|
|
|
echo "Running mas-cli syn2mas migrate in mas via abra app run..."
|
|
abra app run -t "$DOMAIN" mas -- mas-cli syn2mas migrate \
|
|
--config /etc/mas/config.yaml \
|
|
--synapse-config "$syn_cfg"
|
|
}
|
|
|
|
set_admin () {
|
|
admin=akadmin
|
|
if [ -n "$1" ]
|
|
then
|
|
admin=$1
|
|
fi
|
|
psql -U synapse -c "UPDATE users SET admin = 1 WHERE name = '@$admin:$DOMAIN'";
|
|
}
|
|
|
|
set_bridge_tokens() {
|
|
if [ -z "$1" ]; then
|
|
echo "Error: Missing parameter. Usage: set_bridge_tokens <BRIDGETYPE>"
|
|
return 1
|
|
fi
|
|
|
|
BRIDGETYPE=$1
|
|
echo "retrieve tokens from registration.yaml..."
|
|
output=$(abra app run $DOMAIN app cat /${BRIDGETYPE}-data/registration.yaml)
|
|
if [ $? -ne 0 ]; then
|
|
echo "Error: Failed to retrieve registration.yaml for ${BRIDGETYPE} bridge:"
|
|
echo "$output"
|
|
return 1
|
|
fi
|
|
|
|
hs_token=$(echo "$output" | sed -n 's/^hs_token:[[:space:]]*\(.*\)$/\1/p')
|
|
as_token=$(echo "$output" | sed -n 's/^as_token:[[:space:]]*\(.*\)$/\1/p')
|
|
|
|
echo "HS Token: $hs_token"
|
|
echo "AS Token: $as_token"
|
|
echo "UNDEPLOY $DOMAIN?"
|
|
abra app undeploy $DOMAIN
|
|
|
|
echo "Replacing tokens:"
|
|
abra app secret rm $DOMAIN ${BRIDGETYPE}_as_token
|
|
abra app secret insert $DOMAIN ${BRIDGETYPE}_as_token v1 $as_token
|
|
abra app secret rm $DOMAIN ${BRIDGETYPE}_hs_token
|
|
abra app secret insert $DOMAIN ${BRIDGETYPE}_hs_token v1 $hs_token
|
|
|
|
echo "Redeploying $DOMAIN..."
|
|
abra app deploy -n $DOMAIN
|
|
}
|