Sets LNBITS_AUTH_HTTPS_ONLY=false on the lnbits service so the regtest stack works when the webapp dev server hits lnbits over a LAN IP (e.g. http://192.168.0.32:5181 → http://192.168.0.32:5001) instead of localhost. Browsers silently discard `Secure` cookies over plain HTTP from non-loopback origins, which kills the /auth/sign-event double- submit CSRF flow (the GET succeeds, the cookie never lands, every POST 403s). aiolabs/lnbits#52 made the lnbits-side gate use settings.auth_https_only for the XSRF-TOKEN cookie (matching the existing auth_cookie_token behavior). With that fix on dev + this env var here, the regtest stack finally lets the bookmark/RSVP flow round-trip from a LAN-IP browser. Production deploys leave the setting unset (default true) — Secure stays on, behavior unchanged.
237 lines
13 KiB
YAML
237 lines
13 KiB
YAML
# Lightweight LNbits dev environment — FakeWallet, no Lightning nodes
|
|
#
|
|
# Usage:
|
|
# docker compose -f docker-compose.dev.yml up -d --build
|
|
# docker compose -f docker-compose.dev.yml logs -f lnbits
|
|
# docker compose -f docker-compose.dev.yml down
|
|
#
|
|
# LNBITS_SRC defaults to ~/dev/lnbits/main. Override to use a different branch:
|
|
# LNBITS_SRC=~/dev/lnbits/dev docker compose -f docker-compose.dev.yml up -d --build
|
|
#
|
|
# LNbits: http://localhost:5001
|
|
# Fava: http://localhost:3333
|
|
# pict-rs: http://localhost:6033
|
|
|
|
services:
|
|
lnbits:
|
|
hostname: lnbits
|
|
build: ${LNBITS_SRC:-/home/padreug/dev/lnbits/main}
|
|
restart: on-failure
|
|
user: "0:0"
|
|
environment:
|
|
LNBITS_PORT: 5001
|
|
DEBUG: true
|
|
LNBITS_ADMIN_UI: true
|
|
# Allow LAN/HTTP access for dev (auth cookies don't require HTTPS).
|
|
# Keep TRUE for any production-shaped deployment.
|
|
AUTH_HTTPS_ONLY: "false"
|
|
LNBITS_BACKEND_WALLET_CLASS: "FakeWallet"
|
|
LNBITS_DATA_FOLDER: "./data"
|
|
LNBITS_EXTENSIONS_PATH: "/shared"
|
|
FAKE_WALLET_SECRET: "devsecret"
|
|
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "lnurlp,nostrclient,nostrrelay,nostrmarket,events,libra,satmachineclient,satmachineadmin"
|
|
LNBITS_ADMIN_EXTENSIONS: "nostrclient,nostrrelay,satmachineadmin"
|
|
LNBITS_USER_DEFAULT_EXTENSIONS: "lnurlp,nostrmarket,events,libra,satmachineclient"
|
|
# Nostr transport layer (HTTP-free RPC over kind-21000 events).
|
|
# The keypair below is pinned via the local `.env` so it survives
|
|
# container restarts. Without a pinned key LNbits auto-generates a
|
|
# fresh one on every boot, which breaks any ATM provisioned against
|
|
# the prior pubkey (encryption target no longer exists). The relay
|
|
# points at the nostrrelay extension running inside the same
|
|
# container — a relay named "test" must be created via the UI
|
|
# before the transport will successfully connect (the reconnect
|
|
# loop retries every 5s).
|
|
NOSTR_TRANSPORT_ENABLED: "true"
|
|
NOSTR_TRANSPORT_RELAYS: '["ws://localhost:5001/nostrrelay/test"]'
|
|
NOSTR_TRANSPORT_PRIVATE_KEY: ${NOSTR_TRANSPORT_PRIVATE_KEY}
|
|
NOSTR_TRANSPORT_PUBLIC_KEY: ${NOSTR_TRANSPORT_PUBLIC_KEY}
|
|
# Path-B roster gate (aiolabs/lnbits#42 / satmachineadmin#20). When
|
|
# true, inbound kind-21000 RPCs from unregistered sender pubkeys are
|
|
# rejected + ERROR-logged instead of auto-creating an account-from-
|
|
# npub (which was the silent-drop failure mode on 2026-05-30).
|
|
# satmachineadmin's resolver routes registered ATMs to the operator's
|
|
# wallet directly; under this flag, that's the ONLY routing path.
|
|
NOSTR_TRANSPORT_ROSTER_REQUIRED: "true"
|
|
# Envelope-encryption master key for user nostr nsecs (aiolabs/lnbits#9
|
|
# PR #17). Required — lnbits fail-closes at startup if unset. Sourced
|
|
# from .env; rotate via `openssl rand -hex 32` (one-time ops procedure).
|
|
LNBITS_KEY_MASTER: ${LNBITS_KEY_MASTER}
|
|
|
|
# nsec bunker integration (aiolabs/lnbits#18 phase 2.1 + 2.2).
|
|
# When set, new accounts get provisioned via the sidecar bunker
|
|
# (signer_type=RemoteBunkerSigner) instead of the LocalSigner
|
|
# encrypted-blob path. All four must be present together or
|
|
# NsecBunkerAdminClient.from_settings raises.
|
|
# Bunker container needs NSECBUNKER_ADMIN_NPUBS configured with the
|
|
# npub corresponding to LNBITS_NSEC_BUNKER_ADMIN_NSEC below.
|
|
LNBITS_NSEC_BUNKER_URL: ${LNBITS_NSEC_BUNKER_URL:-}
|
|
LNBITS_NSEC_BUNKER_PUBKEY: ${LNBITS_NSEC_BUNKER_PUBKEY:-}
|
|
LNBITS_NSEC_BUNKER_ADMIN_NSEC: ${LNBITS_NSEC_BUNKER_ADMIN_NSEC:-}
|
|
LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE: ${LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE:-}
|
|
# CORS allowlist for credentialed cross-origin webapp calls
|
|
# (aiolabs/lnbits PR #31). Mirrors the aio-demo wiring from
|
|
# server-deploy `b920b79`; covers three hostname flavors browsers
|
|
# treat as distinct origins: `localhost`, the LAN IP `192.168.0.32`,
|
|
# and the loopback IP `127.0.0.1` (browsers do NOT alias loopback
|
|
# to localhost for SOP purposes — `localhost` and `127.0.0.1` are
|
|
# different origins as far as CORS is concerned; gotcha caught
|
|
# 2026-05-30T11:05Z when a chromium session opened on the loopback
|
|
# IP got rejected against an allowlist that only had localhost +
|
|
# LAN-IP). Covers every standalone webapp dev port from
|
|
# `~/dev/webapp/dev/vite.*.config.ts` plus the umbrella 5173:
|
|
# 5173 main, 5180 libra, 5181 activities, 5182 wallet,
|
|
# 5183 chat, 5184 forum, 5185 market, 5186 tasks,
|
|
# 5187 restaurant.
|
|
# Empty allowlist would fall back to `Access-Control-Allow-Origin: *`
|
|
# (no credentials); explicit allowlist also covers the
|
|
# /auth/sign-event credentialed flow when bucket-B PRs land.
|
|
LNBITS_CORS_ALLOWED_ORIGINS: '["http://localhost:5173","http://192.168.0.32:5173","http://127.0.0.1:5173","http://localhost:5180","http://192.168.0.32:5180","http://127.0.0.1:5180","http://localhost:5181","http://192.168.0.32:5181","http://127.0.0.1:5181","http://localhost:5182","http://192.168.0.32:5182","http://127.0.0.1:5182","http://localhost:5183","http://192.168.0.32:5183","http://127.0.0.1:5183","http://localhost:5184","http://192.168.0.32:5184","http://127.0.0.1:5184","http://localhost:5185","http://192.168.0.32:5185","http://127.0.0.1:5185","http://localhost:5186","http://192.168.0.32:5186","http://127.0.0.1:5186","http://localhost:5187","http://192.168.0.32:5187","http://127.0.0.1:5187"]'
|
|
# Drop the `Secure` flag on auth + CSRF cookies so the regtest
|
|
# stack works over plain HTTP from a LAN IP (e.g. testing the
|
|
# webapp at http://192.168.0.32:5181 against this lnbits at
|
|
# http://192.168.0.32:5001). Browsers silently discard `Secure`
|
|
# cookies served over `http://` from non-loopback origins, which
|
|
# breaks the /auth/sign-event double-submit CSRF flow. Production
|
|
# leaves this unset (default `true`) — see aiolabs/lnbits#52.
|
|
LNBITS_AUTH_HTTPS_ONLY: "false"
|
|
# Lowered from the 40_000 default just to make sharding easy to
|
|
# exercise in local tests without seeding hundreds of payments.
|
|
# Production runs should leave this unset (defaults to 40_000).
|
|
NOSTR_TRANSPORT_MAX_EVENT_CONTENT_LENGTH: "20000"
|
|
# Auto-credit 1M sats to freshly-created accounts. Required by the
|
|
# transport-driver `--flow subscribe` end-to-end test which pays a
|
|
# FakeWallet invoice between two newly created wallets. Gated to
|
|
# FakeWallet + this explicit opt-in (see lnbits/core/services/users.py).
|
|
LNBITS_DEMO_MODE: "true"
|
|
ports:
|
|
- 5001:5001
|
|
volumes:
|
|
- ./data/lnbits-dev:/app/data
|
|
- /home/padreug/dev/shared/extensions:/shared/extensions
|
|
# Bind the lnbits package so host edits are picked up on container
|
|
# restart without rebuilding the image. The venv at /app/.venv stays
|
|
# baked in.
|
|
- ${LNBITS_SRC:-/home/padreug/dev/lnbits/main}/lnbits:/app/lnbits
|
|
# Pre-start init: pin libra's fava_url to http://fava:5000.
|
|
- ./lnbits-entrypoint.sh:/usr/local/bin/lnbits-entrypoint.sh:ro
|
|
command: ["/usr/local/bin/lnbits-entrypoint.sh"]
|
|
|
|
fava:
|
|
hostname: fava
|
|
build: ./fava
|
|
restart: on-failure
|
|
ports:
|
|
- 3333:5000
|
|
volumes:
|
|
- ./data/fava:/bean
|
|
|
|
# Image hosting backend. Mirrors the deploy flake's services.pict-rs
|
|
# (modules/services/pict-rs.nix → port 6033). The container is now
|
|
# internal-only; host traffic goes through the pict-rs-nginx sidecar
|
|
# which adds the CORS layer that production's nginx vhost provides.
|
|
pict-rs:
|
|
image: asonix/pictrs:0.5
|
|
hostname: pict-rs
|
|
restart: on-failure
|
|
user: "0:0"
|
|
environment:
|
|
PICTRS__SERVER__ADDRESS: 0.0.0.0:6033
|
|
expose:
|
|
- "6033"
|
|
volumes:
|
|
- ./data/pict-rs:/mnt
|
|
|
|
# nsecbunkerd — Nostr remote-signing daemon (Pablo's nsecBunker).
|
|
# Phase 2 of aiolabs/lnbits#9: the bunker that will hold every target
|
|
# nsec for lnbits user accounts, with lnbits acting as a NIP-46 client
|
|
# over kind-24133 (signing) + kind-24134 (admin) events.
|
|
#
|
|
# First-pass dev config:
|
|
# - Connects to public relays (damus.io, relay.nsecbunker.com) so
|
|
# it boots without depending on the lnbits nostrrelay extension.
|
|
# The internal-relay channel migration is a follow-up — see
|
|
# ~/dev/lnbits/nsec-bunker-spike-findings.md for the iteration log.
|
|
# - Admin npub from NSECBUNKER_ADMIN_NPUBS env (in .env).
|
|
# - SQLite + JSON config persisted under ./data/nsecbunker; survives
|
|
# `down -v` only if the directory itself isn't wiped.
|
|
# - Memory cap mirrors upstream's docker-compose.yml.
|
|
#
|
|
# Setup the first time only:
|
|
# 1. Set NSECBUNKER_ADMIN_NPUBS in .env (your admin npub, comma-sep
|
|
# for multiple).
|
|
# 2. `mkdir -p ./data/nsecbunker`
|
|
# 3. `docker compose -f docker-compose.dev.yml up -d nsecbunker`
|
|
# 4. `docker compose -f docker-compose.dev.yml logs -f nsecbunker`
|
|
# should show "✅ adminNpubs: npub1..."
|
|
nsecbunker:
|
|
# Builds from the aiolabs/nsecbunkerd fork checkout at
|
|
# ${NSECBUNKER_SRC:-~/dev/nsecbunkerd/dev}. The `dev` worktree
|
|
# carries the #7-diagnosis series (kind-fix, db migration entrypoint,
|
|
# nix flake fixes, transport instrumentation) on top of the master
|
|
# baseline (upstream-rot patches #1-#5, #8). Once dev is merged to
|
|
# master, this default can swap back to `master/`. See aiolabs/
|
|
# nsecbunkerd dev branch and ~/dev/coordination/log.md 2026-05-27.
|
|
build: ${NSECBUNKER_SRC:-/home/padreug/dev/nsecbunkerd/dev}
|
|
hostname: nsecbunker
|
|
# Use `unless-stopped` (not `on-failure`) so the watchdog's deliberate
|
|
# exit(0) on prolonged relay-disconnect gets auto-recovered. The
|
|
# bunker's relay-watchdog is designed to exit-and-let-supervisor-
|
|
# restart; `on-failure` is half-implementing that contract (catches
|
|
# crashes but not the clean exit-as-restart-signal). See the watchdog
|
|
# comment in src/daemon/admin/index.ts and the routine-rebuild
|
|
# gotcha: any restart of the lnbits container that takes >60s knocks
|
|
# the bunker's WebSocket out beyond the grace window, which under
|
|
# `on-failure` left the bunker silently dead after every
|
|
# `regtest-lnbits-rebuild` until manually started.
|
|
restart: unless-stopped
|
|
pids_limit: 100
|
|
mem_limit: 256mb
|
|
memswap_limit: 256mb
|
|
environment:
|
|
DATABASE_URL: "file:/app/config/nsecbunker.db"
|
|
ADMIN_NPUBS: ${NSECBUNKER_ADMIN_NPUBS}
|
|
# Per-relay publish-status diagnostic logging for aiolabs/nsecbunkerd#7.
|
|
# Set to '1' in .env or shell to enable REQUEST_IN / RESPONSE_SENT /
|
|
# PUBLISHED / PUBLISH_FAILED instrumentation. Default off.
|
|
NSEC_BUNKER_DEBUG_TRANSPORT: ${NSEC_BUNKER_DEBUG_TRANSPORT:-0}
|
|
# Disable the relay-connection watchdog in the regtest stack.
|
|
# The watchdog calls process.exit(0) if the relay pool reports
|
|
# no connected relays for >60s; in dev that fires routinely
|
|
# during lnbits rebuilds (regtest-lnbits-rebuild takes more
|
|
# than 60s, the bunker's WebSocket drops, watchdog exits the
|
|
# process). With this set to '1' the bunker idles patiently
|
|
# instead — NDK's relay-reconnect loop attaches once lnbits is
|
|
# back up, and the in-memory unlocked-keys state from autounlock
|
|
# persists across the disconnect. Production deployments should
|
|
# leave this unset so external liveness checking gets the
|
|
# exit-as-restart-signal it expects.
|
|
NSEC_BUNKER_DISABLE_WATCHDOG: "1"
|
|
# Boot-time autounlock per aiolabs/nsecbunkerd#16 — every encrypted
|
|
# key in the Key table is unlocked at startup using this passphrase
|
|
# so RemoteBunkerSigner clients (lnbits, future webapp NIP-46) don't
|
|
# need to drive an admin unlock_key RPC per restart. Same passphrase
|
|
# the operator-side LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE uses to
|
|
# provision keys. See docs/AUTOUNLOCK.md in the bunker repo for the
|
|
# security trade. Leave unset to keep autounlock off (manual unlock
|
|
# per key per restart, the pre-#16 default).
|
|
NSEC_BUNKER_AUTOUNLOCK_PASSPHRASE: ${LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE:-}
|
|
volumes:
|
|
- ./data/nsecbunker:/app/config
|
|
|
|
# Reverse proxy in front of pict-rs. Production runs pict-rs behind
|
|
# an nginx vhost (deploy/server-deploy/modules/services/pict-rs.nix)
|
|
# that adds the CORS headers and OPTIONS preflight handling browsers
|
|
# need for cross-origin uploads from the webapp dev server. Without
|
|
# this, POST /image from http://localhost:5173 gets blocked even
|
|
# though pict-rs itself is healthy. Config mirrors the prod nginx
|
|
# vhost verbatim.
|
|
pict-rs-nginx:
|
|
image: nginx:alpine
|
|
hostname: pict-rs-nginx
|
|
restart: on-failure
|
|
depends_on:
|
|
- pict-rs
|
|
ports:
|
|
- 6033:80
|
|
volumes:
|
|
- ./pict-rs-nginx.conf:/etc/nginx/nginx.conf:ro
|