#!/usr/bin/env bash # check_mattermost.sh — outside-in checks for Taler Mattermost (chat) # # Default host: mattermost.taler.net # Override: MATTERMOST_PUBLIC=https://mattermost.example.org # MATTERMOST_HOST=mattermost.example.org # # Mobile compatibility (feature): # Current Mattermost Android/iOS store apps require a minimum *server* version # (default MATTERMOST_MOBILE_MIN_SERVER=10.11.0 — Play Store / mobile docs 2026). # Server older than that → ERROR (users on newest app cannot rely on the server). # Override: MATTERMOST_MOBILE_MIN_SERVER=x.y.z # MATTERMOST_MOBILE_CHECK=0 to skip # # Outside-only (no SSH). Phase name: mattermost # set -euo pipefail ROOT=$(cd "$(dirname "$0")" && pwd) # shellcheck source=lib.sh source "$ROOT/lib.sh" set_area mattermost section "mattermost · public chat (outside-in)" # Minimum server version for current mobile apps (Mattermost docs / Play Store). : "${MATTERMOST_MOBILE_MIN_SERVER:=10.11.0}" : "${MATTERMOST_MOBILE_CHECK:=1}" if [ -n "${MATTERMOST_PUBLIC:-}" ]; then BASE="${MATTERMOST_PUBLIC%/}" elif [ -n "${MATTERMOST_HOST:-}" ]; then BASE="https://${MATTERMOST_HOST}" else BASE="https://mattermost.taler.net" fi HOST=${BASE#https://} HOST=${HOST#http://} HOST=${HOST%%/*} info "target" "$BASE" info "mobile policy" "newest store apps require server ≥ ${MATTERMOST_MOBILE_MIN_SERVER} (MATTERMOST_MOBILE_CHECK=${MATTERMOST_MOBILE_CHECK})" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT # GET with follow redirects (Mattermost SPA often 302 → /) mm_get() { local url="$1" out="$2" curl -skS -L --max-redirs 5 -m "${TIMEOUT:-12}" -o "$out" -w '%{http_code}' "$url" 2>/dev/null || echo 000 } # --- DNS / HTTPS landing --- set_group landing code=$(mm_get "$BASE/" "$tmp/root.html") if [ "$code" = "200" ] && head -c 512 "$tmp/root.html" | grep -qiE '/dev/null || echo 000) MM_SERVER_VER="" if [ -f "$hdr_file" ]; then # X-Version-Id: 9.5.3.8427860509.c8cabb… or 10.11.0.… MM_SERVER_VER=$(grep -i '^x-version-id:' "$hdr_file" | head -1 \ | sed 's/^[Xx]-[Vv]ersion-[Ii]d:[[:space:]]*//' | tr -d '\r' \ | sed 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/') fi if [ "$code" != "200" ]; then fail "system/ping" "$BASE/api/v4/system/ping → HTTP $code (want 200 JSON)" else if python3 - "$tmp/ping.json" <<'PY' 2>/dev/null import json, sys with open(sys.argv[1]) as f: d = json.load(f) sys.exit(0 if isinstance(d, dict) else 2) PY then ok "system/ping" "$BASE/api/v4/system/ping → HTTP 200 JSON${MM_SERVER_VER:+ · server ${MM_SERVER_VER}}" else fail "system/ping" "$BASE/api/v4/system/ping → HTTP 200 but not valid JSON object" fi fi # --- Server version vs current mobile app requirement --- set_group version if [ "${MATTERMOST_MOBILE_CHECK}" = "0" ]; then info "mobile compat" "skipped (MATTERMOST_MOBILE_CHECK=0)" elif [ -z "$MM_SERVER_VER" ]; then warn "server version" "could not parse X-Version-Id from /api/v4/system/ping (mobile min=${MATTERMOST_MOBILE_MIN_SERVER})" else info "server version" "X-Version-Id → ${MM_SERVER_VER} (mobile apps require ≥ ${MATTERMOST_MOBILE_MIN_SERVER})" # Compare MAJOR.MINOR.PATCH (semver-ish); returns 0 if server >= min if python3 - "$MM_SERVER_VER" "$MATTERMOST_MOBILE_MIN_SERVER" <<'PY' import sys def parse(v: str): parts = [] for p in v.strip().split(".")[:3]: try: parts.append(int(p)) except ValueError: parts.append(0) while len(parts) < 3: parts.append(0) return tuple(parts) server, need = parse(sys.argv[1]), parse(sys.argv[2]) sys.exit(0 if server >= need else 1) PY then ok "mobile compat" \ "server ${MM_SERVER_VER} ≥ ${MATTERMOST_MOBILE_MIN_SERVER} · current Android/iOS store apps OK" else # Hard ERROR: newest Play Store app refuses / breaks on unsupported servers fail "mobile compat" \ "server ${MM_SERVER_VER} < ${MATTERMOST_MOBILE_MIN_SERVER} (required by current Mattermost Android/iOS store apps)" \ "users on latest mobile app get connect failures/crashes · upgrade Mattermost server · override MATTERMOST_MOBILE_MIN_SERVER= or MATTERMOST_MOBILE_CHECK=0" fi fi # Client config (optional): BuildDate / advertised Android min if server sets it set_group client-config code=$(mm_get "$BASE/api/v4/config/client?format=old" "$tmp/client.json") if [ "$code" = "200" ] && [ -s "$tmp/client.json" ]; then build=$(python3 - "$tmp/client.json" <<'PY' 2>/dev/null import json, sys with open(sys.argv[1]) as f: d = json.load(f) print(d.get("BuildDate") or d.get("Version") or "") PY ) and_min=$(python3 - "$tmp/client.json" <<'PY' 2>/dev/null import json, sys with open(sys.argv[1]) as f: d = json.load(f) # empty strings are common on older servers print((d.get("AndroidMinVersion") or "").strip()) PY ) if [ -n "$build" ]; then info "client config" "BuildDate/Version=${build}${and_min:+ · AndroidMinVersion=${and_min}}" else info "client config" "HTTP 200 (no BuildDate field)" fi if [ -n "$and_min" ] && [ "$and_min" != "0.0.0" ]; then info "android min (server)" "server advertises AndroidMinVersion=${and_min}" fi else info "client config" "HTTP ${code:-?} — optional" fi # --- Login SPA (must be served for users) --- set_group login code=$(mm_get "$BASE/login" "$tmp/login.html") if [ "$code" = "200" ] && head -c 512 "$tmp/login.html" | grep -qiE '/dev/null 2>&1; then end=$(echo | openssl s_client -servername "$HOST" -connect "${HOST}:443" 2>/dev/null \ | openssl x509 -noout -enddate 2>/dev/null | sed 's/notAfter=//') if [ -n "$end" ]; then # days remaining end_epoch=$(date -d "$end" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$end" +%s 2>/dev/null || echo 0) now_epoch=$(date +%s) if [ "$end_epoch" -gt 0 ]; then days=$(( (end_epoch - now_epoch) / 86400 )) if [ "$days" -lt 0 ]; then fail "tls cert" "$HOST cert expired ($end)" elif [ "$days" -lt 14 ]; then warn "tls cert" "$HOST expires in ${days}d ($end)" elif [ "$days" -lt 30 ]; then warn "tls cert" "$HOST expires in ${days}d ($end)" else ok "tls cert" "$HOST valid · ${days}d left · $end" fi else info "tls cert" "$HOST notAfter=$end" fi else warn "tls cert" "$HOST could not read certificate" fi else info "tls cert" "openssl not available — skip expiry check" fi info "hint" "surface catalog also lists $HOST; this phase checks SPA + /api/v4/system/ping + mobile server min (${MATTERMOST_MOBILE_MIN_SERVER})" # Non-zero when any fail/err so parent phase + mon pages mark ERROR if [ "${FAIL_N:-0}" -gt 0 ]; then exit 1 fi exit 0