#!/usr/bin/env bash set -Eeuo pipefail PROJECT_NAME="SWChat" INSTALL_DIR="${INSTALL_DIR:-/opt/swchat}" SOURCE_DIR="${SOURCE_DIR:-$INSTALL_DIR/source}" DATA_DIR="${DATA_DIR:-$INSTALL_DIR/data}" BACKUP_DIR="${BACKUP_DIR:-$INSTALL_DIR/backups}" ENV_FILE="${ENV_FILE:-$INSTALL_DIR/.env}" COMPOSE_FILE="${COMPOSE_FILE:-$SOURCE_DIR/docker-compose.yml}" SERVER_NAME="${SERVER_NAME:-matrix.stackworks.ru}" REPORT_STATS="${REPORT_STATS:-no}" MATRIX_BIND_HOST="${MATRIX_BIND_HOST:-127.0.0.1}" AUTO_START="${AUTO_START:-yes}" AUTO_INSTALL_DOCKER="${AUTO_INSTALL_DOCKER:-yes}" log() { echo "[INFO] $*"; } warn() { echo "[WARN] $*"; } fail() { echo "[ERROR] $*" >&2; exit 1; } need_root() { if [ "$(id -u)" -ne 0 ]; then fail "Run as root: sudo bash scripts/install.sh" fi } need_command() { command -v "$1" >/dev/null 2>&1 || fail "Required command not found: $1" } random_secret() { if command -v openssl >/dev/null 2>&1; then openssl rand -hex 32 else tr -dc 'a-f0-9' /dev/null 2>&1 && docker compose version >/dev/null 2>&1; then log "Docker and Docker Compose plugin already installed" return fi if [ "$AUTO_INSTALL_DOCKER" != "yes" ]; then fail "Docker is not installed and AUTO_INSTALL_DOCKER is not yes" fi detect_os log "Installing Docker Engine and Docker Compose plugin for $OS_ID $OS_CODENAME" apt-get update apt-get install -y ca-certificates curl gnupg lsb-release install -m 0755 -d /etc/apt/keyrings if [ ! -f /etc/apt/keyrings/docker.gpg ]; then curl -fsSL "https://download.docker.com/linux/$OS_ID/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg else warn "Docker GPG key already exists, keeping it" fi DOCKER_LIST="/etc/apt/sources.list.d/docker.list" if [ ! -f "$DOCKER_LIST" ]; then echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS_ID $OS_CODENAME stable" > "$DOCKER_LIST" else warn "Docker apt source already exists, keeping it" fi apt-get update apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin systemctl enable docker >/dev/null 2>&1 || true systemctl start docker >/dev/null 2>&1 || true command -v docker >/dev/null 2>&1 || fail "Docker installation failed" docker compose version >/dev/null 2>&1 || fail "Docker Compose plugin installation failed" } safe_mkdirs() { log "Creating project directories" mkdir -p "$INSTALL_DIR" "$SOURCE_DIR" "$DATA_DIR" "$BACKUP_DIR" "$INSTALL_DIR/logs" mkdir -p "$DATA_DIR/postgres" "$DATA_DIR/synapse" "$DATA_DIR/coturn" } check_protected_assets() { log "Split-server policy" echo " - This installer configures SWChat/Pulse Core only" echo " - FastPanel server is not modified" echo " - ArtistFlow is not involved" echo " - widget.stackworks.ru is not involved" } check_requirements() { log "Checking requirements" install_docker need_command docker docker compose version >/dev/null 2>&1 || fail "Docker Compose plugin is required" need_command sed need_command grep need_command ss } write_env() { if [ -f "$ENV_FILE" ]; then warn ".env already exists, keeping it: $ENV_FILE" if ! grep -q '^MATRIX_BIND_HOST=' "$ENV_FILE"; then echo "MATRIX_BIND_HOST=$MATRIX_BIND_HOST" >> "$ENV_FILE" warn "Added missing MATRIX_BIND_HOST=$MATRIX_BIND_HOST to existing .env" fi return fi log "Creating .env" POSTGRES_PASSWORD="$(random_secret)" REGISTRATION_SHARED_SECRET="$(random_secret)" MACAROON_SECRET_KEY="$(random_secret)" FORM_SECRET="$(random_secret)" cat > "$ENV_FILE" </dev/null; then warn "Synapse config already patched, skipping" return fi log "Patching Synapse config for PostgreSQL and public base URL" cp "$DATA_DIR/synapse/homeserver.yaml" "$BACKUP_DIR/homeserver.yaml.$(date +%F_%H-%M-%S).bak" cat >> "$DATA_DIR/synapse/homeserver.yaml" <<'EOF' # SWChat managed PostgreSQL database config database: name: psycopg2 args: user: synapse password: __POSTGRES_PASSWORD__ database: synapse host: postgres cp_min: 5 cp_max: 10 # SWChat public endpoint public_baseurl: https://__SERVER_NAME__/ trusted_key_servers: - server_name: matrix.org EOF . "$ENV_FILE" sed -i "s|__POSTGRES_PASSWORD__|$POSTGRES_PASSWORD|g" "$DATA_DIR/synapse/homeserver.yaml" sed -i "s|__SERVER_NAME__|$SERVER_NAME|g" "$DATA_DIR/synapse/homeserver.yaml" } start_stack() { if [ "$AUTO_START" != "yes" ]; then warn "AUTO_START is not yes, skipping docker compose up" return fi log "Starting SWChat containers" cd "$SOURCE_DIR" docker compose --env-file "$ENV_FILE" up -d } show_next_steps() { echo echo "== Installation complete ==" echo echo "SWChat/Pulse Core installed on this server" echo "Matrix local endpoint: http://127.0.0.1:8008" echo "Matrix bind host: $MATRIX_BIND_HOST" echo "Public Matrix server name: $SERVER_NAME" echo "Install dir: $INSTALL_DIR" echo echo "Next checks:" echo " sudo bash $SOURCE_DIR/scripts/healthcheck.sh" echo " curl http://127.0.0.1:8008/_matrix/client/versions" echo echo "If FastPanel reverse proxy is used over private LAN:" echo " 1) set MATRIX_BIND_HOST to the Core private IP in $ENV_FILE" echo " 2) run: sudo FASTPANEL_PROXY_IP=192.168.0.221 MATRIX_PORT=8008 bash $SOURCE_DIR/scripts/firewall_private_proxy.sh" echo } main() { echo "== $PROJECT_NAME Auto Installer ==" need_root check_protected_assets safe_mkdirs check_requirements ensure_compose_file write_env generate_synapse_config patch_synapse_config start_stack show_next_steps } main "$@"