#+TITLE: Mastodon Backup
#+DATE: 2023-07-26
* Prologue
People on the Fediverse like to point out how you have to backup your
data regularly in case of a catastrophic failure effecting your
instance, the host of your instance, the data centre of your instance
or maybe your admin. Heck, even a catastrophic failure effecting an
admin of /another/ instance might make you wish you had a backup of
your follows list[fn::If a remote admin decides to de-federate your
instance it will sever all your connections with people on that
instance.].

Remembering to make a backup manually is already difficult
enough[fn::My last manual backup was over a year old.], but then you
also need to remember *how* to make that backup...
* Manual Backups
Got to "App Settings". You do not know where "App Settings" are? I
know the feeling... They are behind the three cogs on the top
left. Navigate to "Import and export". Click "Data export". Click the
"CSV" link. Click the other "CSV" link. Click the other other "CSV"
link. You have now downloaded your "follows", "muted" and "blocked"
accounts lists.
* Automatic Backups
I have recently discovered [[https://toot.bezdomni.net/introduction.html][toot - Mastodon CLI client]] which can output
a list of your follows and people following you. I have [[https://github.com/ihabunek/toot/pull/390][contributed
code for "muted" and "blocked" commands]] which got accepted and
released in version 0.38.

We first need to authenticate to our instance by running ~toot
login~. This is only needed once. For more information [[https://toot.bezdomni.net/usage.html][see the
official documentation]].

I am running the following script from cron once a day:
#+begin_src shell
  #! /bin/ksh
  ACCOUNT=florian
  INSTANCE=bsd.network
  BACKUPDIR=/home/mastodonbackup/backup

  set -e
  set -o pipefail

  exit_if_nonzero_or_stderr() {
          (
                  set -o pipefail
                  { "$@" 1>&3 ;} 2>&1 | {
                          if IFS= read -r line; then
                                  printf "%s\n" "$line"
                                  cat
                                  exit 1
                          fi
                  } >&2
          ) 3>&1
  }

  exit_if_nonzero_or_stderr /usr/local/bin/toot muted | \
      awk '$2 !~ /^@.*@/ {print $2 "@'${INSTANCE}'"; next} {print $2}' \
      > ${BACKUPDIR}/muted.new

  mv ${BACKUPDIR}/muted{.new,}

  exit_if_nonzero_or_stderr /usr/local/bin/toot blocked | \
      awk '$2 !~ /^@.*@/ {print $2 "@'${INSTANCE}'"; next} {print $2}' \
      > ${BACKUPDIR}/blocked.new
  mv ${BACKUPDIR}/blocked{.new,}

  exit_if_nonzero_or_stderr /usr/local/bin/toot following ${ACCOUNT} | \
      awk '$2 !~ /^@.*@/ {print $2 "@'${INSTANCE}'"; next} {print $2}' \
      > ${BACKUPDIR}/following.new
  mv ${BACKUPDIR}/following{.new,}

  exit_if_nonzero_or_stderr /usr/local/bin/toot followers ${ACCOUNT} | \
      awk '$2 !~ /^@.*@/ {print $2 "@'${INSTANCE}'"; next} {print $2}' \
      > ${BACKUPDIR}/followers.new
  mv ${BACKUPDIR}/followers{.new,}
#+end_src

~toot~ outputs accounts from the local instance without
/@instance/. ~awk(1)~ checks for the existence of two /@/ characters
and if they are not present outputs /@instance@/ after the account
name.

Unfortunately ~toot~ always exits with 0, even if there is an error so
we need to check if there is some output on =stderr= so that we do not
replace our backup with empty files. For that I copy-pasted
=exit_if_nonzero_or_stderr= from [[https://stackoverflow.com/a/61468182][stackoverflow]] like a pro.

My crontab entry looks like this:
#+begin_src crontab
  ~ 9 * * * -sn su -s /bin/sh mastodonbackup -c /home/mastodonbackup/backup.sh
#+end_src
=-sn= ensures that the script is only run once and that no mail is
sent after a successful run. See [[https://man.openbsd.org/crontab.5][crontab(5)]] for details.

* Epilogue
I have automated backing up the most important lists from my mastodon
account. My mastodon account is running on somebody else's
computer. The backup data is written as text files on a system that I
control. That system is hooked up to my standard backup solution to
have daily, multiple-redundant backups.

I expire my toots after 90 days using [[https://ephemetoot.hugh.run/][ephemetoot]]. I have configured it
to save the toots and media before deleting them from my account.
