#!/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 # # Client compatibility (feature) — server min for current *official* clients: # Android / iOS store apps → default ≥ 10.11.0 (Play/App Store + mobile docs 2026) # Desktop (Win/Mac/Linux) → default ≥ 10.11.0 (current Desktop / ESR pairing) # Vendor support floor → default ≥ 10.11.0 (ESR; older = unsupported) # Web SPA → served by this server (always loads) but WARN if # server is below support floor (security/EOL). # # Env: # MATTERMOST_CLIENT_CHECK=1|0 master switch (default 1) # MATTERMOST_MOBILE_CHECK=1|0 alias / legacy (if set 0 and CLIENT unset → off) # MATTERMOST_MOBILE_MIN_SERVER default 10.11.0 # MATTERMOST_DESKTOP_MIN_SERVER default 10.11.0 # MATTERMOST_SUPPORT_MIN_SERVER default 10.11.0 (vendor-supported floor) # # 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)" # Defaults: current official clients + vendor ESR floor (2026-07 docs). : "${MATTERMOST_MOBILE_MIN_SERVER:=10.11.0}" : "${MATTERMOST_DESKTOP_MIN_SERVER:=10.11.0}" : "${MATTERMOST_SUPPORT_MIN_SERVER:=10.11.0}" # Master switch: CLIENT_CHECK, else legacy MOBILE_CHECK, else on if [ -n "${MATTERMOST_CLIENT_CHECK:-}" ]; then : elif [ -n "${MATTERMOST_MOBILE_CHECK:-}" ]; then MATTERMOST_CLIENT_CHECK="${MATTERMOST_MOBILE_CHECK}" else MATTERMOST_CLIENT_CHECK=1 fi : "${MATTERMOST_MOBILE_CHECK:=${MATTERMOST_CLIENT_CHECK}}" 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 "client policy" "mobile≥${MATTERMOST_MOBILE_MIN_SERVER} · desktop≥${MATTERMOST_DESKTOP_MIN_SERVER} · support≥${MATTERMOST_SUPPORT_MIN_SERVER} (CLIENT_CHECK=${MATTERMOST_CLIENT_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 official client families (mobile / desktop / support / web) --- set_group clients mm_ver_ge() { # $1=server $2=need → 0 if server >= need python3 - "$1" "$2" <<'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) sys.exit(0 if parse(sys.argv[1]) >= parse(sys.argv[2]) else 1) PY } # One policy line: id|min_env_default|severity(error|warn)|human label # severity: error → fail; warn → warn _mm_client_policies() { printf '%s\n' \ "mobile|${MATTERMOST_MOBILE_MIN_SERVER}|error|Android/iOS store apps (Mattermost Mobile)" \ "desktop|${MATTERMOST_DESKTOP_MIN_SERVER}|error|Desktop app (Windows/macOS/Linux official)" \ "support|${MATTERMOST_SUPPORT_MIN_SERVER}|error|vendor-supported server floor (ESR/current)" \ "web|${MATTERMOST_SUPPORT_MIN_SERVER}|warn|browser webapp (loads with server, but EOL server is insecure/unsupported)" } if [ "${MATTERMOST_CLIENT_CHECK}" = "0" ]; then info "client compat" "skipped (MATTERMOST_CLIENT_CHECK=0 / MATTERMOST_MOBILE_CHECK=0)" elif [ -z "$MM_SERVER_VER" ]; then warn "server version" "could not parse X-Version-Id from /api/v4/system/ping — cannot judge client compatibility" else info "server version" "X-Version-Id → ${MM_SERVER_VER}" while IFS='|' read -r cid cmin csever clabel; do [ -n "$cid" ] || continue if mm_ver_ge "$MM_SERVER_VER" "$cmin"; then ok "${cid} compat" "server ${MM_SERVER_VER} ≥ ${cmin} · ${clabel} OK" else detail="server ${MM_SERVER_VER} < ${cmin} · ${clabel}" hint="upgrade Mattermost server · set MATTERMOST_${cid^^}_MIN_SERVER= or MATTERMOST_CLIENT_CHECK=0" # Fix hint for support/web keys case "$cid" in mobile) hint="upgrade server · MATTERMOST_MOBILE_MIN_SERVER= / MATTERMOST_CLIENT_CHECK=0" ;; desktop) hint="upgrade server · MATTERMOST_DESKTOP_MIN_SERVER= / MATTERMOST_CLIENT_CHECK=0" ;; support) hint="server below vendor support floor · MATTERMOST_SUPPORT_MIN_SERVER= / MATTERMOST_CLIENT_CHECK=0" ;; web) hint="web SPA is served by this old server (works) but platform is unsupported · upgrade recommended" ;; esac if [ "$csever" = "error" ]; then fail "${cid} compat" "$detail" "$hint" else warn "${cid} compat" "$detail · $hint" fi fi done < <(_mm_client_policies) fi # Client config (optional): BuildDate / advertised Android/iOS 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) print((d.get("AndroidMinVersion") or "").strip()) PY ) ios_min=$(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("IosMinVersion") or d.get("IOSMinVersion") or "").strip()) PY ) if [ -n "$build" ]; then info "client config" "BuildDate/Version=${build}${and_min:+ · AndroidMinVersion=${and_min}}${ios_min:+ · IosMinVersion=${ios_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 if [ -n "$ios_min" ] && [ "$ios_min" != "0.0.0" ]; then info "ios min (server)" "server advertises IosMinVersion=${ios_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; client floors: mobile/desktop/support ≥${MATTERMOST_MOBILE_MIN_SERVER}/${MATTERMOST_DESKTOP_MIN_SERVER}/${MATTERMOST_SUPPORT_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