Own the API password, chain source and liquidity policy - #9
Merged
Conversation
…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
approved these changes
Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
phoenixd shipped with no StartOS-side configuration. The API password was read out of
phoenix.confthrough 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.confis now aFileHelper.envmodel. Install seedshttp-passwordso phoenixd never appends its own behindmain.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()inmain.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
importanttask points at it whilestore.jsonsays none has been set.importantrather thancriticalbecausecriticalblocks 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:porttyped in. The selection is StartOS state instore.json;init/watchChainSource.tsderiveselectrum-serverfrom it andsetupDependenciesdeclares 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:
TLS.TRUSTED_CERTIFICATES()in its--electrum-serverparser and exposes no plaintext or self-signed option, so the plaintext bridge port is unusable and the resolver asks forssl: true./etc/ssl/certs/ca-certificates.crt, so thetrust-startos-caoneshot 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-startosandfulcrum-startos; the external port is resolved at runtime bygetBridgeAddress.Set Liquidity Policy
Exposes
auto-liquidity,max-mining-feeandmax-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,loadinguntil 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
10.0.3.1:50001and Fulcrum to10.0.3.1:50002from the same internal-port constant. Both connect over TLS and report a local block height; the dependency appears and clears with the selection.electrum-serverrather than writing the literalundefined.0.9.0:3install 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:onWritetransformer stripsundefinedvalues, becauseFileHelper.envin start-sdk 2.0.9 writes them as the literal string. fix(start-sdk): drop undefined keys when writing an env file model Start9Labs/start-technologies#3874 fixes it in the SDK; the transformer goes when this package moves to a release carrying it.getRootCainstartos/utils.tsstands in forsdk.getRootCa, which lands in start-sdk 2.0.10.Separately,
setupI18nformats a numeric param throughIntl.NumberFormaton a locale derived fromLANG, which isC.UTF-8in a StartOS container and makesIntlthrow. The health check's params are stringified to avoid it, but it will bite any package that passes a number toi18n().🤖 Generated with Claude Code