Skip to content

Instantly share code, notes, and snippets.

@elfurbe
Created December 24, 2024 02:22
Show Gist options
  • Save elfurbe/8a538d5710b959923731d3ff4e220cd4 to your computer and use it in GitHub Desktop.
Save elfurbe/8a538d5710b959923731d3ff4e220cd4 to your computer and use it in GitHub Desktop.
A shell script that backs up your atproto data, including your bsky prefs, to a tarball. Requires goat, jq and tar.
#!/usr/bin/env bash
usage() {
cat - <<EOF
usage: ${0##*/} -u <handle or did>
-u|--user: pds login handle
-p|--password: pds login password (default: prompt for password, app password recommended))
-s|--pds: pds server (default: autodetect)
-o|--output-dir: output directory (default: ./<handle>_<did>_backup)
-c|--compress: compresses backup (default: tar with gzip, options: gzip, bzip2, xz, lzma, zstd)
-d|--debug: show debug messages
-h|--help: show this help
EOF
}
if [ $# -eq 0 ]; then
usage
exit
fi
COMPRESSOR="gzip"
while [ $# -gt 0 ]; do
case "${1}" in
-u|--user)
shift
HANDLE="${1}"
shift;;
-p|--password|--pass)
shift
PASSWORD="${1}"
shift;;
-s|--server|--pds)
shift
PDS_URL="${1}"
shift;;
-o|--output|--dir)
shift
BACKUPDIR="${1}"
shift;;
-c|--compressor)
shift
COMPRESSOR="${1}"
shift;;
-d|--debug)
DEBUG=1
shift;;
-h|--help)
usage
exit;;
*)
echo "$0: invalid option ${1}"
usage
exit 1;;
esac
done
which ${COMPRESSOR} &>/dev/null
if [ $? -eq 1 ]; then
echo ">> ERROR: could not find '${COMPRESSOR}'"
exit
fi
case "${COMPRESSOR}" in
gzip) EXT="gz";;
bzip2) EXT="bz2";;
xz) EXT="xz";;
lzma) EXT="lz";;
zstd) EXT="zst";;
*) EXT="${COMPRESSOR}";;
esac
if [ -z "${HANDLE}" ]; then
echo ">> ERROR: No handle supplied"
echo
usage
exit
fi
# we need goat
GOAT=$(which goat 2>/dev/null)
if [ -z "${GOAT}" ]; then
echo ">> ERROR: goat not installed"
echo ">> See https://github.com/bluesky-social/indigo/tree/main/cmd/goat"
echo
exit
fi
# resolve DID document which has _things_ in it
DID_DOC="$(${GOAT} resolve ${HANDLE})"
# we also need jq
JQ=$(which jq 2>/dev/null)
if [ -z "${JQ}" ]; then
echo ">> ERROR: jq not installed"
echo ">> See https://jqlang.github.io/jq/"
echo
exit
fi
PDS_URL=$(echo "${DID_DOC}" | ${JQ} -r '.service[0].serviceEndpoint')
if [ -z "${PDS_URL}" ]; then
echo ">> ERROR: pds could not be resolved"
[ $DEBUG ] && ${GOAT} resolve ${HANDLE}
echo
exit
fi
if [ -z "${PASSWORD}" ]; then
echo -n "Enter password: "
read -s PASSWORD
echo
if [ -z "${PASSWORD}" ]; then
echo ">> ERROR: password not entered"
echo
exit
fi
fi
# resolve DID
DID=$(echo "${DID_DOC}" | ${JQ} -r '.id')
# datestamp to date all the things
#DATESTAMP="$(date +%Y%m%d%H%m%S)"
DATESTAMP="$(date -Iseconds)"
# capture the current working directory in case someone sources this script like a doofus
CWD="${PWD}"
# the backup dir must be named
[ -z "${BACKUPDIR}" ] && BACKUPDIR="bskybackup-${HANDLE}_${DATESTAMP}"
# the tarball must be named
TARBALL="${BACKUPDIR}.tar.${EXT}"
if [ $DEBUG ]; then
echo "HANDLE: ${HANDLE}"
echo "PASSWORD: ********** (hunter2, ofc)"
echo "PDS_URL: ${PDS_URL}"
echo "DID: ${DID}"
echo "DATESTAMP: ${DATESTAMP}"
echo "BACKUPDIR: ${BACKUPDIR}"
echo "COMPRESSOR: ${COMPRESSOR}"
echo "TARBALL: ${TARBALL}"
echo "PWD: ${PWD}"
read -n 1 -s -r -p ">> Press any key to continue..."
echo
fi
## we're done preparing the way
## IT'S BUSINESS TIME
# check for the backup directory and make it if it doesn't exist
[ -d ${BACKUPDIR} ] || mkdir ${BACKUPDIR}
cd ${BACKUPDIR}
# login to pds
${GOAT} account login -u ${HANDLE} -p "${PASSWORD}"
# export repo CAR file
CARFILE="${DID}.car"
${GOAT} repo export --output ${CARFILE} ${HANDLE}
# export blobs (manually iterate, the export subcommand shits the bed on missing blobs)
BLOBDIR=${DID}_blobs
[ ! -d ${BLOBDIR} ] && mkdir ${BLOBDIR}
for BLOB in `${GOAT} blob ls ${HANDLE}`; do
${GOAT} blob download --output ${BLOBDIR}/${BLOB} ${HANDLE} ${BLOB} || echo "could not download ${BLOB}"
done
# export bsky app prefs
${GOAT} bsky prefs export > ${DID}_prefs.json
# logout
${GOAT} account logout
cd ..
# create tarball
if [ -f "${TARBALL}" ]; then
echo ">> ERROR: backup archive already exists ${TARBALL}"
echo
exit
fi
tar --use-compress-program=${COMPRESSOR} -cf ${TARBALL} ${BACKUPDIR}
# clean up the directory if the tar worked
if [ $? -eq 0 ]; then
rm -rf ${BACKUPDIR}
fi
# announce success
echo "Backup complete."
echo
# go back to wherever we started
cd ${CWD}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment