Skip to content

Instantly share code, notes, and snippets.

@stek29
Created December 20, 2022 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stek29/b77e2cc12cdcc15e0247a68072a118a2 to your computer and use it in GitHub Desktop.
Save stek29/b77e2cc12cdcc15e0247a68072a118a2 to your computer and use it in GitHub Desktop.
restore folder structure in Bitwarden after migrating from 1Password
#!/usr/bin/env bash
set -euo pipefail
# Step 0: Log in
if ! op whoami >/dev/null 2>/dev/null; then
eval "$(op signin)"
fi
if [ "$(bw status | jq -r .status)" != "unlocked" ]; then
BW_SESSION="$(bw unlock --raw)"
export BW_SESSION
fi
# and ensure everything is synced -- but only bw nees explicit sync :)
# bw sync
# Step 1. Ensure BW folders for all 1P vaults
bw_missing_folders="$(comm -13 <(bw list folders | jq -r '.[].name' | sort) <(op vault list --format=json | jq -r '.[].name' | sort))"
if [ ! -z "${bw_missing_folders}" ]; then
while read -r folder_name; do
echo "[BW] creating folder: ${folder_name}"
bw get template folder | jq --arg name "${folder_name}" '.name=$name' | bw encode | bw create folder
done <<<"${bw_missing_folders}"
fi
# Step 2. Get item name -> ID mapping for BW
# Print all items with non-unique names
echo "[BW] Items with non-unique names (they will be skipped):"
bw list items | jq -r '[.[] | select(.folderId == null) ] | group_by(.name) | .[] | select(length > 1) | .[].name'
echo "[1P] Items with non-unique names (they will be skipped):"
op item list --format=json | jq -r '. | group_by(.title) | .[] | select(length > 1) | .[] | "\(.vault.name)/\(.title)"'
# Get mapping from item name to bitwarden folder via 1p vault
bw_folder_ids="$(bw list folders | jq -c 'map(select(.id != null)) | map({key: .id, value: .name}) | from_entries')"
bw_folder_ids_reverse="$(bw list folders | jq -c 'map(select(.id != null)) | map({key: .name, value: .id}) | from_entries')"
op_item_map="$(op item list --format=json | jq --argjson bw_folders "${bw_folder_ids_reverse}" -c '[ . | group_by(.title) | .[] | select(length == 1) | .[0] | {key: .title, value: $bw_folders[.vault.name]} ] | from_entries')"
echo "[BW] This items don't have matching 1P items (they will be skipped):"
bw list items | jq --argjson item_map "${op_item_map}" -r '[ [.[] | select(.folderId == null) ] | group_by(.name) | .[] | select(length == 1) | .[0] ] | map(select($item_map[.name] == null)) | .[].name'
upd_items="$(bw list items | jq --argjson item_map "${op_item_map}" -c '[ [.[] | select(.folderId == null) ] | group_by(.name) | .[] | select(length == 1) | .[0] ] | map(select($item_map[.name] != null)) | map(.folderId=$item_map[.name]) | map({key: .id, value: .}) | from_entries')"
jq -r 'keys[]' <<<"${upd_items}" | while read -r item_id; do
<<<"${upd_items}" jq --arg item_id "${item_id}" --argjson bw_folders "${bw_folder_ids}" -r '.[$item_id] | "[BW] Moving \(.name) to \($bw_folders[.folderId])"'
<<<"${upd_items}" jq --arg item_id "${item_id}" --argjson bw_folders "${bw_folder_ids}" -r '.[$item_id]' | bw encode | bw edit item "${item_id}" | jq -c '{id: .id, folderId: .folderId}'
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment