Adds the render.py WeasyPrint renderer, the pod-based bitwarden-print.sh wrapper, a Containerfile, a sample export (JSON + PDF), README and LICENSE.
118 lines
4.4 KiB
Markdown
118 lines
4.4 KiB
Markdown
# bitwarden-print
|
|
|
|
Render a Bitwarden `.json` export as a dense PDF: a summary, a page-numbered
|
|
index, and one compact block per vault item that prints **every** value in the
|
|
export — known fields first (username, password, TOTP, card, identity, SSH key,
|
|
custom fields, notes, history, attachments), then everything else
|
|
alphabetically, including unknown/extra keys and empty objects/lists.
|
|
|
|
Everything runs in a throwaway Podman pod on this machine; the vault never
|
|
leaves it.
|
|
|
|
## Requirements
|
|
|
|
- Podman (tested with 6.x)
|
|
- On macOS: `podman machine start` (the default machine is used)
|
|
|
|
## Quick start
|
|
|
|
```sh
|
|
./bitwarden-print.sh ~/Downloads/bitwarden_export_20261113.json
|
|
# -> ~/Downloads/bitwarden_export_20261113.pdf
|
|
```
|
|
|
|
Custom output and options:
|
|
|
|
```sh
|
|
./bitwarden-print.sh vault.json -o ~/Desktop/vault.pdf \
|
|
--paper letter --sort name --title "Vault 2026-11-13"
|
|
```
|
|
|
|
Mask secrets instead of printing them:
|
|
|
|
```sh
|
|
./bitwarden-print.sh vault.json --mask-secrets
|
|
```
|
|
|
|
Options:
|
|
|
|
| Option | Meaning |
|
|
| --- | --- |
|
|
| `-o, --output FILE` | output PDF (default: next to the export, same basename) |
|
|
| `--sort folder\|name\|type\|as-is` | item order (default: `folder`, then name) |
|
|
| `--title TEXT` | document title (also the page footer) |
|
|
| `--paper a4\|letter` | page size (default: `a4`) |
|
|
| `--mask-secrets` | mask `login.password`, `login.totp`, `card.number`, `card.code`, `identity.ssn`, `sshKey.privateKey`, …; values keep a length hint |
|
|
|
|
## What the PDF contains
|
|
|
|
1. **Header** — source file, item count, value count, timestamp.
|
|
2. **Summary** — export metadata, item/type/folder counts, custom field,
|
|
attachment and password-history totals, created/revised date ranges, plus a
|
|
collections table if the export has one.
|
|
3. **Index** — one line per item (type, name, folder, username) with a real
|
|
page number, clickable in the PDF.
|
|
4. **Items** — for each item a heading (number, type badge, name, folder) and a
|
|
two-column table of `json.path` → value. Values wrap, newlines in notes are
|
|
preserved, and empty values (`null`, `""`, `{}`, `[]`) are shown dimmed so
|
|
nothing silently disappears.
|
|
|
|
Items are not truncated: an export with many entries produces many pages, but
|
|
each line is only a few millimetres tall.
|
|
|
|
## How it runs
|
|
|
|
```
|
|
./bitwarden-print.sh
|
|
-> podman build -t localhost/bitwarden-print:latest . # Python + WeasyPrint image
|
|
-> podman pod create --name bitwarden-print-pod
|
|
-> podman create --pod bitwarden-print-pod ... bitwarden-print-run
|
|
-> podman cp export.json bitwarden-print-run:/export.json # no bind mounts
|
|
-> podman start -a bitwarden-print-run # render.py -> /output.pdf
|
|
-> podman cp bitwarden-print-run:/output.pdf <output>
|
|
-> pod, container and temp file are removed
|
|
```
|
|
|
|
The input is copied into the container instead of bind-mounted, so arbitrary
|
|
host paths work (including `/tmp`, which the Podman machine does not share) and
|
|
no SELinux relabelling is required.
|
|
|
|
Manual equivalent:
|
|
|
|
```sh
|
|
podman build -t localhost/bitwarden-print:latest .
|
|
podman pod create --name bitwarden-print-pod
|
|
podman create --pod bitwarden-print-pod --name bw localhost/bitwarden-print:latest \
|
|
/export.json -o /output.pdf
|
|
podman cp vault.json bw:/export.json
|
|
podman start -a bw
|
|
podman cp bw:/output.pdf ./vault.pdf
|
|
podman rm -f bw && podman pod rm -f bitwarden-print-pod
|
|
```
|
|
|
|
## Files
|
|
|
|
- `bitwarden-print.sh` — CLI wrapper that builds the image and drives the pod
|
|
- `Containerfile` — `python:3.12-slim-bookworm` + WeasyPrint (Pango/Cairo) + DejaVu fonts
|
|
- `render.py` — JSON → HTML → PDF, no data leaves the container
|
|
- `examples/sample-export.json` — synthetic vault used for testing (all values fake)
|
|
|
|
Run the renderer directly on a host that has WeasyPrint installed:
|
|
|
|
```sh
|
|
pip install weasyprint==62.3 pydyf==0.10.0
|
|
./render.py examples/sample-export.json -o /tmp/sample.pdf --html-out /tmp/sample.html
|
|
```
|
|
|
|
## Security notes
|
|
|
|
- The generated PDF contains your vault in **plaintext** unless you pass
|
|
`--mask-secrets`. Treat the PDF like the export itself: keep it local, and
|
|
delete it when you are done.
|
|
- `--mask-secrets` only masks well-known secret paths. Secrets stored inside
|
|
custom fields (`fields[i].value`), notes or file names are printed as-is.
|
|
- Encrypted exports (`"encrypted": true`) are rejected with a hint: re-export
|
|
from Bitwarden as `.json` without a file password first.
|
|
- The container is removed after each run, but the image
|
|
(`localhost/bitwarden-print:latest`) stays cached for the next run.
|