Improve Mattermost OIDC mobile bridge installer

This commit is contained in:
2026-09-28 22:06:17 +02:00
parent 1ce47febbf
commit da911ff607
+216 -29
View File
@@ -35,6 +35,7 @@ SYMLINK_CHANGED=false
UNIT_CREATED=false UNIT_CREATED=false
ROLLBACK_TARGET="" ROLLBACK_TARGET=""
LEGACY_BRIDGE_BACKUP="" LEGACY_BRIDGE_BACKUP=""
REINSTALL_BRIDGE_BACKUP=""
# ---------- # ----------
@@ -162,6 +163,7 @@ ask_yes_no() {
rollback_bridge() { rollback_bridge() {
local rollback_failed=false local rollback_failed=false
local rollback_path="" local rollback_path=""
local rollback_tmp_link="${MM_BRIDGE_LINK}.rollback.$$"
[[ "$SYMLINK_CHANGED" = true ]] || return 0 [[ "$SYMLINK_CHANGED" = true ]] || return 0
@@ -189,11 +191,12 @@ rollback_bridge() {
if [[ "$rollback_failed" = true ]]; then if [[ "$rollback_failed" = true ]]; then
error "Initial-install cleanup was not fully successful. Please check '${MM_BRIDGE_LINK}', '${MM_BRIDGE_SERVICE}' and nginx manually." error "Initial-install cleanup was not fully successful. Please check '${MM_BRIDGE_LINK}', '${MM_BRIDGE_SERVICE}' and nginx manually."
return 1
else else
info "Initial activation was removed. The new binary, source directory and unit file were kept for analysis." info "Initial activation was removed. The new binary, source directory and unit file were kept for analysis."
fi
return 0 return 0
fi fi
fi
warn "The new bridge did not pass all checks. Trying to restore the previous installation." warn "The new bridge did not pass all checks. Trying to restore the previous installation."
@@ -205,25 +208,85 @@ rollback_bridge() {
fi fi
fi fi
if [[ -n "$ROLLBACK_TARGET" && -e "$rollback_path" ]]; then # A same-version reinstall replaces the file referenced by ROLLBACK_TARGET.
ln -s "$ROLLBACK_TARGET" "${MM_BRIDGE_LINK}.rollback.$$" >> "$log_file" 2>&1 \ # Restore that saved binary first; changing the symlink alone would otherwise
&& mv -Tf "${MM_BRIDGE_LINK}.rollback.$$" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 \ # still point to the newly installed (and possibly broken) binary.
|| rollback_failed=true if [[ -n "$REINSTALL_BRIDGE_BACKUP" ]]; then
elif [[ -n "$LEGACY_BRIDGE_BACKUP" && -f "$LEGACY_BRIDGE_BACKUP" ]]; then echononl "Restore previous same-version bridge binary.."
rm -f "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 _restore_tmp="${MM_BRIDGE_BIN}.restore.$$"
cp -a "$LEGACY_BRIDGE_BACKUP" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 || rollback_failed=true rm -f "$_restore_tmp"
if [[ -f "$REINSTALL_BRIDGE_BACKUP" ]] \
&& cp -a "$REINSTALL_BRIDGE_BACKUP" "$_restore_tmp" >> "$log_file" 2>&1 \
&& mv -Tf "$_restore_tmp" "$MM_BRIDGE_BIN" >> "$log_file" 2>&1 ; then
echo_ok
else else
rm -f "$_restore_tmp"
echo_failed
error "Could not restore '${REINSTALL_BRIDGE_BACKUP}' to '${MM_BRIDGE_BIN}'."
rollback_failed=true rollback_failed=true
fi fi
fi
if [[ "$rollback_failed" = false ]]; then if [[ "$rollback_failed" = false ]]; then
systemctl restart "$MM_BRIDGE_SERVICE" >> "$log_file" 2>&1 || rollback_failed=true if [[ -n "$ROLLBACK_TARGET" && -e "$rollback_path" ]]; then
echononl "Restore previous stable bridge symlink.."
rm -f "$rollback_tmp_link"
if ln -s "$ROLLBACK_TARGET" "$rollback_tmp_link" >> "$log_file" 2>&1 \
&& mv -Tf "$rollback_tmp_link" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 ; then
echo_ok
else
rm -f "$rollback_tmp_link"
echo_failed
error "Could not restore stable bridge symlink '${MM_BRIDGE_LINK}' -> '${ROLLBACK_TARGET}'."
rollback_failed=true
fi
elif [[ -n "$LEGACY_BRIDGE_BACKUP" && -f "$LEGACY_BRIDGE_BACKUP" ]]; then
echononl "Restore previous legacy bridge binary.."
if rm -f "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 \
&& cp -a "$LEGACY_BRIDGE_BACKUP" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
error "Could not restore legacy bridge binary from '${LEGACY_BRIDGE_BACKUP}'."
rollback_failed=true
fi
else
error "No usable rollback target was found."
rollback_failed=true
fi
fi
if [[ "$rollback_failed" = false ]]; then
echononl "Restart previous Mattermost OIDC Mobile Bridge service.."
if systemctl restart "$MM_BRIDGE_SERVICE" >> "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
error "Could not restart '${MM_BRIDGE_SERVICE}' after rollback."
rollback_failed=true
fi
fi
if [[ "$NGINX_CHANGED" = true && -n "$NGINX_BACKUP" && -f "$NGINX_BACKUP" ]]; then
echononl "Restore previous nginx configuration.."
if cp -a "$NGINX_BACKUP" "$NGINX_CONFIG_FILE" >> "$log_file" 2>&1 \
&& nginx -t >> "$log_file" 2>&1 \
&& systemctl reload nginx.service >> "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
error "Could not fully restore/reload the previous nginx configuration. Check nginx manually."
rollback_failed=true
fi
fi fi
if [[ "$rollback_failed" = true ]]; then if [[ "$rollback_failed" = true ]]; then
error "Automatic rollback failed. Please check '${MM_BRIDGE_LINK}' and '${MM_BRIDGE_SERVICE}' manually." error "Automatic rollback failed. Please check the bridge installation, '${MM_BRIDGE_SERVICE}' and nginx configuration manually."
return 1
else else
info "Previous bridge installation was restored and restarted." info "Previous bridge installation was restored and restarted."
return 0
fi fi
} }
@@ -777,7 +840,14 @@ blank_line
if [[ -e "$MM_BRIDGE_BIN" ]]; then if [[ -e "$MM_BRIDGE_BIN" ]]; then
echononl "Backup existing bridge binary.." echononl "Backup existing bridge binary.."
if mv "$MM_BRIDGE_BIN" "${MM_BRIDGE_BIN}.${backup_date}" > "$log_file" 2>&1 ; then _bridge_binary_backup="${MM_BRIDGE_BIN}.${backup_date}"
if mv "$MM_BRIDGE_BIN" "$_bridge_binary_backup" > "$log_file" 2>&1 ; then
# During a same-version reinstall this backup is the actual rollback
# payload. ROLLBACK_TARGET alone is insufficient because it names the
# same versioned path that is about to be replaced.
if [[ "$INSTALLATION_MODE" = "upgrade" && "$MM_NEW_VERSION" = "$MM_CURRENT_VERSION" ]]; then
REINSTALL_BRIDGE_BACKUP="$_bridge_binary_backup"
fi
echo_ok echo_ok
else else
echo_failed echo_failed
@@ -797,6 +867,25 @@ else
fatal "$(cat "$log_file")" fatal "$(cat "$log_file")"
fi fi
# On an initial installation the systemd unit below already references the
# stable bridge symlink. Create that symlink before systemd-analyze verifies
# the unit. During upgrades, keep the existing symlink untouched until the
# final activation step.
if [[ "$INSTALLATION_MODE" = "initial-installation" ]]; then
echononl "Create initial stable bridge symlink.."
_tmp_link="${MM_BRIDGE_LINK}.new.$$"
rm -f "$_tmp_link"
if ln -s "${MM_BRIDGE_NAME}-${MM_NEW_VERSION}" "$_tmp_link" > "$log_file" 2>&1 \
&& mv -Tf "$_tmp_link" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 ; then
SYMLINK_CHANGED=true
echo_ok
else
rm -f "$_tmp_link"
echo_failed
fatal "$(cat "$log_file")"
fi
fi
# Preserve an old unversioned installation before replacing it with a symlink. # Preserve an old unversioned installation before replacing it with a symlink.
if [[ "$INSTALLATION_MODE" = "legacy-upgrade" ]]; then if [[ "$INSTALLATION_MODE" = "legacy-upgrade" ]]; then
LEGACY_BRIDGE_BACKUP="${MM_BRIDGE_LINK}.${backup_date}" LEGACY_BRIDGE_BACKUP="${MM_BRIDGE_LINK}.${backup_date}"
@@ -898,6 +987,20 @@ else
fi fi
# Ensure that the bridge service is enabled also when an existing unit is reused.
echononl "Ensure '${MM_BRIDGE_SERVICE}' is enabled.."
if systemctl is-enabled --quiet "$MM_BRIDGE_SERVICE" 2>/dev/null ; then
echo_ok
else
if systemctl enable "$MM_BRIDGE_SERVICE" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
fatal "$(cat "$log_file")"
fi
fi
# ---------- # ----------
# Configure nginx if requested # Configure nginx if requested
# ---------- # ----------
@@ -991,7 +1094,7 @@ NGINX_MOBILE_EOF
fi fi
echononl "Insert missing bridge locations before Mattermost 'location /'.." echononl "Insert missing bridge locations before Mattermost 'location /'.."
python3 - "$NGINX_CONFIG_FILE" "$NGINX_INSERT_FILE" "$MM_BRIDGE_UPSTREAM" <<'PY_EOF' > "$log_file" 2>&1 python3 - "$NGINX_CONFIG_FILE" "$NGINX_INSERT_FILE" "$MM_BRIDGE_UPSTREAM" "$_mm_site_host" <<'PY_EOF' > "$log_file" 2>&1
import re import re
import sys import sys
from pathlib import Path from pathlib import Path
@@ -999,6 +1102,7 @@ from pathlib import Path
config = Path(sys.argv[1]) config = Path(sys.argv[1])
insert = Path(sys.argv[2]).read_text() insert = Path(sys.argv[2]).read_text()
upstream = sys.argv[3].rstrip("/") upstream = sys.argv[3].rstrip("/")
site_host = sys.argv[4].strip()
text = config.read_text() text = config.read_text()
def matching_brace(data, open_pos): def matching_brace(data, open_pos):
@@ -1032,25 +1136,53 @@ def matching_brace(data, open_pos):
return pos return pos
return None return None
servers = [] all_servers = []
for match in re.finditer(r'(?m)^[ \t]*server[ \t]*\{', text): for match in re.finditer(r'(?m)^[ \t]*server[ \t]*\{', text):
open_pos = text.find("{", match.start(), match.end()) open_pos = text.find("{", match.start(), match.end())
close_pos = matching_brace(text, open_pos) close_pos = matching_brace(text, open_pos)
if close_pos is None: if close_pos is None:
raise SystemExit("Could not parse nginx server block") raise SystemExit("Could not parse nginx server block")
body = text[open_pos + 1:close_pos] body = text[open_pos + 1:close_pos]
all_servers.append((open_pos + 1, close_pos, body))
def general_locations(body):
return list(re.finditer(r'(?m)^[ \t]*location[ \t]+/[ \t]*\{', body))
# Prefer the Mattermost vHost identified by SiteURL/server_name. This works
# with both direct proxy_pass targets and named nginx upstreams. The HTTP
# redirect vHost is excluded because it has no general "location /" block.
servers = []
if site_host:
for server in all_servers:
body = server[2]
names = []
for match in re.finditer(r'(?m)^[ \t]*server_name[ \t]+([^;]+);', body):
names.extend(match.group(1).split())
if site_host in names and len(general_locations(body)) == 1:
servers.append(server)
# Fallback for installations where SiteURL is unavailable: retain the old
# direct-upstream detection.
if not servers:
proxy_re = re.compile(r'proxy_pass\s+' + re.escape(upstream) + r'/?\s*;') proxy_re = re.compile(r'proxy_pass\s+' + re.escape(upstream) + r'/?\s*;')
if proxy_re.search(body): servers = [
servers.append((open_pos + 1, close_pos, body)) server for server in all_servers
if proxy_re.search(server[2]) and len(general_locations(server[2])) == 1
]
if len(servers) != 1: if len(servers) != 1:
raise SystemExit(f"Expected exactly one server block proxying to {upstream}, found {len(servers)}") if site_host:
raise SystemExit(
f"Expected exactly one Mattermost server block for server_name "
f"{site_host} with a general 'location /', found {len(servers)}"
)
raise SystemExit(
f"Expected exactly one server block proxying to {upstream} "
f"with a general 'location /', found {len(servers)}"
)
body_start, body_end, body = servers[0] body_start, body_end, body = servers[0]
locations = list(re.finditer(r'(?m)^[ \t]*location[ \t]+/[ \t]*\{', body)) locations = general_locations(body)
if len(locations) != 1:
raise SystemExit(f"Expected exactly one general 'location /' in Mattermost server block, found {len(locations)}")
insert_pos = body_start + locations[0].start() insert_pos = body_start + locations[0].start()
text = text[:insert_pos] + insert + text[insert_pos:] text = text[:insert_pos] + insert + text[insert_pos:]
config.write_text(text) config.write_text(text)
@@ -1089,6 +1221,12 @@ blank_line
echo -e "\033[37m\033[1mActivate Mattermost OIDC Mobile Bridge..\033[m" echo -e "\033[37m\033[1mActivate Mattermost OIDC Mobile Bridge..\033[m"
blank_line blank_line
if [[ "$INSTALLATION_MODE" = "initial-installation" ]] \
&& [[ -L "$MM_BRIDGE_LINK" ]] \
&& [[ "$(readlink "$MM_BRIDGE_LINK")" = "${MM_BRIDGE_NAME}-${MM_NEW_VERSION}" ]]; then
echononl "Keep initial stable bridge symlink to '${MM_BRIDGE_NAME}-${MM_NEW_VERSION}'.."
echo_ok
else
echononl "Switch stable bridge symlink to '${MM_BRIDGE_NAME}-${MM_NEW_VERSION}'.." echononl "Switch stable bridge symlink to '${MM_BRIDGE_NAME}-${MM_NEW_VERSION}'.."
_tmp_link="${MM_BRIDGE_LINK}.new.$$" _tmp_link="${MM_BRIDGE_LINK}.new.$$"
rm -f "$_tmp_link" rm -f "$_tmp_link"
@@ -1101,6 +1239,7 @@ else
echo_failed echo_failed
fatal "$(cat "$log_file")" fatal "$(cat "$log_file")"
fi fi
fi
if [[ "$NGINX_CHANGED" = true ]]; then if [[ "$NGINX_CHANGED" = true ]]; then
echononl "Reload nginx service.." echononl "Reload nginx service.."
@@ -1109,14 +1248,32 @@ if [[ "$NGINX_CHANGED" = true ]]; then
else else
echo_failed echo_failed
error "nginx reload failed. Restoring the previous nginx configuration." error "nginx reload failed. Restoring the previous nginx configuration."
nginx_rollback_failed=false
cp -a "$NGINX_CONFIG_FILE" "${NGINX_CONFIG_FILE}.failed-${backup_date}" 2>/dev/null || true cp -a "$NGINX_CONFIG_FILE" "${NGINX_CONFIG_FILE}.failed-${backup_date}" 2>/dev/null || true
if cp -a "$NGINX_BACKUP" "$NGINX_CONFIG_FILE" >> "$log_file" 2>&1 && nginx -t >> "$log_file" 2>&1 && systemctl reload nginx.service >> "$log_file" 2>&1 ; then
if cp -a "$NGINX_BACKUP" "$NGINX_CONFIG_FILE" >> "$log_file" 2>&1 \
&& nginx -t >> "$log_file" 2>&1 \
&& systemctl reload nginx.service >> "$log_file" 2>&1 ; then
info "Previous nginx configuration was restored and reloaded." info "Previous nginx configuration was restored and reloaded."
NGINX_CHANGED=false
else else
error "Could not fully restore/reload the previous nginx configuration. Check nginx manually." error "Could not fully restore/reload the previous nginx configuration. Check nginx manually."
nginx_rollback_failed=true
fi
if rollback_bridge ; then
bridge_rollback_failed=false
else
bridge_rollback_failed=true
fi
if [[ "$nginx_rollback_failed" = false && "$bridge_rollback_failed" = false ]]; then
fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed because nginx could not be reloaded.\n The previous nginx configuration and bridge installation were successfully restored."
else
fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed because nginx could not be reloaded.\n The previous system state could not be fully restored.\n\n Manual intervention is required."
fi fi
rollback_bridge
fatal "nginx reload failed after adding the Mattermost OIDC Mobile Bridge locations."
fi fi
fi fi
@@ -1125,20 +1282,29 @@ if systemctl restart "$MM_BRIDGE_SERVICE" > "$log_file" 2>&1 ; then
echo_ok echo_ok
else else
echo_failed echo_failed
rollback_bridge if rollback_bridge ; then
fatal "Failed to restart '${MM_BRIDGE_SERVICE}'." fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed because '${MM_BRIDGE_SERVICE}' could not be restarted.\n The previous bridge installation was successfully restored."
else
fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed because '${MM_BRIDGE_SERVICE}' could not be restarted.\n The previous installation could not be restored.\n\n Manual intervention is required."
fi
fi fi
if ! bridge_test ; then if ! bridge_test ; then
rollback_bridge if rollback_bridge ; then
fatal "Mattermost OIDC Mobile Bridge functional test failed." fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed.\n The previous bridge installation was successfully restored."
else
fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed\n and the previous installation could not be restored.\n\n Manual intervention is required."
fi
fi fi
if [[ -n "$NGINX_CONFIG_FILE" ]]; then if [[ -n "$NGINX_CONFIG_FILE" ]]; then
if [[ -n "$MM_SITE_URL" ]]; then if [[ -n "$MM_SITE_URL" ]]; then
if ! nginx_e2e_test ; then if ! nginx_e2e_test ; then
rollback_bridge if rollback_bridge ; then
fatal "Mattermost OIDC Mobile Bridge end-to-end test through nginx failed." fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed during the nginx end-to-end test.\n The previous bridge installation was successfully restored."
else
fatal "Installation of Mattermost OIDC Mobile Bridge ${MM_NEW_VERSION} failed during the nginx end-to-end test\n and the previous installation could not be restored.\n\n Manual intervention is required."
fi
fi fi
else else
warn "Mattermost SiteURL could not be read from '${MM_CONFIG_FILE}'. End-to-end nginx tests were skipped." warn "Mattermost SiteURL could not be read from '${MM_CONFIG_FILE}'. End-to-end nginx tests were skipped."
@@ -1146,6 +1312,27 @@ if [[ -n "$NGINX_CONFIG_FILE" ]]; then
fi fi
# ----------
# Remove redundant same-version backup after successful tests
# ----------
if [[ -n "$REINSTALL_BRIDGE_BACKUP" && -f "$REINSTALL_BRIDGE_BACKUP" ]]; then
if cmp -s "$REINSTALL_BRIDGE_BACKUP" "$MM_BRIDGE_BIN"; then
echononl "Remove identical same-version bridge backup.."
if rm -f "$REINSTALL_BRIDGE_BACKUP" > "$log_file" 2>&1 ; then
echo_ok
REINSTALL_BRIDGE_BACKUP=""
else
echo_failed
warn "Could not remove redundant bridge backup '${REINSTALL_BRIDGE_BACKUP}'."
fi
else
echononl "Keep different same-version bridge backup.."
echo_ok
fi
fi
# ---------- # ----------
# Final information # Final information
# ---------- # ----------