Skip to content

Own the API password, chain source and liquidity policy - #9

Merged
MattDHill merged 1 commit into
masterfrom
feat/config-actions
Aug 31, 2026
Merged

Own the API password, chain source and liquidity policy#9
MattDHill merged 1 commit into
masterfrom
feat/config-actions

Conversation

@helix-nine

Copy link
Copy Markdown
Contributor

phoenixd shipped with no StartOS-side configuration. The API password was read out of phoenix.conf through the service terminal, the chain source was whatever public Electrum server ACINQ happened to pick, the liquidity ceilings were invisible, and the only health check was "port 9740 is listening".

What changed

phoenix.conf is now a FileHelper.env model. Install seeds http-password so phoenixd never appends its own behind main.ts's reactive read, and the shape's .catchall(z.string()) preserves the limited-access password and webhook secret phoenixd still writes for itself. Every key an action can reach is read with .const() in main.ts, so an edit restarts the daemon rather than taking effect at some unrelated later boot.

Set API Password

Sets and rotates in one handler, and shows the credential once — when it is set. There is deliberately no prefill and no read-back path: a form seeded with the current password is the "show credentials" antipattern wearing a different hat, and the answer to a lost password is to set a new one.

An important task points at it while store.json says none has been set. important rather than critical because critical blocks startup, and a dependent that reads the password off the mounted volume — Alby Hub does — works whether or not the user has set one.

Set Chain Source

A union over ACINQ's public pool, Electrs on this server, Fulcrum on this server, or a host:port typed in. The selection is StartOS state in store.json; init/watchChainSource.ts derives electrum-server from it and setupDependencies declares only the indexer chosen, so a default install declares nothing.

Reaching an on-server indexer took two facts that are not obvious from either side:

  • phoenixd hardcodes TLS.TRUSTED_CERTIFICATES() in its --electrum-server parser and exposes no plaintext or self-signed option, so the plaintext bridge port is unusable and the resolver asks for ssl: true.
  • Its Linux TLS is rustls reading /etc/ssl/certs/ca-certificates.crt, so the trust-startos-ca oneshot installs this server's root CA. StartOS mints the bridge listener's certificate per connection over a hostname set that includes the accepted socket's own address, so the certificate carries the bridge IP in its SANs and then validates.

Only the dependency's internal port and host id are fixed, imported as constants from electrs-startos and fulcrum-startos; the external port is resolved at runtime by getBridgeAddress.

Set Liquidity Policy

Exposes auto-liquidity, max-mining-fee and max-fee-credit — the first time the ceilings on what phoenixd spends buying inbound liquidity from ACINQ are visible anywhere.

Node health check

Reports block height and open channel count over phoenix-cli getinfo, loading until phoenixd has reached a chain source. Zero channels is a success, not a failure: a fresh wallet has none until a Lightning payment arrives.

Verified on a dev box

  • Clean install seeds the API password; phoenixd adds only its own two secrets and never rewrites the file.
  • Rotation is accepted and the old password rejected by the running daemon, node id unchanged; phoenixd's own keys survive the write.
  • Electrs resolves to 10.0.3.1:50001 and Fulcrum to 10.0.3.1:50002 from the same internal-port constant. Both connect over TLS and report a local block height; the dependency appears and clears with the selection.
  • Switching back to the public pool removes electrum-server rather than writing the literal undefined.
  • Upgrading an existing 0.9.0:3 install leaves its password untouched and raises the task.

Notes for review

Two workarounds are tagged for removal rather than left as folklore, both in AGENTS.md:

Separately, setupI18n formats a numeric param through Intl.NumberFormat on a locale derived from LANG, which is C.UTF-8 in a StartOS container and makes Intl throw. The health check's params are stringified to avoid it, but it will bite any package that passes a number to i18n().

🤖 Generated with Claude Code

…3 → 0.9.0:4

phoenixd had no StartOS-side configuration at all. The API password was read
out of phoenix.conf through the service terminal, the chain source was whatever
public Electrum server ACINQ picked, the liquidity ceilings were invisible, and
the only health check was "port 9740 is listening".

phoenix.conf is now a FileHelper.env model. Install seeds http-password so
phoenixd never appends its own behind main.ts's reactive read, and the shape's
catchall preserves the limited-access password and webhook secret phoenixd
still writes for itself. Every key an action can reach is read with .const() in
main.ts, so an edit restarts the daemon rather than waiting for an unrelated
boot.

Three actions:

- Set API Password sets and rotates in one handler and shows the credential
  once. There is no prefill and no read-back path — a form seeded with the
  current password is the "show credentials" antipattern in another shape. An
  important task points at it while store.json says none has been set;
  important rather than critical because a dependent that reads the password
  off the volume works without it.
- Set Chain Source is a union over ACINQ's public pool, Electrs here, Fulcrum
  here, or a typed host:port. The selection is StartOS state in store.json;
  watchChainSource derives electrum-server from it and setupDependencies
  declares only the indexer chosen.
- Set Liquidity Policy exposes auto-liquidity, max-mining-fee and
  max-fee-credit.

Reaching an on-server indexer took two things that are not obvious. phoenixd
hardcodes TLS.TRUSTED_CERTIFICATES() in its --electrum-server parser, so the
plaintext bridge port is unusable and the resolver asks for ssl: true. Its
Linux TLS is rustls reading /etc/ssl/certs/ca-certificates.crt, so the
trust-startos-ca oneshot installs this server's root CA and the per-connection
certificate — minted with the bridge IP in its SANs — then validates.

A node health check reports block height and open channels over phoenix-cli
getinfo, loading until phoenixd has reached a chain source. Its params are
stringified: setupI18n formats a number through Intl, which throws on the
container's C.UTF-8 locale.

Verified on a dev box: clean install seeds the password and phoenixd adds only
its own two secrets; rotation is accepted and the old password rejected by the
running daemon; Electrs resolves to 10.0.3.1:50001 and Fulcrum to
10.0.3.1:50002 from the same internal-port constant, both connecting and
reporting a local block height; switching back to public removes the key rather
than writing the literal "undefined".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MattDHill
MattDHill merged commit ee62161 into master Aug 31, 2026
3 checks passed
@MattDHill
MattDHill deleted the feat/config-actions branch August 31, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants