Session-based operations with no interactive prompts. Covers: - Session setup from provider.env - Read (list, get by name, get by ID) - Create (base64-encoded JSON, with collection) - Update (get → modify → pipe to edit) - Delete - Upsert pattern (create if absent, update if exists) - Rebuild authorized_keys from vault items Proven working: full round-trip of key creation → vault publish → read back → delete on OSA 2026-06-21.
5.3 KiB
5.3 KiB
| name | description |
|---|---|
| bitwarden-cli-vault | Use the Bitwarden CLI (bw) to read, write, update, and delete vault items — session-based, no interactive prompts. |
Bitwarden CLI (bw) — Vault Operations
Use the bw CLI to automate vault reads and writes. Session-based auth:
no interactive master password prompts after initial unlock.
Prerequisites
provider.env must contain the 3 bootstrap values:
BW_CLIENTID=user.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BW_CLIENTSECRET=***
BW_PASSWORD=***
Session setup (once per script)
source <(grep -E '^BW_' /usr/local/etc/colibri/provider.env)
bw logout >/dev/null 2>&1
bw login --apikey >/dev/null 2>&1
BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD 2>&1 | grep 'export' | sed 's/.*BW_SESSION="\(.*\)".*/\1/')
export BW_SESSION
All subsequent commands use --session "$BW_SESSION". No password prompts.
Read
List all items
bw list items --session "$BW_SESSION" | python3.12 -c "import sys,json; [print(i['name'],i['id']) for i in json.load(sys.stdin)]"
Get one item by name
ITEM_ID=$(bw list items --search "item-name" --session "$BW_SESSION" | python3.12 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
bw get item "$ITEM_ID" --session "$BW_SESSION" | python3.12 -c "import sys,json; d=json.load(sys.stdin); print(d['notes'])"
Get one item by ID
bw get item "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" --session "$BW_SESSION"
List collections
bw list collections --session "$BW_SESSION" | python3.12 -c "import sys,json; [print(c['name'],c['id']) for c in json.load(sys.stdin)]"
Create
Create a secure note with base64-encoded JSON. Pipe it in — no interactive prompts.
NOTES="your content here"
JSON=$(python3.12 -c "
import json, base64
item = {
'type': 2, # 2 = secure note
'name': 'item-name',
'notes': '$NOTES'
}
print(base64.b64encode(json.dumps(item).encode()).decode())
")
echo "$JSON" | bw create item --session "$BW_SESSION"
Create in a specific collection
Add collectionIds to the JSON:
item = {
'type': 2,
'name': 'item-name',
'notes': '$NOTES',
'collectionIds': ['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx']
}
Get a collection ID by name
COLLECTION_ID=$(bw list collections --session "$BW_SESSION" | python3.12 -c "
import sys,json
for c in json.load(sys.stdin):
if c['name'] == 'hive-pubkeys':
print(c['id'])
")
If the collection doesn't exist, create it via the Vaultwarden web UI (no CLI for org-collections without organization admin API).
Update
Get the item, modify the JSON, re-encode, pipe to bw edit:
ITEM_ID=$(bw list items --search "item-name" --session "$BW_SESSION" | python3.12 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
EXISTING=$(bw get item "$ITEM_ID" --session "$BW_SESSION")
UPDATED=$(echo "$EXISTING" | python3.12 -c "
import json, base64, sys
d = json.load(sys.stdin)
d['notes'] = 'new content'
print(base64.b64encode(json.dumps(d).encode()).decode())
")
echo "$UPDATED" | bw edit item "$ITEM_ID" --session "$BW_SESSION"
Delete
bw delete item "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" --session "$BW_SESSION"
Note: this moves to trash. Use Vaultwarden web UI for permanent deletion.
Common patterns
Upsert (create if absent, update if exists)
ITEM_NAME="$(hostname)"
NOTES="$PUBKEY"
EXISTING_ID=$(bw list items --search "$ITEM_NAME" --session "$BW_SESSION" 2>/dev/null | python3.12 -c "
import sys,json
data = json.load(sys.stdin)
for i in data:
if i.get('name') == '$ITEM_NAME':
print(i['id'])
break
" 2>/dev/null)
if [ -n "$EXISTING_ID" ]; then
# Update
CURRENT=$(bw get item "$EXISTING_ID" --session "$BW_SESSION")
echo "$CURRENT" | python3.12 -c "
import json, base64, sys
d = json.load(sys.stdin)
d['notes'] = '$NOTES'
print(base64.b64encode(json.dumps(d).encode()).decode())
" | bw edit item "$EXISTING_ID" --session "$BW_SESSION"
else
# Create
python3.12 -c "
import json, base64
print(base64.b64encode(json.dumps({
'type': 2,
'name': '$ITEM_NAME',
'notes': '$NOTES'
}).encode()).decode())
" | bw create item --session "$BW_SESSION"
fi
Rebuild authorized_keys from vault items
# For mother-sync-hive-keys: pull all pubkeys, wrap with SSH restrictions
bw list items --search "colibri@" --session "$BW_SESSION" | python3.12 -c "
import json, sys
for i in json.load(sys.stdin):
pubkey = i.get('notes','')
hostname = i.get('name','')
if pubkey.startswith('ssh-'):
print(f'command=\"colibri-mcp\",restrict,no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding {pubkey} colibri@{hostname}')
" > /var/db/colibri/.ssh/authorized_keys.hive
chmod 600 /var/db/colibri/.ssh/authorized_keys.hive
Pitfalls
bw create/editneed base64-encoded JSON piped to stdin;--sessionmust be a flag, not env-onlybw get/list/deleteaccept--sessiondirectlybw list items --searchdoes substring match — filter by exact.nameif multiple results- Session expires after a few hours; re-unlock for long-running scripts
bw create org-collectionrequires organization admin (not available via personal API key) — create collections in web UI- Delete moves to trash; permanent delete requires web UI or
bw delete item <id> --permanent