#!/usr/bin/env bash # ============================================================================== # Mattermost Deployment Script (Docker Compose + Traefik) # ============================================================================== # Deploys Mattermost + PostgreSQL behind the shared Traefik reverse proxy. # ChatOps-optimized: bot accounts, webhooks, personal access tokens enabled. # # Usage: deploy-docker-mattermost.sh # deploy-docker-mattermost.sh --domain chat.us.an2.io # deploy-docker-mattermost.sh --remove [--purge] [--yes] # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common.sh" # --- Parse arguments --- parse_args "$@" # --- Service config --- SERVICE_NAME="mattermost" BASE_DIR="/opt/${SERVICE_NAME}" CONFIG_NAME="${SERVICE_NAME}" UNIT_NAME="${SERVICE_NAME}-docker" CALLS_PORT="${CALLS_PORT:-8443}" # --- Handle --remove --- if [[ "$ARG_REMOVE" == "1" ]]; then require_root do_remove "$SERVICE_NAME" "$CONFIG_NAME" "$UNIT_NAME" "$BASE_DIR" exit 0 fi # --- Domain resolution: --domain > existing .env > default --- if [[ -n "$ARG_DOMAIN" ]]; then DOMAIN="$ARG_DOMAIN" elif [[ -f "${BASE_DIR}/.env" ]] && grep -q '^DOMAIN=' "${BASE_DIR}/.env"; then DOMAIN="$(grep '^DOMAIN=' "${BASE_DIR}/.env" | cut -d= -f2)" else DOMAIN="mm.an2.io" fi # --- Detect current state for banner --- traefik_status="Not found — will deploy" if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^traefik$'; then traefik_status="Running" elif [[ -f "${TRAEFIK_DIR}/docker-compose.yml" ]]; then traefik_status="Stopped — will start" fi install_mode="Fresh install" if [[ -f "${BASE_DIR}/.env" ]]; then install_mode="Re-run (preserving data)" fi # --- Print deployment plan --- echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e "${CYAN} Deploying Mattermost (Docker)${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e " Domain: ${DOMAIN}" echo -e " Data: ${BASE_DIR}" echo -e " Calls Port: ${CALLS_PORT}" echo -e " Traefik: ${traefik_status}" echo -e " Mode: ${install_mode}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" # --- Check if already deployed and running --- if [[ -f "${BASE_DIR}/docker-compose.yml" ]] && \ docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^mm-app$'; then echo -e "${GREEN} Mattermost is already running — nothing to do.${NC}" echo "" echo -e " Logs: docker logs -f mm-app" echo -e " Backup: ${SCRIPT_DIR}/backup-mattermost.sh" echo -e " Remove: $0 --remove [--purge]" echo "" exit 0 fi # --- Shared infrastructure --- require_root detect_os install_prerequisites ensure_docker_network ensure_traefik configure_firewall "${CALLS_PORT}/udp" "${CALLS_PORT}/tcp" # --- Backup restore check --- if check_and_restore_backup "$SERVICE_NAME"; then saved_domain="$DOMAIN" # shellcheck source=/dev/null source "${BASE_DIR}/.env" DOMAIN="$saved_domain" else # --- Fresh install --- if [[ -f "${BASE_DIR}/.env" ]]; then saved_domain="$DOMAIN" # shellcheck source=/dev/null source "${BASE_DIR}/.env" DOMAIN="$saved_domain" else POSTGRES_USER="mmuser" POSTGRES_PASSWORD="$(generate_password)" POSTGRES_DB="mattermost" fi info "Creating directory layout..." mkdir -p "${BASE_DIR}"/{postgres/data,mattermost/{config,data,logs,plugins,client-plugins,bleve-indexes}} chown -R 2000:2000 "${BASE_DIR}/mattermost" # --- Generate .env --- cat > "${BASE_DIR}/.env" < "${BASE_DIR}/docker-compose.yml" <<'COMPOSE' # Mattermost + PostgreSQL # Based on https://github.com/mattermost/docker services: postgres: image: postgres:16-alpine container_name: mm-postgres restart: unless-stopped security_opt: - no-new-privileges:true mem_limit: 4G read_only: true tmpfs: - /tmp - /var/run/postgresql volumes: - ./postgres/data:/var/lib/postgresql environment: - TZ - POSTGRES_USER - POSTGRES_PASSWORD - POSTGRES_DB networks: - mm-internal logging: driver: json-file options: max-size: "50m" max-file: "5" mattermost: depends_on: postgres: condition: service_started image: mattermost/mattermost-enterprise-edition:release-11.0 container_name: mm-app restart: unless-stopped security_opt: - no-new-privileges:true mem_limit: 4G tmpfs: - /tmp volumes: - ./mattermost/config:/mattermost/config:rw - ./mattermost/data:/mattermost/data:rw - ./mattermost/logs:/mattermost/logs:rw - ./mattermost/plugins:/mattermost/plugins:rw - ./mattermost/client-plugins:/mattermost/client/plugins:rw - ./mattermost/bleve-indexes:/mattermost/bleve-indexes:rw environment: - TZ - MM_SQLSETTINGS_DRIVERNAME - MM_SQLSETTINGS_DATASOURCE - MM_BLEVESETTINGS_INDEXDIR - MM_SERVICESETTINGS_SITEURL - MM_SERVICESETTINGS_ENABLEBOTACCOUNTCREATION - MM_SERVICESETTINGS_ENABLEOAUTHSERVICEPROVIDER - MM_SERVICESETTINGS_ENABLEINCOMINGWEBHOOKS - MM_SERVICESETTINGS_ENABLEOUTGOINGWEBHOOKS - MM_SERVICESETTINGS_ENABLEPOSTUSERNAMEOVERRIDE - MM_SERVICESETTINGS_ENABLEPOSTICONOVERRIDE - MM_SERVICESETTINGS_ENABLECUSTOMEMOJI - MM_SERVICESETTINGS_ENABLEUSERACCESSTOKENS - MM_PLUGINSETTINGS_ENABLEUPLOADS - MM_TEAMSETTINGS_ENABLEOPENSERVER ports: - "${CALLS_PORT}:${CALLS_PORT}/udp" - "${CALLS_PORT}:${CALLS_PORT}/tcp" networks: - mm-internal - traefik-public logging: driver: json-file options: max-size: "50m" max-file: "5" networks: mm-internal: driver: bridge traefik-public: external: true COMPOSE ok "docker-compose.yml written." fi # --- Always do these (fresh or restored) --- write_traefik_dynamic_config "$CONFIG_NAME" "$DOMAIN" "http://mm-app:8065" install_systemd_unit "$UNIT_NAME" "$BASE_DIR" (cd "$BASE_DIR" && docker compose up -d) wait_for_healthy "mm-app" "http://localhost:8443/api/v4/system/ping" 120 # --- Summary --- echo "" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e "${GREEN} Mattermost deployed successfully${NC}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e " URL: https://${DOMAIN}" echo -e " Calls: Port ${CALLS_PORT} (UDP+TCP)" echo -e " Data: ${BASE_DIR}/" echo -e " Traefik: ${TRAEFIK_DYNAMIC_DIR}/${CONFIG_NAME}.yml" echo -e " Systemd: systemctl status ${UNIT_NAME}" echo -e " Logs: docker logs -f mm-app" echo "" echo -e " ${YELLOW}Next: Open https://${DOMAIN} and create your admin account.${NC}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo ""