Skip to content

Instantly share code, notes, and snippets.

@ROCKTAKEY
Last active March 1, 2026 17:08
Show Gist options
  • Select an option

  • Save ROCKTAKEY/5cd247f981202b9e31699b309044e61b to your computer and use it in GitHub Desktop.

Select an option

Save ROCKTAKEY/5cd247f981202b9e31699b309044e61b to your computer and use it in GitHub Desktop.
Mattermost upgrade batch
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
VERSION=""
EDITION="enterprise"
ARCH="amd64"
INSTALL_PATH="/opt"
DOWNLOAD_DIR="/tmp"
SERVICE_NAME="mattermost"
OWNER=""
GROUP=""
SETCAP_ENABLED=0
DRY_RUN=0
KEEP_TMP=0
ARCHIVE_PATH=""
ARCHIVE_WAS_DOWNLOADED=0
SERVICE_STOPPED=0
declare -a EXTRA_PRESERVE_PATHS=()
usage() {
cat <<'EOF'
Usage:
mattermost-upgrade.sh --version X.Y.Z [options]
Required:
--version X.Y.Z Mattermost target version (example: 10.11.2)
Options:
--edition enterprise|team Edition to download (default: enterprise)
--arch amd64|arm64 Archive architecture (default: amd64)
--install-path PATH Base install path that contains ./mattermost (default: /opt)
--download-dir PATH Temporary working directory (default: /tmp)
--service NAME systemd service name (default: mattermost)
--owner USER Owner for chown (default: detected from existing binary, fallback: mattermost)
--group GROUP Group for chown (default: detected from existing binary, fallback: mattermost)
--preserve-path REL_PATH Extra relative path under mattermost/ to preserve (repeatable)
--archive PATH Use local archive instead of downloading
--setcap Run setcap on ./mattermost/bin/mattermost after copy
--keep-tmp Keep extracted files and archive in download-dir
--dry-run Print commands without executing
-h, --help Show this help
EOF
}
log() {
printf '[%s] %s\n' "$SCRIPT_NAME" "$*"
}
fail() {
printf '[%s] ERROR: %s\n' "$SCRIPT_NAME" "$*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "command not found: $1"
}
run_cmd() {
if [[ "$DRY_RUN" -eq 1 ]]; then
printf '+'
for arg in "$@"; do
printf ' %q' "$arg"
done
printf '\n'
return 0
fi
"$@"
}
declare -a ROOT_PREFIX=()
run_root_cmd() {
run_cmd "${ROOT_PREFIX[@]}" "$@"
}
recover_service_on_error() {
local status=$?
if [[ "$status" -ne 0 && "$SERVICE_STOPPED" -eq 1 ]]; then
log "Upgrade failed. Attempting service recovery: systemctl start $SERVICE_NAME"
if [[ "$DRY_RUN" -eq 1 ]]; then
run_root_cmd systemctl start "$SERVICE_NAME" || true
else
run_root_cmd systemctl start "$SERVICE_NAME" >/dev/null 2>&1 || true
fi
fi
exit "$status"
}
trim_trailing_slash() {
local path="$1"
if [[ "$path" == "/" ]]; then
printf '/\n'
else
printf '%s\n' "${path%/}"
fi
}
normalize_rel_path() {
local rel="$1"
rel="${rel#./}"
rel="${rel%/}"
[[ -n "$rel" ]] || fail "empty preserve path is not allowed"
[[ "$rel" != /* ]] || fail "preserve path must be relative to mattermost/: $1"
[[ "$rel" != *".."* ]] || fail "preserve path must not include '..': $1"
printf '%s\n' "$rel"
}
path_is_preserved() {
local rel="$1"
local keep
for keep in "${PRESERVE_PATHS[@]}"; do
if [[ "$rel" == "$keep" || "$rel" == "$keep/"* || "$keep" == "$rel/"* ]]; then
return 0
fi
done
return 1
}
cleanup_current_install() {
local mm_dir="$1"
local entry rel
local -a top_entries=()
local -a client_entries=()
shopt -s nullglob dotglob
for entry in "$mm_dir"/*; do
rel="${entry#"$mm_dir"/}"
if path_is_preserved "$rel"; then
continue
fi
top_entries+=("$entry")
done
if [[ -d "$mm_dir/client" ]]; then
for entry in "$mm_dir/client"/*; do
rel="client/${entry#"$mm_dir/client/"}"
if path_is_preserved "$rel"; then
continue
fi
client_entries+=("$entry")
done
fi
shopt -u nullglob dotglob
if [[ "${#top_entries[@]}" -gt 0 ]]; then
log "Removing stale top-level files/directories from $mm_dir"
for entry in "${top_entries[@]}"; do
run_root_cmd rm -rf -- "$entry"
done
fi
if [[ "${#client_entries[@]}" -gt 0 ]]; then
log "Removing stale client files/directories from $mm_dir/client"
for entry in "${client_entries[@]}"; do
run_root_cmd rm -rf -- "$entry"
done
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
VERSION="${2:-}"
shift 2
;;
--edition)
EDITION="${2:-}"
shift 2
;;
--arch)
ARCH="${2:-}"
shift 2
;;
--install-path)
INSTALL_PATH="${2:-}"
shift 2
;;
--download-dir)
DOWNLOAD_DIR="${2:-}"
shift 2
;;
--service)
SERVICE_NAME="${2:-}"
shift 2
;;
--owner)
OWNER="${2:-}"
shift 2
;;
--group)
GROUP="${2:-}"
shift 2
;;
--preserve-path)
EXTRA_PRESERVE_PATHS+=("$(normalize_rel_path "${2:-}")")
shift 2
;;
--archive)
ARCHIVE_PATH="${2:-}"
shift 2
;;
--setcap)
SETCAP_ENABLED=1
shift
;;
--keep-tmp)
KEEP_TMP=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
fail "unknown option: $1"
;;
esac
done
[[ -n "$VERSION" ]] || {
usage
fail "--version is required"
}
case "$EDITION" in
enterprise | team) ;;
*) fail "--edition must be either 'enterprise' or 'team'" ;;
esac
case "$ARCH" in
amd64 | arm64) ;;
*) fail "--arch must be either 'amd64' or 'arm64'" ;;
esac
need_cmd tar
need_cmd cp
need_cmd rm
need_cmd chown
need_cmd systemctl
need_cmd stat
if [[ "$SETCAP_ENABLED" -eq 1 ]]; then
need_cmd setcap
fi
if [[ "$(id -u)" -ne 0 ]]; then
need_cmd sudo
ROOT_PREFIX=(sudo)
fi
INSTALL_PATH="$(trim_trailing_slash "$INSTALL_PATH")"
DOWNLOAD_DIR="$(trim_trailing_slash "$DOWNLOAD_DIR")"
readonly MATTERMOST_DIR="$INSTALL_PATH/mattermost"
readonly UPGRADE_DIR="$DOWNLOAD_DIR/mattermost-upgrade"
[[ -d "$MATTERMOST_DIR" ]] || fail "install directory not found: $MATTERMOST_DIR"
[[ "$MATTERMOST_DIR" == */mattermost ]] || fail "invalid install directory: $MATTERMOST_DIR"
if [[ "$EDITION" == "enterprise" ]]; then
ARCHIVE_NAME="mattermost-${VERSION}-linux-${ARCH}.tar.gz"
else
ARCHIVE_NAME="mattermost-team-${VERSION}-linux-${ARCH}.tar.gz"
fi
if [[ -z "$ARCHIVE_PATH" ]]; then
ARCHIVE_PATH="$DOWNLOAD_DIR/$ARCHIVE_NAME"
DOWNLOAD_URL="https://releases.mattermost.com/${VERSION}/${ARCHIVE_NAME}"
ARCHIVE_WAS_DOWNLOADED=1
fi
if [[ ! -f "$MATTERMOST_DIR/bin/mattermost" ]]; then
fail "existing binary not found: $MATTERMOST_DIR/bin/mattermost"
fi
CURRENT_OWNER_GROUP="$(stat -c '%U:%G' "$MATTERMOST_DIR/bin/mattermost")"
if [[ -z "$OWNER" ]]; then
OWNER="${CURRENT_OWNER_GROUP%%:*}"
[[ -n "$OWNER" ]] || OWNER="mattermost"
fi
if [[ -z "$GROUP" ]]; then
GROUP="${CURRENT_OWNER_GROUP##*:}"
[[ -n "$GROUP" ]] || GROUP="mattermost"
fi
declare -a PRESERVE_PATHS=(
"client"
"client/plugins"
"config"
"logs"
"plugins"
"data"
)
for p in "${EXTRA_PRESERVE_PATHS[@]}"; do
PRESERVE_PATHS+=("$p")
done
readonly BACKUP_DIR="$INSTALL_PATH/mattermost-back-$(date +'%F-%H-%M')"
log "Upgrade target version: $VERSION"
log "Edition: $EDITION"
log "Architecture: $ARCH"
log "Install path: $INSTALL_PATH"
log "Mattermost directory: $MATTERMOST_DIR"
log "Backup directory: $BACKUP_DIR"
log "Temporary directory: $UPGRADE_DIR"
log "Service: $SERVICE_NAME"
log "Owner/group: $OWNER:$GROUP"
log "Preserve paths: ${PRESERVE_PATHS[*]}"
if [[ "$DRY_RUN" -eq 1 ]]; then
log "DRY RUN mode is enabled"
fi
trap recover_service_on_error EXIT
if [[ -z "${DOWNLOAD_URL:-}" ]]; then
[[ -f "$ARCHIVE_PATH" ]] || fail "archive not found: $ARCHIVE_PATH"
log "Using local archive: $ARCHIVE_PATH"
else
need_cmd mkdir
run_cmd mkdir -p -- "$DOWNLOAD_DIR"
run_cmd rm -f -- "$ARCHIVE_PATH"
log "Downloading: $DOWNLOAD_URL"
if command -v curl >/dev/null 2>&1; then
run_cmd curl -fL "$DOWNLOAD_URL" -o "$ARCHIVE_PATH"
elif command -v wget >/dev/null 2>&1; then
run_cmd wget -O "$ARCHIVE_PATH" "$DOWNLOAD_URL"
else
fail "either curl or wget is required to download the archive"
fi
fi
run_cmd rm -rf -- "$UPGRADE_DIR"
run_cmd mkdir -p -- "$UPGRADE_DIR"
log "Extracting archive to $UPGRADE_DIR"
run_cmd tar -xf "$ARCHIVE_PATH" -C "$DOWNLOAD_DIR" --transform='s,^[^/]\+,mattermost-upgrade,'
log "Stopping service: $SERVICE_NAME"
run_root_cmd systemctl stop "$SERVICE_NAME"
SERVICE_STOPPED=1
log "Creating application backup: $BACKUP_DIR"
run_root_cmd mkdir -p -- "$BACKUP_DIR"
run_root_cmd cp -ra "$MATTERMOST_DIR/." "$BACKUP_DIR/"
cleanup_current_install "$MATTERMOST_DIR"
log "Copying upgraded files to $MATTERMOST_DIR"
run_root_cmd cp -an "$UPGRADE_DIR/." "$MATTERMOST_DIR/"
log "Applying ownership: $OWNER:$GROUP"
run_root_cmd chown -R "$OWNER:$GROUP" "$MATTERMOST_DIR"
if [[ "$SETCAP_ENABLED" -eq 1 ]]; then
log "Applying setcap for low port binding"
run_root_cmd setcap cap_net_bind_service=+ep "$MATTERMOST_DIR/bin/mattermost"
fi
log "Starting service: $SERVICE_NAME"
run_root_cmd systemctl start "$SERVICE_NAME"
SERVICE_STOPPED=0
run_root_cmd systemctl is-active --quiet "$SERVICE_NAME"
if [[ "$KEEP_TMP" -eq 0 ]]; then
log "Cleaning temporary files"
run_root_cmd rm -rf -- "$UPGRADE_DIR"
if [[ "$ARCHIVE_WAS_DOWNLOADED" -eq 1 ]]; then
run_root_cmd rm -f -- "$ARCHIVE_PATH"
fi
fi
log "Upgrade completed successfully."
trap - EXIT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment