Archiv des Monats: April 2026

Mastodon-Backups

Vor über 10 Jahren habe ich mal darüber berichtet, wie ich diverse Web-Dienste bei mir lokal sichere. Davon ist fast nichts mehr, wie es war: Viele Dienste wurden umgebaut, manche haben sich verabschiedet, und generell läuft das alle nicht mehr auf einem Raspberry Pi, sondern als virtuelle Maschine auf meinem Proxmox-Server (das wäre mal ein Thema für sich…).

Nicht geändert hat sich mein Wunsch, Daten bei mir lokal gesichert zu haben; das gilt auch für meinen Mastodon-Account (@dasaweb). Ich habe meine Mastodon-Aktivität zum Jahresanfang wieder intensiviert, und es gefällt mir. Mein Account wird zwar auf der Instanz https://wue.social/ von Ralf gehostet, und Ralf kann man auf jeden Fall vertrauen was die Datensicherheit angeht. Aber ihr wisst ja, wie das ist mit dem Vertrauen und der Kontrolle. Außerdem braucht man ja auch was zum basteln, und wenn sich so ein Gedanke erst mal vestgesetzt hat…

Nach etwas Recherche bin ich bei Mastodon-Archive hängen geblieben und habe das erst mal per pip installiert:

sudo apt update
pip install mastodon-archive

Damit habe ich mir dann ein Backup-Script gebaut, welches Beiträge, Follow-Listen, Medien etc. zieht und die Beiträge noch in verschiedenen Formaten speichert:

#!/usr/bin/env bash
export PATH="/usr/local/bin:/usr/bin:/bin:/home/daniel/.local/bin"
set -Eeuo pipefail

# Einfaches Backup-Script basiernd auf "mastodon-archive" https://github.com/kensanata/mastodon-archive
# 
# - optional: quiet-run fuer Crontab via -q/--quiet
# - nur Fehler gehen auf STDERR raus
# - Token/Secret-Dateien bleiben im ZIP enthalten

QUIET=0

usage() {
  cat <<'EOT'
Usage: backup-mastodon.sh [OPTION]

Optionen:
  -q, --quiet     Minimaler Output (fuer Crontab)
  -v, --verbose   Voller Output (Default)
  -h, --help      Diese Hilfe anzeigen
EOT
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -q|--quiet)
      QUIET=1
      ;;
    -v|--verbose)
      QUIET=0
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "❌ Fehler: Unbekannte Option: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
  shift
done

log() {
  if (( QUIET == 0 )); then
    echo "$@"
  fi
}


ACCOUNT="dasaweb@wue.social"
BACKUP_BASE_DIR="$HOME/backups/mastodon"
TOOTS_PER_PAGE="2000"

USER_PART="${ACCOUNT%@*}"
INSTANCE_PART="${ACCOUNT#*@}"
STAMP="$(date +%Y-%m-%d_%H%M%S)"
WORKDIR="$BACKUP_BASE_DIR/workingdir"
ZIPFILE="$BACKUP_BASE_DIR/mastodon-${STAMP}.zip"

mkdir -p "$WORKDIR"
cd "$WORKDIR"

if ! command -v mastodon-archive >/dev/null 2>&1; then
  echo "❌ Fehler: mastodon-archive ist nicht im PATH." >&2
  exit 1
fi

if ! command -v zip >/dev/null 2>&1; then
  echo "❌ Fehler: zip ist nicht installiert." >&2
  exit 1
fi

ma() {
  if (( QUIET == 1 )); then
    mastodon-archive --quiet "$@"
  else
    mastodon-archive "$@"
  fi
}

log "📦 Starte Mastodon-Backup fuer $ACCOUNT"

log "🗂️ Aktualisiere Archiv"
ma archive \
  --with-mentions \
  --with-followers \
  --with-following \
  --with-mutes \
  --with-blocks \
  "$ACCOUNT"

log "🖼️ Lade Medien"
# Medien von eigenen Toots:
ma media --suppress-errors "$ACCOUNT"
# Medien von Toots, die ich gebookmarkt habe:
ma media --collection bookmarks --suppress-errors "$ACCOUNT"
# Medien von Toots, die ich geliked habe:
# ma media --collection favourites --suppress-errors "$ACCOUNT"

log "📝 Erzeuge Text-Exporte"
ma text "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.statuses.txt"
ma text --reverse "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.statuses.reverse.txt"
ma text --collection favourites "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.favourites.txt"
ma text --collection favourites --reverse "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.favourites.reverse.txt"
ma text --collection bookmarks "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.bookmarks.txt"
ma text --collection bookmarks --reverse "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.bookmarks.reverse.txt"
ma text --collection mentions "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.mentions.txt"
ma text --collection mentions --reverse "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.mentions.reverse.txt"

log "🌐 Erzeuge HTML"
ma html --toots-per-page "$TOOTS_PER_PAGE" "$ACCOUNT"
ma html --collection favourites --toots-per-page "$TOOTS_PER_PAGE" "$ACCOUNT"

log "📊 Erzeuge Report"
{
  echo "# report ($(date --iso-8601=seconds))"
  ma report --all --top -1 "$ACCOUNT"
} > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.report.txt"

log "👥 Versuche Zusatzlisten"
for cmd in allowlist followers following mutuals; do
  if ma "$cmd" "$ACCOUNT" > "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.${cmd}.txt" 2>/dev/null; then
    :
  else
    rm -f "$WORKDIR/${INSTANCE_PART}.user.${USER_PART}.${cmd}.txt"
  fi
done

cat > "$WORKDIR/README-backup.txt" <<EOT
📦 Mastodon-Backup
Account: $ACCOUNT
Erstellt: $(date --iso-8601=seconds)
Quelle: mastodon-archive

Enthalten:
- JSON-Archiv(e)
- heruntergeladene Medien für statuses/favourites/bookmarks
- Text-Exporte (normal + reverse)
- HTML-Exporte
- Report
- ggf. Zusatzlisten
- Token-/Secret-Dateien
EOT

log "🗜️ Packe ZIP: $ZIPFILE"
(
  cd "$BACKUP_BASE_DIR"
  zip -qr "$ZIPFILE" "$WORKDIR"
)

log "✅ Fertig: $ZIPFILE"

Man sieht dem Script an, dass ich mir dabei habe helfen lassen, denn so schön wäre ein selbstgebautes Script (bei mir) nicht geworden.

Beim ersten Lauf des Scripts muss man sich autorisieren. Man bekomme eine URL gezeigt, die man im Browser aufruft und damit Credentials erhält, die auch nur Leserechte haben:

Die Credentials werden dann lokal gespeichert. Ein Durchlauf des Scripts sieht in etwas so aus:

daniel@netzbackup:~/skripte$ ./backup-mastodon.sh

📦 Starte Mastodon-Backup fuer dasaweb@wue.social
🗂️ Aktualisiere Archiv
Registering app
This app needs access to your Mastodon account.
Visit the following URL and authorize the app:
https://wue.social/oauth/authorize?client_id=.............................................................................................................&response_type=code&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=read&force_login=False&state=None&lang=None
Then paste the access token here:
_0...................................ciI
Get user info
Get all statuses (this may take a while)
Get favourites (this may take a while)
Get bookmarks (this may take a while)
Get notifications and look for mentions (this may take a while)
Get followers (this may take a while)
Get following (this may take a while)
Get mutes (this may take a while)
Get blocks (this may take a while)
Skipping notes
Saving 307 statuses, 436 favourites, 1 bookmarks, 143 mentions, 88 followers, 182 following, 0 mutes, 0 blocks, 0 notes
🖼️ Lade Medien
123 urls in your backup (39 are previews)
123 to download
Downloading |################################| 123/123
3 urls in your backup (0 are previews)
0 to download

📝 Erzeuge Text-Exporte
🌐 Erzeuge HTML
Loading existing archive: wue.social.user.dasaweb.json
Writing wue.social.user.dasaweb.statuses.0.html
Loading existing archive: wue.social.user.dasaweb.json
Writing wue.social.user.dasaweb.favourites.0.html
📊 Erzeuge Report
👥 Versuche Zusatzlisten
🗜️ Packe ZIP: /home/daniel/backup/Mastodon/mastodon-2026-04-02_001551.zip
✅ Fertig: /home/daniel/backup/Mastodon/mastodon-2026-04-02_001551.zip

Jetzt kann das Script in die Crontab gepackt werden:

0 1 * * 2,6 ~/skripte/backup-mastodon.sh --quiet

Jeder Run aktualisiert inkrementell das Arbeitsverzeichnis und zippt das Ergebnis. Fertig.

Wer bin ich?

Wenn ich die Augen schließe sehe ich den schweren Raumteiler aus dunklem Holz vor mir, direkt daneben das große Ecksofa. Im Raumteiler – vom Sofa aus gut erreichbar- der Plattenspieler, darunter eine Schublade voll hochkant einsortierter Schallplatten. Bis auf die Schallplatten gibt es das alles nur noch in meiner Erinnerung. In einer dieser Schallplatten ist mir als kleiner Junge zum ersten Mal Dietrich Bonhoeffer begegnet.

Das erste Lied auf der Platte ist wohl die erfolgreichste Bonhoeffer-Vertonung: “Von guten Mächten wunderbar geborgen”. Dann als zweites eine Vertonung des Gedichts “Wer bin ich?”, geschrieben im Juni 1944 im Wehrmachtsgefängnis in Berlin Tegel.

Wer bin ich? Sie sagen mir oft,
ich träte aus meiner Zelle
gelassen und heiter und fest,
wie ein Gutsherr aus seinem Schloß.
Wer bin ich? Sie sagen mir oft,
ich spräche mit meinen Bewachern
frei und freundlich und klar,
als hätte ich zu gebieten.

Wer bin ich? Sie sagen mir auch,
ich trüge die Tage des Unglücks
gleichmütig lächelnd und stolz,
wie einer, der Siegen gewohnt ist.

Bin ich das wirklich, was andere von mir sagen?
Oder bin ich nur das, was ich selbst von mir weiß?
Unruhig, sehnsüchtig, krank, wie ein Vogel im Käfig,
ringend nach Lebensatem, als würgte mir einer die Kehle,
hungernd nach Farben, nach Blumen, nach Vogelstimmen,
dürstend nach guten Worten, nach menschlicher Nähe,
zitternd vor Zorn über Willkür und kleinlichste Kränkung,
umgetrieben vom Warten auf große Dinge,
ohnmächtig bangend um Freunde in endloser Ferne,
müde und leer zum Beten, zum Denken, zum Schaffen,
matt und bereit, von allem Abschied zu nehmen?

Wer bin ich? Der oder jener?
Bin ich denn heute dieser und morgen ein andrer?
Bin ich beides zugleich? Vor Menschen ein Heuchler
Und vor mir selbst ein verächtlich wehleidiger Schwächling?
Oder gleicht, was in mir noch ist, dem geschlagenen Heer,
das in Unordnung weicht vor schon gewonnenem Sieg?

Wer bin ich? Einsames Fragen treibt mit mir Spott.
Wer ich auch bin, Du kennst mich, Dein bin ich, o Gott!

(aus: Dietrich Bonhoeffer. Widerstand und Ergebung)

Dietrich Bonhoeffer wurde am 9. April 1945 im KZ Flossenbürg gehängt, also kein Jahr nach diesem Gedicht.


Warum schreibe ich das hier? Weil ich zusammen mit meiner Schwester über diesen Text gestolpert bin und wir beide sofort diese Vertonung von Siegfried Fietz im Ohr hatten:

Die Vertonung ist speziell, vielleicht hat sie sich auch deshalb so tief in mein Gedächtnis eingebrannt. Noch mehr liegt das aber sicherlich am Inhalt des Gedichts: An einem bildgewaltig gezeichneten, tief zerrissenen Inneren, das letztlich allein im göttlichen Gehaltensein zur Ruhe findet.