Improve Mattermost OIDC mobile bridge installer

This commit is contained in:
2026-09-28 22:06:17 +02:00
parent 1ce47febbf
commit da911ff607
+221 -34
View File
@@ -35,6 +35,7 @@ SYMLINK_CHANGED=false
UNIT_CREATED=false
ROLLBACK_TARGET=""
LEGACY_BRIDGE_BACKUP=""
REINSTALL_BRIDGE_BACKUP=""
# ----------
@@ -162,6 +163,7 @@ ask_yes_no() {
rollback_bridge() {
local rollback_failed=false
local rollback_path=""
local rollback_tmp_link="${MM_BRIDGE_LINK}.rollback.$$"
[[ "$SYMLINK_CHANGED" = true ]] || return 0
@@ -189,11 +191,12 @@ rollback_bridge() {
if [[ "$rollback_failed" = true ]]; then
error "Initial-install cleanup was not fully successful. Please check '${MM_BRIDGE_LINK}', '${MM_BRIDGE_SERVICE}' and nginx manually."
return 1
else
info "Initial activation was removed. The new binary, source directory and unit file were kept for analysis."
fi
return 0
fi
fi
warn "The new bridge did not pass all checks. Trying to restore the previous installation."
@@ -205,25 +208,85 @@ rollback_bridge() {
fi
fi
if [[ -n "$ROLLBACK_TARGET" && -e "$rollback_path" ]]; then
ln -s "$ROLLBACK_TARGET" "${MM_BRIDGE_LINK}.rollback.$$" >> "$log_file" 2>&1 \
&& mv -Tf "${MM_BRIDGE_LINK}.rollback.$$" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 \
|| rollback_failed=true
elif [[ -n "$LEGACY_BRIDGE_BACKUP" && -f "$LEGACY_BRIDGE_BACKUP" ]]; then
rm -f "$MM_BRIDGE_LINK" >> "$log_file" 2>&1
cp -a "$LEGACY_BRIDGE_BACKUP" "$MM_BRIDGE_LINK" >> "$log_file" 2>&1 || rollback_failed=true
# A same-version reinstall replaces the file referenced by ROLLBACK_TARGET.
# Restore that saved binary first; changing the symlink alone would otherwise
# still point to the newly installed (and possibly broken) binary.
if [[ -n "$REINSTALL_BRIDGE_BACKUP" ]]; then
echononl "Restore previous same-version bridge binary.."
_restore_tmp="${MM_BRIDGE_BIN}.restore.$$"
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
rm -f "$_restore_tmp"
echo_failed
error "Could not restore '${REINSTALL_BRIDGE_BACKUP}' to '${MM_BRIDGE_BIN}'."
rollback_failed=true
fi
fi
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
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
info "Previous bridge installation was restored and restarted."
return 0
fi
}
@@ -777,7 +840,14 @@ blank_line
if [[ -e "$MM_BRIDGE_BIN" ]]; then
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
else
echo_failed
@@ -797,6 +867,25 @@ else
fatal "$(cat "$log_file")"
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.
if [[ "$INSTALLATION_MODE" = "legacy-upgrade" ]]; then
LEGACY_BRIDGE_BACKUP="${MM_BRIDGE_LINK}.${backup_date}"
@@ -898,6 +987,20 @@ else
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
# ----------
@@ -991,7 +1094,7 @@ NGINX_MOBILE_EOF
fi
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 sys
from pathlib import Path
@@ -999,6 +1102,7 @@ from pathlib import Path
config = Path(sys.argv[1])
insert = Path(sys.argv[2]).read_text()
upstream = sys.argv[3].rstrip("/")
site_host = sys.argv[4].strip()
text = config.read_text()
def matching_brace(data, open_pos):
@@ -1032,25 +1136,53 @@ def matching_brace(data, open_pos):
return pos
return None
servers = []
all_servers = []
for match in re.finditer(r'(?m)^[ \t]*server[ \t]*\{', text):
open_pos = text.find("{", match.start(), match.end())
close_pos = matching_brace(text, open_pos)
if close_pos is None:
raise SystemExit("Could not parse nginx server block")
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*;')
if proxy_re.search(body):
servers.append((open_pos + 1, close_pos, body))
servers = [
server for server in all_servers
if proxy_re.search(server[2]) and len(general_locations(server[2])) == 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]
locations = list(re.finditer(r'(?m)^[ \t]*location[ \t]+/[ \t]*\{', body))
if len(locations) != 1:
raise SystemExit(f"Expected exactly one general 'location /' in Mattermost server block, found {len(locations)}")
locations = general_locations(body)
insert_pos = body_start + locations[0].start()
text = text[:insert_pos] + insert + text[insert_pos:]
config.write_text(text)
@@ -1089,17 +1221,24 @@ blank_line
echo -e "\033[37m\033[1mActivate Mattermost OIDC Mobile Bridge..\033[m"
blank_line
echononl "Switch stable bridge symlink to '${MM_BRIDGE_NAME}-${MM_NEW_VERSION}'.."
_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 \
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}'.."
_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
else
rm -f "$_tmp_link"
echo_failed
fatal "$(cat "$log_file")"
fi
fi
if [[ "$NGINX_CHANGED" = true ]]; then
@@ -1109,14 +1248,32 @@ if [[ "$NGINX_CHANGED" = true ]]; then
else
echo_failed
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
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."
NGINX_CHANGED=false
else
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
rollback_bridge
fatal "nginx reload failed after adding the Mattermost OIDC Mobile Bridge locations."
fi
fi
@@ -1125,20 +1282,29 @@ if systemctl restart "$MM_BRIDGE_SERVICE" > "$log_file" 2>&1 ; then
echo_ok
else
echo_failed
rollback_bridge
fatal "Failed to restart '${MM_BRIDGE_SERVICE}'."
if rollback_bridge ; then
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
if ! bridge_test ; then
rollback_bridge
fatal "Mattermost OIDC Mobile Bridge functional test failed."
if rollback_bridge ; then
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
if [[ -n "$NGINX_CONFIG_FILE" ]]; then
if [[ -n "$MM_SITE_URL" ]]; then
if ! nginx_e2e_test ; then
rollback_bridge
fatal "Mattermost OIDC Mobile Bridge end-to-end test through nginx failed."
if rollback_bridge ; then
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
else
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
# ----------
# 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
# ----------