fix(phoenixd): dot-free datadir + data_uid; docs: iframe rules

phoenixd: the orchestrator treats bind paths containing a dot as file
mounts and never creates their source dir, so the image's default
/phoenix/.phoenix target crash-looped the unit (statfs: no such file).
Datadir moved to /data via PHOENIX_DATADIR; data_uid 1000:1000 matches the
image's phoenix user — without it phoenixd dies on phoenix.conf
'Permission denied'. Both verified end-to-end on archi-dev-box: orch
install OK, seed.dat + db on the host, authenticated /getinfo answers.

alby-hub: launch flips to embedded — pairs with the gate change that
neutralizes upstream frame blocking.

Dev guide: iframe embedding rules (who blocks framing and why the gate
may strip it; when open_in_new_tab is legitimate; test in the embedded
session, never a tab).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-12 12:38:17 -04:00
co-authored by Claude Fable 5
parent b7ba35477c
commit 76e6f06995
3 changed files with 48 additions and 3 deletions
+4 -1
View File
@@ -64,7 +64,10 @@ app:
repo: https://github.com/getAlby/hub
tier: optional
launch:
open_in_new_tab: true
# Embedded: the gate neutralizes Alby Hub's X-Frame-Options: DENY on
# proxied responses. Nodes older than the gate fix show a blocked
# frame — flip to true only if targeting such nodes.
open_in_new_tab: false
features:
- Self-custodial Lightning node (LDK) with a friendly wallet UI
- Connect wallets and apps via Nostr Wallet Connect (NWC)
+12 -1
View File
@@ -10,6 +10,10 @@ app:
# --http-bind-ip 0.0.0.0, as user "phoenix"; no custom args needed.
image: source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0
pull_policy: if-not-present
# The image runs as user phoenix (1000:1000); the datadir bind source
# must be chowned to that identity or phoenixd dies on
# "Failed to open /data/phoenix.conf with Permission denied".
data_uid: "1000:1000"
dependencies:
- storage: 500Mi
@@ -41,11 +45,18 @@ app:
# The wallet seed (seed.dat) and phoenix.conf live here. This directory
# must survive reinstall/migration like any other app data dir —
# losing it means losing funds.
# Target is /data (via PHOENIX_DATADIR below), NOT the image's default
# /phoenix/.phoenix: the orchestrator treats any bind path containing a
# dot as a file mount and skips creating its source directory, so a
# hidden-dir target never gets its host dir and the unit crash-loops.
- type: bind
source: /var/lib/archipelago/phoenixd
target: /phoenix/.phoenix
target: /data
options: [rw]
environment:
- PHOENIX_DATADIR=/data
health_check:
type: tcp
endpoint: localhost:9740
+32 -1
View File
@@ -130,7 +130,38 @@ app:
Additional extension keys may exist for current integrations, for example Bitcoin, Lightning, or app-specific launch/interface metadata. Treat extension keys as transitional unless they are documented as reusable platform primitives.
Use `metadata.launch.open_in_new_tab: true` when the app UI is known to reject iframe embedding with headers such as `X-Frame-Options` or restrictive CSP. The frontend app-session metadata is generated from this flag during release work.
### Iframe embedding — the rules
The dashboard opens apps in an **embedded frame** (My Apps → app session) by
default. Whether that works is decided by HTTP headers, not by wishes, so
know the mechanics:
- Browsers refuse to render a page in an iframe when the response carries
`X-Frame-Options: DENY`/`SAMEORIGIN` (the dashboard and the app are
different origins — different port at minimum) or a CSP `frame-ancestors`
directive that excludes the dashboard's origin.
- Many upstream apps ship exactly those headers (Alby Hub sends
`X-Frame-Options: DENY`). In a normal deployment that is correct hardening;
behind Archipelago's app gate the clickjacking threat those headers address
is already handled — every proxied request is authenticated by the gate
first.
- Therefore **the gate neutralizes frame blocking on proxied responses**: it
removes `X-Frame-Options` and strips only the `frame-ancestors` directive
from the app's CSP. The rest of the app's CSP (script-src, connect-src, …)
passes through untouched — the gate never weakens the app's own content
policy, only its framing policy. You do not need a bespoke reverse proxy,
header patches, or app config to be embeddable.
Set `metadata.launch.open_in_new_tab: true` only when embedding is broken by
things headers can't fix — the app frame-busts in JavaScript, requires being
the top-level origin (OAuth redirect flows, WebAuthn), or sets
`SameSite=Strict` session cookies that never accompany framed requests. Test
in the real embedded app session, **not** a plain browser tab: tabs don't
enforce framing headers, so a tab proves nothing about the iframe.
(Historical note: before the gate handled this, embeddable-but-blocking apps
each carried a hand-built nginx strip proxy — gitea's port-3000 proxy is the
surviving example. Do not copy that pattern for new apps.)
### Launch Interfaces