Adds the render.py WeasyPrint renderer, the pod-based bitwarden-print.sh wrapper, a Containerfile, a sample export (JSON + PDF), README and LICENSE.
110 lines
3.3 KiB
Bash
Executable File
110 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Render a Bitwarden JSON export to PDF inside a dedicated Podman pod.
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
IMAGE=${IMAGE:-localhost/bitwarden-print:latest}
|
|
POD=${POD:-bitwarden-print-pod}
|
|
CONTAINER=${CONTAINER:-bitwarden-print-run}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./bitwarden-print.sh EXPORT.json [-o OUT.pdf] [options]
|
|
|
|
Creates a PDF with every value from a Bitwarden JSON export, densely typeset.
|
|
|
|
Options:
|
|
-o, --output FILE output PDF (default: next to the export, same basename)
|
|
--sort MODE folder (default), name, type or as-is
|
|
--title TEXT document title
|
|
--paper SIZE a4 (default) or letter
|
|
--mask-secrets mask passwords, TOTP, card numbers/codes, SSN, keys
|
|
-h, --help show this help
|
|
|
|
Environment:
|
|
IMAGE, POD, CONTAINER override image/pod/container names
|
|
EOF
|
|
}
|
|
|
|
INPUT=""
|
|
OUTPUT=""
|
|
render_args=()
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-o|--output)
|
|
[ $# -ge 2 ] || { echo "error: $1 needs a value" >&2; exit 2; }
|
|
OUTPUT=$2; shift 2 ;;
|
|
--sort)
|
|
[ $# -ge 2 ] || { echo "error: $1 needs a value" >&2; exit 2; }
|
|
render_args+=(--sort "$2"); shift 2 ;;
|
|
--paper)
|
|
[ $# -ge 2 ] || { echo "error: $1 needs a value" >&2; exit 2; }
|
|
render_args+=(--paper "$2"); shift 2 ;;
|
|
--title)
|
|
[ $# -ge 2 ] || { echo "error: $1 needs a value" >&2; exit 2; }
|
|
render_args+=(--title "$2"); shift 2 ;;
|
|
--mask-secrets)
|
|
render_args+=(--mask-secrets); shift ;;
|
|
-h|--help)
|
|
usage; exit 0 ;;
|
|
-*)
|
|
echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
|
|
*)
|
|
[ -z "$INPUT" ] || { echo "error: only one export file can be given" >&2; exit 2; }
|
|
INPUT=$1; shift ;;
|
|
esac
|
|
done
|
|
|
|
[ -n "$INPUT" ] || { usage >&2; exit 2; }
|
|
[ -f "$INPUT" ] || { echo "error: no such file: $INPUT" >&2; exit 1; }
|
|
|
|
in_dir=$(cd -- "$(dirname -- "$INPUT")" && pwd)
|
|
in_name=$(basename -- "$INPUT")
|
|
|
|
if [ -z "$OUTPUT" ]; then
|
|
OUTPUT="$in_dir/${in_name%.json}.pdf"
|
|
fi
|
|
out_dir=$(dirname -- "$OUTPUT")
|
|
mkdir -p -- "$out_dir"
|
|
out_dir=$(cd -- "$out_dir" && pwd)
|
|
out_name=$(basename -- "$OUTPUT")
|
|
|
|
cleanup() {
|
|
podman rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
|
podman pod exists "$POD" >/dev/null 2>&1 && podman pod rm -f "$POD" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "==> building image $IMAGE"
|
|
podman build -q -t "$IMAGE" "$SCRIPT_DIR"
|
|
|
|
echo "==> creating pod $POD"
|
|
podman pod rm -f "$POD" >/dev/null 2>&1 || true
|
|
podman pod create --name "$POD" >/dev/null
|
|
|
|
# Copy the export in instead of bind-mounting it: works for any host path,
|
|
# including paths the Podman machine does not share (e.g. /tmp) and hosts
|
|
# where SELinux relabelling is not allowed.
|
|
echo "==> rendering $in_name"
|
|
podman create --pod "$POD" --name "$CONTAINER" \
|
|
"$IMAGE" "/export.json" -o "/output.pdf" \
|
|
${render_args[@]+"${render_args[@]}"} >/dev/null
|
|
podman cp "$INPUT" "$CONTAINER:/export.json"
|
|
|
|
status=0
|
|
podman start -a "$CONTAINER" || status=$?
|
|
if [ "$status" -ne 0 ]; then
|
|
echo "error: rendering failed (exit $status)" >&2
|
|
exit "$status"
|
|
fi
|
|
|
|
tmp_pdf=$(mktemp "${TMPDIR:-/tmp}/bitwarden-print.XXXXXX.pdf")
|
|
if ! podman cp "$CONTAINER:/output.pdf" "$tmp_pdf"; then
|
|
echo "error: container produced no PDF at /output.pdf" >&2
|
|
exit 1
|
|
fi
|
|
mv -f "$tmp_pdf" "$out_dir/$out_name"
|
|
|
|
echo "==> PDF: $out_dir/$out_name"
|