macOS, really?

Disclaimer: turns out quite a few people across creative communities, from artists to open source developers to die-hard legacy UNIX enthusiasts use macOS as a daily driver. Yes, I know. Not fully open source (though Darwin is and some of the userspace is), but a weird mix of NeXTSTEP, FreeBSD & even some NetBSD code intertwined with Apple’s own free & non-free components. And yet, there are solid uses for it, so without further ado, let’s rock!

Whenever we upgrade Mac hardware, we face the same daunting choice of either:

  1. use Migration Assistant to get things up & running the quick & dirty way;
  2. use the new hardware as an opportunity to run a thorough cleaning operation, only transferring what is truly special, leaving years of clutter behind;

While a bunch of stuff are synced through iCloud or syncing $HOME (excluding ~/Library), this is definitely not the case for Signal. Which is problematic to say the least, since most if not all of the privacy-conscious and/or technically inclined crowd tend to favor this open source messaging system over proprietary alternatives.

Why this?

Signal Desktop is a linked device, not a standalone client — it only knows what happened while it was linked to your phone. If you unlink and re-link on a new computer, Signal will sync message history from your phone, but media older than 45 days is permanently gone from Signal’s servers. Text messages transfer fine, but photos, voice notes, and files do not survive beyond that window.

Signal doesn’t officially support desktop-to-desktop transfers — their own backup & restore docs say so. Signal Secure Backups are phone-only and don’t cover desktop data either. But unofficially? That’s what this guide is for.

This means the desktop’s local SQLite database — sitting in ~/Library/Application Support/Signal/sql/db.sqlite — is the only copy of your full desktop-side history including all media. If you lose it, it’s gone.

Gone: the love letters, the audio poetry, the enthusiastic screenshots. The photo club conversations, the impromptu video declarations. Everything visual, vibrant, precious — everything worth revisiting. These don’t just convey information: they cement relationships, actualize friendships, fuel movements.

So let’s move that database (and its encryption key) to a new Mac, and make Signal pick it up as if nothing changed. Ready?

Prerequisites

  • Old Mac reachable via ssh (set $OLDMAC to its hostname, e.g. export OLDMAC=oldmac.local);
  • Signal Safe Storage key extracted from old Mac (through security, see below);
  • Homebrew rsync installed on the new Mac (macOS ships rsync 2.6.9 from 2006, which lacks flags we need);
  • Signal is quit on the old Mac (the database is SQLite — copying while Signal writes to it can produce a corrupt copy)
  • Signal has never been opened on the new Mac — opening it before migrating will create a fresh identity and registration, making it much harder to restore the old database

Install Signal on the new Mac but do not launch it:

brew install --cask signal

Go!

0. Extract Signal key

On the old Mac:

security find-generic-password \
    -s "Signal Safe Storage" \
    -w ~/Library/Keychains/login.keychain-db

Save the output (a ~24 character string) to a file:

printf 'Signal Safe Storage\tSignal\t%s\n' \
    "$(security find-generic-password \
    -s "Signal Safe Storage" \
    -w ~/Library/Keychains/login.keychain-db)" \
    > ~/migration-secrets.txt
chmod 600 ~/migration-secrets.txt

Transfer to the new Mac:

scp ~/migration-secrets.txt $USER@$NEWMAC:~/migration-secrets.txt

1. Verify

We need to make sure source data exists:

ssh $OLDMAC 'ls -lh ~/Library/Application\ Support/Signal/sql/db.sqlite ~/Library/Application\ Support/Signal/sql/db.sqlite-wal' 2>&1

STOP if db.sqlite is missing. The -wal file may or may not exist (it’s a SQLite write-ahead log); its absence is fine.

2. Sync

Any network path works here — Wi-Fi, Ethernet, whatever you’ve got. But if you have an Apple Thunderbolt 4 cable, plug it in: macOS automatically creates a Thunderbolt Bridge — a point-to-point network link between the two Macs, no router or switch needed. The cable supports up to 40 Gbps; real-world throughput over rsync/SSH will be lower (expect several Gbps — still dramatically faster than Wi-Fi or gigabit Ethernet, and the fastest Mac-to-Mac direct transfer you can get without Target Disk Mode).

The rsync and ssh flags below are tuned for that kind of bandwidth — on a slower link they won’t hurt, they’ll just matter less:

$(brew --prefix)/bin/rsync -avPHW \
  -e "ssh -c aes128-gcm@openssh.com -o Compression=no" \
  --exclude='*Cache*' --exclude='Logs' --exclude='*.log' --exclude='GPUCache' \
  $USER@$OLDMAC:Library/Application\ Support/Signal/ \
  ~/Library/Application\ Support/Signal/

rsync flags explained:

  • -a archive mode — preserves permissions, symlinks, timestamps, recurses into directories
  • -v verbose output
  • -P shows progress per file + enables resuming partial transfers if interrupted
  • -H preserves hard links
  • -W transfers whole files, skipping the delta/checksum algorithm — faster on high-bandwidth local links where computing diffs costs more than just sending the raw bytes

ssh tuning:

  • -c aes128-gcm@openssh.com lightweight cipher, reduces CPU overhead on a fast link
  • -o Compression=no skips SSH-level compression — pointless at Thunderbolt speeds and wastes CPU

Verify: ls -lh ~/Library/Application\ Support/Signal/sql/db.sqlite

3. Restore

The keychain key really is where it’s at:

security delete-generic-password \
    -s "Signal Safe Storage" \
    ~/Library/Keychains/login.keychain-db 2>/dev/null
SIGNAL_KEY=$(grep "Signal Safe Storage" ~/migration-secrets.txt | cut -f3)
security add-generic-password \
    -a "Signal" -s "Signal Safe Storage" \
    -w "$SIGNAL_KEY" ~/Library/Keychains/login.keychain-db

4. Launch

Open Signal. Close your eyes. Drumroll. Open your eyes: you should see your full conversation history with all media. Browse away and go get those precious memories!

If it asks to link/register: QUIT IMMEDIATELY and rollback:

rm -rf ~/Library/Application\ Support/Signal
security delete-generic-password \
    -s "Signal Safe Storage" \
    ~/Library/Keychains/login.keychain-db 2>/dev/null

Then diagnose before retrying — the database or key may be mismatched.

5. Cleanup

If and only if Signal works, as expected:

rm ~/migration-secrets.txt