Skip to content

Instantly share code, notes, and snippets.

@AlexTMjugador
Created February 28, 2024 17:58
Show Gist options
  • Save AlexTMjugador/bbaab84e630986b75d534a5cb6deab1c to your computer and use it in GitHub Desktop.
Save AlexTMjugador/bbaab84e630986b75d534a5cb6deab1c to your computer and use it in GitHub Desktop.
Backup scripts for personal Bitwarden vaults, storing the results to a password-encrypted LUKS file volume. Perfect for simple and secure off-site backups of Bitwarden vaults where a little human interaction for providing key material is warranted.
#!/bin/sh -eu
# Backs up a personal Bitwarden vault to an encrypted LUKS file store.
. ./bw-common
echo '> Exporting personal vault to JSON file...'
bw export --format json --output "$WORKDIR"/backup/export.json
echo '> Exporting attachments...'
IFS="$(printf '\t')"
bw list items | \
jq -r '.[] |
select(.attachments) |
[.id, foreach .attachments[] as $attachment (null; $attachment.id + "," + $attachment.fileName)] |
@tsv' | \
while read -r item_with_attachments; do
item_id="$(echo "$item_with_attachments" | cut -d "$IFS" -f1 -)"
for attachment in $(echo "$item_with_attachments" | cut -d "$IFS" -f2- -); do
attachment_id="${attachment%%,*}"
attachment_file_name="${attachment#*,}"
echo "> Exporting attachment $attachment_id ($attachment_file_name) for item $item_id..."
mkdir -p "$WORKDIR/backup/attachments/$item_id"
bw get attachment "$attachment_id" \
--itemid "$item_id" \
--output "$WORKDIR/backup/attachments/$item_id/$attachment_file_name"
done
done
echo 'Backup done, have a nice day!'
# shellcheck shell=sh
# Required non-POSIX binaries:
# - cryptsetup
# - sudo
# - fallocate
# - curl
# - jq
# - mkfs.ext4
# - funzip
# - GNU find (i.e., with -delete option)
# The following three constants are parameters that can be changed
readonly SERVER='https://vault.bitwarden.eu'
readonly BW_CLI_VERSION='2024.2.0'
readonly BACKUP_STORE_SIZE=128M
WORKDIR="$(mktemp -d -t bitwarden.XXX)"
readonly WORKDIR
trap '
if [ -d "$WORKDIR/backup" ]; then
sudo umount "$WORKDIR/backup" || true
sudo cryptsetup luksClose bw_backup || true
fi
rm -rf "$WORKDIR/appdata"
find "$WORKDIR" -delete
sync
' EXIT INT TERM
export PATH="$WORKDIR:$PATH"
export BITWARDENCLI_APPDATA_DIR="$WORKDIR/appdata"
if ! [ -f 'backup' ]; then
echo "> Creating encrypted backup store (size: $BACKUP_STORE_SIZE)..."
fallocate -l "$BACKUP_STORE_SIZE" backup
sudo cryptsetup -y luksFormat backup
created_backup_file=1
else
created_backup_file=
fi
echo '> Opening encrypted backup store...'
sudo cryptsetup luksOpen backup bw_backup
if [ -n "$created_backup_file" ]; then
sudo mkfs -t ext4 /dev/mapper/bw_backup
fi
echo '> Mounting encrypted backup store...'
mkdir -p "$WORKDIR/backup"
sudo mount /dev/mapper/bw_backup "$WORKDIR/backup"
if [ -n "$created_backup_file" ]; then
sudo chmod ugo+rwx "$WORKDIR/backup"
fi
echo '> Downloading Bitwarden CLI...'
curl -L "https://github.com/bitwarden/clients/releases/download/cli-v${BW_CLI_VERSION}/bw-linux-${BW_CLI_VERSION}.zip" | \
funzip > "$WORKDIR/bw"
chmod +x "$WORKDIR/bw"
echo '> Logging in to Bitwarden...'
bw config server "$SERVER"
BW_SESSION=$(bw login --raw)
export BW_SESSION
#!/bin/sh -eu
# Mounts a personal Bitwarden vault backup at an encrypted LUKS file store, and
# drops to a shell in the mount point for interactive manipulation.
. ./bw-common
CWD="$PWD"
cd "$WORKDIR"/backup
$SHELL
cd "$CWD"
#!/bin/sh -eu
# Restores a personal Bitwarden vault backup from an encrypted LUKS file store.
. ./bw-common
echo '> Importing personal vault from JSON file...'
bw import bitwardenjson "$WORKDIR"/backup/export.json
# Importing attachments doesn't work reliably because item IDs can be
# randomized on import, so the following won't be able to add attachments
# to unexisting item IDs:
# echo '> Importing attachments...'
# for item_path in "$WORKDIR"/backup/attachments/*; do
# for attachment_path in "$item_path"/*; do
# item_id="${item_path#"$WORKDIR"/backup/attachments/}"
# bw create attachment --itemid "$item_id" --file "$attachment_path"
# done
# done
echo 'Restore done, remember to sync any logged-in clients. Have a nice day!'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment