Files
hash-wallet/tools/demo/lib/secrets.sh
T
jwinterm c396e94b77 Add store capture harness: record real wallet flows into screenshots + video
Drives the real app through real flows on a device while recording the screen,
then cuts ONE recording into both deliverables — store screenshots and
marketing video — so a listing can't drift out of sync with its own trailer.

integration_test/demo/
  DemoSession adds what a passing test lacks: pacing (beat/hold/typeSlowly, so
  footage is watchable rather than a strobe), timing marks reported through
  binding.reportData, and seed guards. Scenarios reuse the existing 25 page
  robots rather than reimplementing navigation:
    core_store_set     dashboard, receive, send (composed, NEVER broadcast),
                       wallets, security — the store screenshot set
    onboarding_create  welcome -> PIN -> seed -> dashboard
    restore_from_seed  restore -> syncing
    swap_and_buy       Trocador quote, stopping BEFORE creating a trade; Buy
                       skipped when no provider key, matching what reviewers see

tools/demo/
  capture.sh   orchestrates boot/record/cut/shots/reel
  marks.py     maps timing marks onto the recording; cuts, extracts frames,
               builds manifests
  reel.py      branded master; reuses such_graphics video_transitions/video_qa
               when ~/src/such-graphics is present, degrades cleanly when not
               (this repo is public and must build without it)
  build-apk.sh local APK via the CI container image

Seed safety — the demo wallet is a real wallet with real funds:
  - seed comes from Vaultwarden at capture time, never the repo
  - guardNoSeedOnScreen() fails the run on a visible mnemonic, catching both
    the known seed and the generic shape of one (so freshly generated seeds
    are caught too)
  - unavoidable seed screens are wrapped in blindfold(); those spans are
    excised from the video with 0.6s padding, and screenshots honour the same
    padding
  - marks.py refuses to build a publishable master if any clip was cut without
    marks, since blindfold spans could not have been excised

Apple App Preview gets no touch indicators, captions, cards or music, and
reel.py enforces the 15-30s window rather than emitting an out-of-spec file.
Everything else keeps taps on, drawn by SurfaceFlinger via show_touches rather
than composited, so they look real.

Play sizing: the harness builds its own 1080x1920 AVD. The stock Medium Phone
AVD is 1080x2400 = 2.22:1 and Play rejects phone screenshots above 2:1.

Also gitignores android/app/upload-keystore.jks (and *.jks/*.keystore): the
Android workflow decodes ANDROID_KEYSTORE_BASE64 to exactly that path, so the
Play upload signing key was one `git add -A` away from being committed.
2026-07-31 15:25:29 -04:00

111 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Demo-wallet secret retrieval from the self-hosted Vaultwarden.
#
# The demo wallet is a REAL wallet with a REAL balance -- that is what makes
# the screenshots honest. Its seed therefore never lands in this repo, in
# .secrets.g.dart, in a shell history, or in a committed env file. It is
# fetched at capture time, held in a shell variable for the life of one
# capture run, and passed to the app as a --dart-define.
#
# Sourced by capture.sh; not meant to be run directly.
#
# Configuration lives in tools/demo/demo.env (gitignored). Copy
# demo.env.example and fill it in. Per ~/src/docs/engineering/
# developer-workstations.md: human credentials in Vaultwarden, ignored
# development values beside the worktree, only reviewed templates committed.
# ---------------------------------------------------------------------------
# bw CLI bootstrap
# ---------------------------------------------------------------------------
demo_secrets_check() {
if ! command -v bw >/dev/null 2>&1; then
cat >&2 <<'EOF'
ERROR: the Bitwarden CLI (`bw`) is not installed, so the demo wallet seed
cannot be fetched from Vaultwarden.
Install one of:
npm install -g @bitwarden/cli
sudo snap install bw
Then point it at the self-hosted server and log in (do this once):
bw config server "$VAULTWARDEN_SERVER" # value lives in tools/demo/demo.env
bw login
export BW_SESSION=$(bw unlock --raw)
Alternatively, for a one-off run you can bypass Vaultwarden entirely:
DEMO_WALLET_SEED='word word ...' tools/demo/capture.sh record core_store_set
...but do not put that in a file, and do not leave it in your shell history
(prefix the command with a space if HISTCONTROL=ignorespace).
EOF
return 1
fi
return 0
}
# demo_secret <field-name>
#
# Resolution order:
# 1. An already-exported env var of the same name (CI / one-off override).
# 2. A custom field on the Vaultwarden item named $VAULTWARDEN_ITEM.
# 3. The item's password/notes, when the field name is `seed`.
demo_secret() {
local field="$1"
local envname
envname="$(echo "$field" | tr '[:lower:]-' '[:upper:]_')"
# 1. explicit override
local existing="${!envname:-}"
if [[ -n "$existing" ]]; then
printf '%s' "$existing"
return 0
fi
demo_secrets_check || return 1
if [[ -z "${BW_SESSION:-}" ]]; then
echo "ERROR: BW_SESSION is not set. Unlock the vault first:" >&2
echo " export BW_SESSION=\$(bw unlock --raw)" >&2
return 1
fi
local item="${VAULTWARDEN_ITEM:-hash-bags-demo-wallet}"
local json
if ! json="$(bw get item "$item" 2>/dev/null)"; then
echo "ERROR: no Vaultwarden item named '$item'." >&2
echo "Create it and store the demo wallet seed there, or set" >&2
echo "VAULTWARDEN_ITEM in tools/demo/demo.env." >&2
return 1
fi
# Prefer an explicitly named custom field.
local value
value="$(printf '%s' "$json" | python3 -c '
import json, sys
field = sys.argv[1]
data = json.load(sys.stdin)
for f in (data.get("fields") or []):
if (f.get("name") or "").lower() == field.lower():
print(f.get("value") or "", end="")
sys.exit(0)
# fall back to notes / password for the seed itself
if field.lower() in ("seed", "demo_wallet_seed"):
print((data.get("notes") or (data.get("login") or {}).get("password") or ""), end="")
' "$field")"
if [[ -z "$value" ]]; then
echo "ERROR: Vaultwarden item '$item' has no field '$field'." >&2
return 1
fi
printf '%s' "$value"
}
# Load the gitignored local config if present.
demo_load_env() {
local envfile="$1"
if [[ -f "$envfile" ]]; then
# shellcheck disable=SC1090
set -a; source "$envfile"; set +a
fi
}