Operator branding — logo, title, color scheme overrides #47
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
apps/machine/public/logo.png— baked into the nix-store closureapps/machine/src/views/IdleView.vue:72— title (Bitcoinmat)apps/machine/src/style.css— color scheme (currently defaults to gruvbox:root; 6 built-in[data-theme='...']palettes available)This conflicts with the "identity-free image" property of the deploy pipeline (
docs/machine-installation.md): one image flavor should serve every operator, with per-deployment variation living in/var/lib/bitspire/and provisioned viaprovision-atm.sh. Defaults baked into the build apply when no override is present.V1 scope — dual-source
Branding has TWO concurrent sources. Source-of-truth priority (highest wins):
@bitSpire/brandingNostr event from satmachineadmin — operator pubkey publishes a replaceable event (kind + d-tag specified in follow-up issue). The ATM subscribes on boot; subsequent updates apply live./var/lib/bitspire/branding/—logo.png+logo-dark.png+branding.jsondeposited on disk by the operator (e.g. viaprovision-branding.sh).Bitcoinmat//logo.png/ gruvbox baseline.The ATM-side consumer is structured as a
BrandingSourceinterface so the Nostr path can be wired without touching renderer code. Local-file is implemented first (faster to ship, no hard dep on satmachineadmin work); the Nostr path lands once the corresponding satmachineadmin work is done (follow-up issue).Schema
The local-file source uses three sibling files under
/var/lib/bitspire/branding/. Any of them is optional; absent files fall back to defaults.logo.png— operator logo, rendered at ~12vh on the idle screenlogo-dark.png— optional dark-mode variant; renderer auto-switches based on the effective color mode (resolvescolorMode = systemviaprefers-color-scheme). Falls back tologo.pngwhen absentbranding.json— JSON with these optional fields:title(string, default"Bitcoinmat")theme— one of"gruvbox","catppuccin","cyberpunk","dracula","nord","tokyo-night", or"custom"custom_colors— whentheme: "custom", an object mapping CSS-var names to hex values. Supports any of:--background,--foreground,--card,--card-foreground,--popover,--popover-foreground,--primary,--primary-foreground,--secondary,--secondary-foreground,--muted,--muted-foreground,--accent,--accent-foreground,--destructive,--border,--input,--ring,--success,--success-foreground,--warning,--warning-foreground,--bitcoin,--bitcoin-foreground,--qr,--qr-foreground,--radius. Operator can override a subset; unset vars fall back to gruvbox defaults.custom_colors.dark— same shape, applied under.darkmodeThe Nostr-event source (V2) will carry the same logical payload; binary logos go inline as base64 (or as an
https://URL the ATM dereferences) since Nostr events can't reference sibling files.Plumbing
Electron main process exposes a
brandingobject via the existingget-configIPC, with bothlogoDataUrlandlogoDarkDataUrlfields (base64 PNGs or null). The renderer appliestitle+theme+ (fortheme: "custom") injects a<style id="custom-theme">block setting the chosen vars on:rootand.dark. Logos bind to an<img :src>reactive ref that switches between light/dark variants based on the effective color mode — so live Nostr updates AND OS-level dark-mode toggles propagate without a reload.Provisioning (local-file source)
New
deploy/nixos/provision-branding.sh: rsyncs a local<dir>into/var/lib/bitspire/branding/on the ATM and restartsbitspire.service. Composes withprovision-atm.shrather than overloading it (the existing script bundles dev-only QEMU credential fetching and is awkward to extend with a--branding-dirflag).Future considerations (decide when we pick this up)
To weigh after bitSpire + satmachineadmin dashboard are finished:
No KYC/No Registration/Just Bitcoin(IdleView.vue:73-82). Could be an array of{label, variant}inbranding.json./support. Probably needed for any real fleet deployment.Buy Bitcoin/Sell Bitcoinplus subtitles (IdleView.vue:127-142). Operator might want localized or different wording.Cross-product note
The eventual source-of-truth for branding is satmachineadmin acting as the bitSpire operator dashboard (see follow-up issue). Operator uploads logo / picks theme / sets title once in the LNbits admin UI; satmachineadmin publishes a replaceable Nostr event keyed on operator pubkey; every bitSpire ATM tied to that operator subscribes and applies live. This matches the "no HTTP to the Lightning backend" / "Nostr-only comms" direction documented in
CLAUDE.mdondevand avoids per-ATM rsync entirely once it lands. The local-file source remains as a bootstrap path and an offline-recovery escape hatch.Acceptance
branding.jsonwiththeme: "catppuccin"+ customtitle+logo.pngin/var/lib/bitspire/branding/renders the overridelogo-dark.pngin addition tologo.pngswaps logos when the kiosk's color mode is dark (verified by toggling the light/dark button)branding.jsonwiththeme: "custom"and a partialcustom_colorsmap applies overrides on top of gruvbox defaultsprovision-branding.sh ./acme-brand <ip>deploys branding folder and restartsbitspire.servicesystemctl restart bitspire(no rebuild needed)BrandingSourceinterface in place so the Nostr-event source can be added without rewriting renderer consumers