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:
co-authored by
Claude Fable 5
parent
b7ba35477c
commit
76e6f06995
@@ -64,7 +64,10 @@ app:
|
|||||||
repo: https://github.com/getAlby/hub
|
repo: https://github.com/getAlby/hub
|
||||||
tier: optional
|
tier: optional
|
||||||
launch:
|
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:
|
features:
|
||||||
- Self-custodial Lightning node (LDK) with a friendly wallet UI
|
- Self-custodial Lightning node (LDK) with a friendly wallet UI
|
||||||
- Connect wallets and apps via Nostr Wallet Connect (NWC)
|
- Connect wallets and apps via Nostr Wallet Connect (NWC)
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ app:
|
|||||||
# --http-bind-ip 0.0.0.0, as user "phoenix"; no custom args needed.
|
# --http-bind-ip 0.0.0.0, as user "phoenix"; no custom args needed.
|
||||||
image: source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0
|
image: source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0
|
||||||
pull_policy: if-not-present
|
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:
|
dependencies:
|
||||||
- storage: 500Mi
|
- storage: 500Mi
|
||||||
@@ -41,11 +45,18 @@ app:
|
|||||||
# The wallet seed (seed.dat) and phoenix.conf live here. This directory
|
# The wallet seed (seed.dat) and phoenix.conf live here. This directory
|
||||||
# must survive reinstall/migration like any other app data dir —
|
# must survive reinstall/migration like any other app data dir —
|
||||||
# losing it means losing funds.
|
# 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
|
- type: bind
|
||||||
source: /var/lib/archipelago/phoenixd
|
source: /var/lib/archipelago/phoenixd
|
||||||
target: /phoenix/.phoenix
|
target: /data
|
||||||
options: [rw]
|
options: [rw]
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- PHOENIX_DATADIR=/data
|
||||||
|
|
||||||
health_check:
|
health_check:
|
||||||
type: tcp
|
type: tcp
|
||||||
endpoint: localhost:9740
|
endpoint: localhost:9740
|
||||||
|
|||||||
@@ -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.
|
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
|
### Launch Interfaces
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user