Created
December 24, 2024 02:22
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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