Gitea + ArgoCD + cert-manager + Traefik + CNPG + monitoring + loki + alloy Matrix homeservers for mrt0rtikize.ru, t0rt1k.tech, roglog.space
246 lines
10 KiB
Bash
Executable File
246 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ================================================================
|
|
# Backup script for Matrix instances on yc-playground cluster
|
|
# ================================================================
|
|
# Backs up the old chart (matrix-2.9.17) prod instances:
|
|
# t0rt1k.tech, roglog.space, uretra.space
|
|
#
|
|
# Usage: ./backup-yc-playground.sh <name> [--no-downtime]
|
|
# Example: ./backup-yc-playground.sh t0rt1k
|
|
# ./backup-yc-playground.sh t0rt1k --no-downtime (test, no scaling)
|
|
# ================================================================
|
|
|
|
readonly YC_CONTEXT="yc-playground"
|
|
readonly K="${KUBECTL:-kubectl} --context ${YC_CONTEXT}"
|
|
readonly BACKUP_BASE="$(dirname "$(realpath "$0")")/../backups"
|
|
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
log() { echo -e "${GREEN}[$(date +%H:%M:%S)]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[$(date +%H:%M:%S)] WARN:${NC} $*"; }
|
|
err() { echo -e "${RED}[$(date +%H:%M:%S)] ERROR:${NC} $*"; }
|
|
|
|
# -------------------------------------------------------------------
|
|
# Parse args
|
|
# -------------------------------------------------------------------
|
|
DOWNTIME=true
|
|
NAME=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--no-downtime) DOWNTIME=false ;;
|
|
*) NAME="$arg" ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "${NAME}" ]; then
|
|
echo "Usage: $0 <name> [--no-downtime]"
|
|
echo " name: t0rt1k | roglog | uretra"
|
|
echo " --no-downtime: skip scale up/down (test run)"
|
|
exit 1
|
|
fi
|
|
|
|
readonly NS="matrix-${NAME}"
|
|
readonly BACKUP_DIR="${BACKUP_BASE}/matrix-${NAME}-${TIMESTAMP}"
|
|
|
|
# Synapse labels
|
|
readonly SYNAPSE_LABEL="app.kubernetes.io/instance=chat,app.kubernetes.io/name=matrix"
|
|
# MAS labels
|
|
readonly MAS_LABEL="app=mas"
|
|
readonly MAS_PG_LABEL="app=mas-postgresql"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Prerequisites
|
|
# -------------------------------------------------------------------
|
|
log "=== Backing up ${NS} (downtime=${DOWNTIME}) ==="
|
|
|
|
if ! ${K} get ns "${NS}" >/dev/null 2>&1; then
|
|
err "Namespace ${NS} not found on yc-playground."
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "${BACKUP_DIR}"
|
|
log "Backup directory: ${BACKUP_DIR}"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 1: Backup Synapse media
|
|
# -------------------------------------------------------------------
|
|
log "=== Step 1: Backing up Synapse media ==="
|
|
|
|
SYNAPSE_POD=$(${K} -n "${NS}" get pods -l "${SYNAPSE_LABEL}" -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' 2>/dev/null)
|
|
|
|
if [ -n "${SYNAPSE_POD}" ]; then
|
|
log "Synapse pod: ${SYNAPSE_POD}"
|
|
# Check if media directory exists
|
|
if ${K} exec -n "${NS}" "${SYNAPSE_POD}" -- test -d /data/media_store 2>/dev/null; then
|
|
${K} exec -n "${NS}" "${SYNAPSE_POD}" -- tar czf /tmp/synapse-media.tar.gz -C /data media_store/
|
|
${K} cp "${NS}/${SYNAPSE_POD}:/tmp/synapse-media.tar.gz" "${BACKUP_DIR}/synapse-media.tar.gz" 2>/dev/null || \
|
|
${K} cp "${NS}/${SYNAPSE_POD}:tmp/synapse-media.tar.gz" "${BACKUP_DIR}/synapse-media.tar.gz"
|
|
${K} exec -n "${NS}" "${SYNAPSE_POD}" -- rm -f /tmp/synapse-media.tar.gz
|
|
log "Media saved: $(du -h "${BACKUP_DIR}/synapse-media.tar.gz" | cut -f1)"
|
|
else
|
|
warn "/data/media_store not found in Synapse pod."
|
|
fi
|
|
else
|
|
warn "No running Synapse pod found — skipping media backup."
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 2: Stop Synapse + MAS
|
|
# -------------------------------------------------------------------
|
|
if ${DOWNTIME}; then
|
|
log "=== Step 2: Stopping Synapse + MAS ==="
|
|
|
|
SYNAPSE_READY=$(${K} -n "${NS}" get deploy chat-matrix -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
|
|
MAS_READY=$(${K} -n "${NS}" get deploy mas -o jsonpath='{.status.readyReplicas}' 2>/dev/null || echo "0")
|
|
|
|
if [ "${SYNAPSE_READY}" != "0" ]; then
|
|
${K} -n "${NS}" scale deploy chat-matrix --replicas=0
|
|
log "Synapse scaled to 0."
|
|
else
|
|
log "Synapse already scaled to 0."
|
|
fi
|
|
|
|
if [ "${MAS_READY}" != "0" ]; then
|
|
${K} -n "${NS}" scale deploy mas --replicas=0
|
|
log "MAS scaled to 0."
|
|
else
|
|
log "MAS already scaled to 0."
|
|
fi
|
|
|
|
${K} -n "${NS}" wait --for=delete pod -l "${SYNAPSE_LABEL}" --timeout=120s 2>/dev/null || warn "Synapse may still be terminating."
|
|
${K} -n "${NS}" wait --for=delete pod -l "${MAS_LABEL}" --timeout=120s 2>/dev/null || warn "MAS may still be terminating."
|
|
log "Synapse + MAS stopped."
|
|
else
|
|
log "=== Step 2: Skipping downtime (--no-downtime) ==="
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 3: Dump Synapse PostgreSQL
|
|
# -------------------------------------------------------------------
|
|
log "=== Step 3: Dumping Synapse PostgreSQL ==="
|
|
|
|
# Get the postgres password from the secret
|
|
SYNAPSE_PG_PW=$(${K} -n "${NS}" get secret chat-postgresql -o jsonpath='{.data.postgres-password}' 2>/dev/null | base64 -d)
|
|
|
|
if [ -z "${SYNAPSE_PG_PW}" ]; then
|
|
SYNAPSE_PG_PW=$(${K} -n "${NS}" get secret chat-postgresql -o jsonpath='{.data.password}' 2>/dev/null | base64 -d)
|
|
fi
|
|
|
|
if [ -z "${SYNAPSE_PG_PW}" ]; then
|
|
warn "Could not read chat-postgresql secret — trying env var from pod."
|
|
SYNAPSE_PG_PW=$(${K} exec -n "${NS}" chat-postgresql-0 -c postgresql -- bash -c 'echo $POSTGRES_POSTGRES_PASSWORD' 2>/dev/null)
|
|
fi
|
|
|
|
${K} exec -n "${NS}" chat-postgresql-0 -c postgresql -- bash -c "env PGPASSWORD='${SYNAPSE_PG_PW}' pg_dump -U postgres -d matrix -f /bitnami/postgresql/data/dump.sql" 2>&1
|
|
${K} cp "${NS}/chat-postgresql-0:/bitnami/postgresql/data/dump.sql" "${BACKUP_DIR}/dump-synapse.sql" 2>/dev/null || true
|
|
${K} exec -n "${NS}" chat-postgresql-0 -c postgresql -- rm -f /bitnami/postgresql/data/dump.sql
|
|
log "Synapse dump saved: ${BACKUP_DIR}/dump-synapse.sql ($(wc -c < "${BACKUP_DIR}/dump-synapse.sql") bytes)"
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 4: Dump MAS PostgreSQL
|
|
# -------------------------------------------------------------------
|
|
log "=== Step 4: Dumping MAS PostgreSQL ==="
|
|
|
|
MAS_PG_POD=$(${K} -n "${NS}" get pods -l "${MAS_PG_LABEL}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
|
|
|
if [ -n "${MAS_PG_POD}" ]; then
|
|
MAS_PG_PW=$(${K} exec -n "${NS}" "${MAS_PG_POD}" -c postgresql -- bash -c 'echo $POSTGRESQL_PASSWORD' 2>/dev/null)
|
|
MAS_DB=$(${K} exec -n "${NS}" "${MAS_PG_POD}" -c postgresql -- bash -c 'echo $POSTGRESQL_DATABASE' 2>/dev/null)
|
|
|
|
if [ -n "${MAS_PG_PW}" ]; then
|
|
${K} exec -n "${NS}" "${MAS_PG_POD}" -c postgresql -- bash -c "env PGPASSWORD='${MAS_PG_PW}' pg_dump -U '${MAS_DB:-mas}' -d '${MAS_DB:-mas}' -f /bitnami/postgresql/data/dump.sql" 2>&1
|
|
${K} cp "${NS}/${MAS_PG_POD}:/bitnami/postgresql/data/dump.sql" "${BACKUP_DIR}/dump-mas.sql" 2>/dev/null || true
|
|
${K} exec -n "${NS}" "${MAS_PG_POD}" -c postgresql -- rm -f /bitnami/postgresql/data/dump.sql
|
|
log "MAS dump saved: ${BACKUP_DIR}/dump-mas.sql ($(wc -c < "${BACKUP_DIR}/dump-mas.sql") bytes)"
|
|
else
|
|
warn "Could not read MAS PG password — skipping MAS dump."
|
|
fi
|
|
else
|
|
warn "No MAS PG pod found — skipping MAS dump."
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 5: Export secrets
|
|
# -------------------------------------------------------------------
|
|
log "=== Step 5: Exporting secrets ==="
|
|
|
|
for secret in chat-matrix chat-postgresql mas matrixrtc-livekit; do
|
|
if ${K} -n "${NS}" get secret "${secret}" >/dev/null 2>&1; then
|
|
${K} -n "${NS}" get secret "${secret}" -o yaml > "${BACKUP_DIR}/secret-${secret}.yaml"
|
|
log "Secret saved: secret-${secret}.yaml"
|
|
else
|
|
warn "Secret '${secret}' not found."
|
|
fi
|
|
done
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 6: Restart Synapse + MAS
|
|
# -------------------------------------------------------------------
|
|
if ${DOWNTIME}; then
|
|
log "=== Step 6: Restarting Synapse + MAS ==="
|
|
${K} -n "${NS}" scale deploy chat-matrix --replicas=1 2>/dev/null || warn "Could not scale Synapse."
|
|
${K} -n "${NS}" scale deploy mas --replicas=1 2>/dev/null || warn "Could not scale MAS."
|
|
log "Synapse + MAS restarted."
|
|
else
|
|
log "=== Step 6: Skipped (--no-downtime) ==="
|
|
fi
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 7: Write README
|
|
# -------------------------------------------------------------------
|
|
log "=== Step 7: Writing README ==="
|
|
|
|
cat > "${BACKUP_DIR}/README.txt" << READEOF
|
|
Backup for ${NS} — ${TIMESTAMP}
|
|
==================================
|
|
|
|
Instance: ${NAME} (namespace: ${NS})
|
|
Chart: matrix-2.9.17 (old chart, NOT ESS)
|
|
Dump PG: Synapse (chat-postgresql) + MAS (mas-postgresql)
|
|
|
|
Files:
|
|
dump-synapse.sql Synapse PostgreSQL dump (pg_dump -U postgres -d matrix)
|
|
dump-mas.sql MAS PostgreSQL dump (pg_dump -U mas -d mas)
|
|
synapse-media.tar.gz Synapse media files (/data/media_store/)
|
|
secret-chat-matrix.yaml Synapse secrets (signing.key, macaroon, etc.)
|
|
secret-chat-postgresql.yaml PG passwords
|
|
secret-mas.yaml MAS secrets (encryption-key, signing-key, shared-secret)
|
|
secret-matrixrtc-livekit.yaml LiveKit secrets
|
|
|
|
CRITICAL for restore:
|
|
- secret-chat-matrix.yaml (SYNAPSE_SIGNING_KEY for federation identity)
|
|
- secret-mas.yaml (MAS encryption-key for user sessions)
|
|
- dump-synapse.sql (all user data, rooms, messages)
|
|
- dump-mas.sql (MAS user auth data)
|
|
|
|
Migration to ESS chart notes:
|
|
- The old chart uses separate Helm releases per component (chat, element-call, livekit).
|
|
- ESS bundles everything into the matrix-stack chart.
|
|
- PostgreSQL is external (CNPG) on the new cluster.
|
|
- MAS keys must be restored EXACTLY as-is for user auth to work.
|
|
- The Synapse signing.key MUST match the federation identity.
|
|
READEOF
|
|
|
|
log "README saved."
|
|
|
|
# -------------------------------------------------------------------
|
|
# Summary
|
|
# -------------------------------------------------------------------
|
|
cat <<SUMMARY
|
|
|
|
${GREEN}========================================${NC}
|
|
${GREEN} Backup Complete: ${NS}${NC}
|
|
${GREEN}========================================${NC}
|
|
|
|
Directory: ${BACKUP_DIR}
|
|
Size: $(du -sh "${BACKUP_DIR}" | cut -f1)
|
|
Downtime: ${DOWNTIME}
|
|
|
|
SUMMARY
|