Mastodon 4.6.0 Upgrade — MajorToot¶
Readiness assessment and manual upgrade runbook for toot.majorshouse.com (Hetzner CPX31, Ubuntu 24.04). Upgrading from 4.5.11 → 4.6.0 — a clean single-step upgrade. Verified live on majortoot-hetzner 2026-06-17.
Status
This 4.5.11 → 4.6.0 upgrade was completed on 2026-06-17. The instance has since tracked the 4.6.x patch line and currently runs 4.6.2 — treat the version floors below as the baseline this runbook was written against; the pre/post-deploy migration ordering applies to any 4.5 → 4.6 major bump.
Access
Management is via the ansible user with ~/.ssh/id_ansible (ssh ansible@<tailscale-ip>, or ssh majortoot once the alias is corrected — see server doc 2026-06-17 entry). The ansible user has sudo. Run all tootctl/rails commands as the mastodon user.
Readiness — all dependency floors met (live-verified 2026-06-17)¶
| Requirement (4.6.0) | On majortoot | Status |
|---|---|---|
| Ruby ≥ 3.3 | 3.4.7 (rbenv, +PRISM) | ✅ |
| Node ≥ 22 | v24.11.0 (nvm) | ✅ |
| FFmpeg ≥ 5.1 | 6.1.1 (apt) | ✅ |
| libvips ≥ 8.13 (ImageMagick removed) | libvips42t64 8.15.1 | ✅ already in use |
| PostgreSQL ≥ 14 | 17.x (PGDG) | ✅ |
| Redis ≥ 7.0 | 7.0.15 | ✅ |
| Disk free for pg_dump | 124 GB free (15% used) | ✅ |
Key point on libvips: 4.6.0 removes ImageMagick entirely and ignores MASTODON_USE_LIBVIPS=false. MajorToot already runs on libvips 8.15.1 (MASTODON_USE_LIBVIPS unset), so this is a non-event. The original Hetzner provisioning dep list (cutover plan / migration brief) named imagemagick and omitted libvips, but libvips came in transitively and is what Mastodon actually uses.
What does NOT affect MajorToot¶
- Custom mute system (
mastodon_mute.py/claude_mute_token) — uses the account-mute REST endpoints, unchanged in 4.6.0. The only mute-adjacent change is anexpire_at→expires_atrename in muted-words/filter CSV exports, which this tooling doesn't parse. - S3 fix from 2026-06-01 —
S3_PERMISSION=empty andS3_ACLcommented (BucketOwnerEnforced). 4.6.0 doesn't change S3 behavior; the fix holds. Confirmed still set. - Invite permission split (
invite_bypass_approval) — solo instance, irrelevant. - Email subscriptions — admin-gated + per-user opt-in; can hard-disable with
DISABLE_EMAIL_SUBSCRIPTIONS=trueif outbound mail volume ever matters. ENABLE_SIDEKIQ_UNIQUE_JOBS_UI(removed in 4.6.0) — not set. ✅
Watch items (non-blocking)¶
- nginx vhost: MajorToot hand-maintains
/etc/nginx/sites-available/mastodon(bad-bot UA stanza, fail2ban). 4.6.0 changes the default sample (location blocks #19644/#37866,proxy_read_timeout→ 120s). An upgrade won't touch the custom vhost — but diff it against the newdist/nginx.conffor the changed location blocks. The UA block stays. - Custom themes would break (theming rebuilt) — MajorToot runs no custom theme.
Manual upgrade runbook¶
Run as the mastodon user unless noted. Solo instance, so no maintenance window needed; old DO droplet remains as a fallback.
# 1. Back up the DB FIRST (as postgres or via sudo)
pg_dump -Fc mastodon_production > ~/mastodon-pre-4.6.0.dump
# 2. Get the code (as mastodon user)
cd /home/mastodon/live
git fetch && git checkout v4.6.0
# 3. Install deps
bundle install -j$(getconf _NPROCESSORS_ONLN)
yarn install --immutable
# If charlock_holmes fails to build on recent gcc:
# BUNDLE_BUILD__CHARLOCK_HOLMES="--with-cxxflags=-std=c++17" bundle install
# 4. Precompile assets
RAILS_ENV=production bundle exec rails assets:precompile
# 5. PRE-deployment migrations
SKIP_POST_DEPLOYMENT_MIGRATIONS=true RAILS_ENV=production bundle exec rails db:migrate
# 6. Restart services (confirm Sidekiq drained first)
sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming
# 7. POST-deployment migrations
RAILS_ENV=production bundle exec rails db:migrate
Migration ordering matters
Pre-deploy migrations (step 5) → restart (step 6) → post-deploy migrations (step 7). Do not collapse them.
Post-upgrade verification¶
# Site loads
curl -I https://toot.majorshouse.com
# Version reports 4.6.0
curl -s https://toot.majorshouse.com/api/v2/instance | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])"
# Sidekiq clean
journalctl -u mastodon-sidekiq -f | grep -i "error\|fail\|retry"
Rollback¶
Restore the pre-upgrade dump and git checkout v4.5.11, or fall back to the still-running old DO droplet by reverting DNS if it hasn't been destroyed.