Mohamed Elashri

Breaking Thunderbird Free from Linux Package Lag

Thunderbird finally added native Exchange Web Services support, no more paying for third-party add-ons. But there is a catch: Linux distributions lag behind by months, and even Mozilla's official Flatpak builds can freeze at older versions indefinitely. I don't want to manually check for updates, so I automated the entire process to pull directly from Mozilla's release archive using a script that also protects against downgrades and profile breakage.

The strategy: query Mozilla's product-details API for the latest Thunderbird release, validate that the .deb (or .rpm, with minor changes) actually exists (because the API sometimes advertises a version before binaries are published), and handle installation automatically. The script includes an intelligent fallback if the API version is not yet available, scraping the release archive for the most recent published non-ESR version, plus extra safeguards to refuse downgrades and create backups of your profile on major upgrades.

Auto-update script with downgrade protection

Create ~/.local/bin/update-thunderbird:

[!COLLAPSE] Show full update script | Hide script
#!/bin/bash
set -euo pipefail

LOG="${HOME}/.local/log/thunderbird-update.log"
mkdir -p "$(dirname "$LOG")"

log()   { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"        | tee -a "$LOG"; }
error() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG" >&2; exit 1; }

TB_PROFILE_ROOT="${HOME}/.thunderbird"
BACKUP_DIR="${HOME}/.thunderbird-backups"

log "Starting Thunderbird update check..."

# --- 0. Sanity: ensure necessary tools ---
for cmd in curl jq dpkg-query dpkg sort grep sed; do
  command -v "$cmd" >/dev/null 2>&1 || error "Required command '$cmd' not found in PATH"
done

# --- 1. Get candidate version from API ---
CANDIDATE_VERSION=$(
  curl -fsS "https://product-details.mozilla.org/1.0/thunderbird_versions.json" \
    | jq -r '.LATEST_THUNDERBIRD_VERSION // empty'
)

if [[ -z "$CANDIDATE_VERSION" ]]; then
  error "Failed to fetch LATEST_THUNDERBIRD_VERSION from Mozilla API"
fi
log "Candidate version (API): $CANDIDATE_VERSION"

# --- 2. Validate .deb exists for candidate; if not, fall back to latest published ---
validate_version() {
  local v="$1"
  local url="https://archive.mozilla.org/pub/thunderbird/releases/${v}/linux-x86_64/en-US/thunderbird-${v}.deb"
  local code
  code=$(curl -fsSI "$url" | awk 'NR==1 {print $2}')
  if [[ "$code" == "200" ]]; then
    echo "$v"
    return 0
  fi
  return 1
}

VERSION=""
if validate_version "$CANDIDATE_VERSION" >/dev/null; then
  VERSION="$CANDIDATE_VERSION"
  log "Using candidate version: $VERSION"
else
  log "Binary for $CANDIDATE_VERSION not yet available. Falling back to latest published..."
  VERSION=$(
    curl -fsS "https://archive.mozilla.org/pub/thunderbird/releases/" \
      | grep -oE 'href="[0-9]+\.[0-9]+(\.[0-9]+)?/"' \
      | sed 's|href="||; s|/"||' \
      | grep -vi 'esr' \
      | sort -V \
      | tail -n1
  )
  [[ -z "$VERSION" ]] && error "No non-ESR versions found in releases index"
  log "Fallback version: $VERSION"
  validate_version "$VERSION" >/dev/null || error "Even fallback version $VERSION has no .deb"
fi

# --- 3. Get installed version (if any) ---
INSTALLED_VERSION=""
if dpkg -l thunderbird &>/dev/null; then
  INSTALLED_VERSION=$(
    dpkg-query -f '${Version}' -W thunderbird 2>/dev/null \
      | sed 's/.*://' \
      | cut -d'-' -f1 \
      | sed 's/esr.*//'
  )
  log "Installed version: $INSTALLED_VERSION"
else
  log "Thunderbird not installed (fresh install)"
fi

# --- 4. Enforce monotonic version (no downgrade) ---
if [[ -n "$INSTALLED_VERSION" ]]; then
  if dpkg --compare-versions "$VERSION" eq "$INSTALLED_VERSION"; then
    log "Already up-to-date (installed: $INSTALLED_VERSION, latest: $VERSION)."
    exit 0
  fi

  if dpkg --compare-versions "$VERSION" lt "$INSTALLED_VERSION"; then
    log "Refusing to downgrade Thunderbird (installed: $INSTALLED_VERSION, candidate: $VERSION). Aborting."
    exit 0
  fi
fi

# --- 5. Optional: backup profile on major-version bump ---
if [[ -n "${INSTALLED_VERSION:-}" && -d "$TB_PROFILE_ROOT" ]]; then
  MAJOR_INSTALLED="${INSTALLED_VERSION%%.*}"
  MAJOR_NEW="${VERSION%%.*}"

  if [[ "$MAJOR_NEW" -gt "$MAJOR_INSTALLED" ]]; then
    mkdir -p "$BACKUP_DIR"
    BACKUP_FILE="${BACKUP_DIR}/profile-$(date +%Y%m%d-%H%M%S)-${INSTALLED_VERSION}.tar.zst"
    log "Major upgrade detected (${INSTALLED_VERSION} -> ${VERSION}). Backing up profile to: $BACKUP_FILE"
    tar -C "$HOME" -I 'zstd -19' -cf "$BACKUP_FILE" ".thunderbird" || \
      log "WARNING: Profile backup failed; proceeding with update anyway."
  fi
fi

# --- 6. Download & install ---
DEB_FILE="/tmp/thunderbird-${VERSION}.deb"
DEB_URL="https://archive.mozilla.org/pub/thunderbird/releases/${VERSION}/linux-x86_64/en-US/thunderbird-${VERSION}.deb"

log "Downloading $DEB_URL"
curl -fsSL "$DEB_URL" -o "$DEB_FILE"

if [[ ! -s "$DEB_FILE" ]]; then
  error "Downloaded file $DEB_FILE is empty or missing"
fi

log "Installing Thunderbird $VERSION"
if ! sudo dpkg -i "$DEB_FILE"; then
  log "dpkg reported issues, attempting to fix dependencies..."
  sudo apt-get -f install -y
  log "Re-running dpkg -i after fixing dependencies..."
  sudo dpkg -i "$DEB_FILE"
fi

log "Thunderbird updated to $VERSION"
rm -f "$DEB_FILE"

# --- 7. Optional: log current profile setup for traceability ---
if [[ -d "$TB_PROFILE_ROOT" && -f "${TB_PROFILE_ROOT}/profiles.ini" ]]; then
  log "Profile root: $TB_PROFILE_ROOT"
  DEFAULT_PROFILE_PATH=$(
    awk -F= '
      /^\[Profile[0-9]+\]$/ { sec=1; isdef=0; path="" }
      sec && /^Default=1$/ { isdef=1 }
      sec && /^Path=/ { path=$2 }
      sec && isdef && path { print path; exit }
    ' "${TB_PROFILE_ROOT}/profiles.ini" || true
  )

  if [[ -n "$DEFAULT_PROFILE_PATH" ]]; then
    log "Default profile path (relative): $DEFAULT_PROFILE_PATH"
    COMPAT_FILE="${TB_PROFILE_ROOT}/${DEFAULT_PROFILE_PATH}/compatibility.ini"
    if [[ -f "$COMPAT_FILE" ]]; then
      LAST_BUILD_ID=$(grep '^LastAppBuildID=' "$COMPAT_FILE" | cut -d= -f2 || true)
      [[ -n "$LAST_BUILD_ID" ]] && log "Profile LastAppBuildID: $LAST_BUILD_ID"
    fi
  else
    log "Could not determine default profile from profiles.ini"
  fi
else
  log "No Thunderbird profile directory found at $TB_PROFILE_ROOT (might be first install or using snap/other layout)"
fi

# --- 8. Desktop notification (optional) ---
if command -v notify-send >/dev/null; then
  notify-send "Thunderbird Updated" "v$VERSION installed" --icon=mail-unread || true
fi

log "Done."

We have some built-in safety that can save your profile from breakage:

Set permissions and test:

chmod +x ~/.local/bin/update-thunderbird
mkdir -p ~/.local/log
~/.local/bin/update-thunderbird

Automating with systemd user timers

systemd user timers handle scheduling more reliably than cron and survive reboots. Create ~/.config/systemd/user/thunderbird-update.timer:

[Unit]
Description=Check for Thunderbird updates weekly

[Timer]
OnCalendar=weekly
Persistent=true
RandomizedDelaySec=3600

[Install]
WantedBy=timers.target

And ~/.config/systemd/user/thunderbird-update.service:

[Unit]
Description=Update Thunderbird to latest release
After=network-online.target

[Service]
Type=oneshot
ExecStart=%h/.local/bin/update-thunderbird
Environment=HOME=%h

Enable and start the timer:

systemctl --user daemon-reload
systemctl --user enable --now thunderbird-update.timer

The timer runs weekly, persists across reboots, and logs to ~/.local/log/thunderbird-update.log. You can check status with:

systemctl --user status thunderbird-update.timer

or view logs with:

journalctl --user -u thunderbird-update.service

Optional one-liner for manual updates

For quick manual updates, you can still keep a simple alias in ~/.bashrc:

alias tb-update='VERSION=$(curl -s https://product-details.mozilla.org/1.0/thunderbird_versions.json | jq -r .LATEST_THUNDERBIRD_VERSION) && echo "Latest: $VERSION" && wget -qO /tmp/tb.deb "https://download.mozilla.org/?product=thunderbird-$VERSION&os=linux64&lang=en-US" && sudo dpkg -i /tmp/tb.deb || { sudo apt --fix-broken install -y && sudo dpkg -i /tmp/tb.deb; } && rm -f /tmp/tb.deb'

Run tb-update anytime you want an immediate bump outside the weekly schedule. With this setup, Thunderbird stays current across all your machines, you get native EWS support as soon as Mozilla ships it, and distribution release cycles, or accidental downgrades stop being a problem.