Two bootstrap-correctness fixes on the encrypted Secret store:
- Track nsec ownership with an explicit nsec_state (legacy | encrypted |
cleared) instead of a nsec_managed bool. The bool could not tell "never
migrated" apart from "intentionally cleared" — both leave encrypted_nsec
empty — so a cleared identity could be resurrected on a fresh process from a
stale legacy NSEC (env or old settings blob) and re-derive its npub. Bootstrap
now branches purely on the state: encrypted decrypts (a missing ciphertext is
a fail-fast inconsistency, never a silent fall-through to legacy), cleared
actively empties the live nsec and npub, and legacy imports the plaintext
once.
- Claim a generated admin password atomically. When no password exists, the
generated one is written via a conditional UPDATE (WHERE admin_password_hash
IS NULL) and only the worker that wins the update (rowcount 1) prints it. A
racing worker on a shared database adopts the winner's hash and stays silent,
so the operator never sees a second password that was never stored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two ways a stale legacy NSEC could override or resurrect an nsec the
vault already owns (issue #553):
- initialize() re-applied env/blob values onto live settings after
bootstrap had decrypted the authoritative nsec, so a stale NSEC left
in .env would clobber it on restart (e.g. after rotating the key in
the admin UI). _apply_to_live_settings now never re-applies secret
fields; bootstrap_secrets is their only writer.
- An empty encrypted_nsec could not distinguish "never migrated" from
"intentionally cleared", so clearing the identity via the admin API
and restarting re-imported the old NSEC from env/blob. Record vault
ownership in a new secrets.nsec_managed column (set on legacy import
and on every set_nsec write); bootstrap skips the legacy import once
the vault owns the nsec, so a cleared identity stays cleared.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The add-secrets migration branched off b5e7c9d1f3a2, but the add-slug
migration c6d7e8f9a0b1 has since landed on that same parent, leaving two
Alembic heads. `alembic upgrade head` then refuses to run and the node
fails to boot. Repoint down_revision onto the current head so the chain
is linear again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a singleton Secret model holding the admin password hash and the
Fernet-encrypted nsec, with a hand-written migration for the secrets table.
Add suite-wide pytest config pinning a valid ROUTSTR_SECRET_KEY.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve pay_for_request conflict: keep main's atomic child-key
balance_limit guard and post-rowcount-check ordering, and stamp
reserved_at on both billing and child reservations.
LightningInvoice had no columns for balance_limit, balance_limit_reset,
or validity_date. SQLModel silently dropped these constructor kwargs, so
create_api_key_from_invoice always produced an unconstrained key.
Add the three columns to LightningInvoice with a migration, and wire them
through to the ApiKey in create_api_key_from_invoice, matching the pattern
already used in the child key creation path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Created migration scripts to establish the `upstream_providers` table and integrate it with the `models` table.
- Enhanced the proxy functionality to support dynamic upstream provider initialization and model resolution.
- Implemented API endpoints for managing upstream providers, including CRUD operations and model fetching.
- Refactored pricing calculations to accommodate upstream provider overrides and ensure accurate cost estimation.
- Updated the admin interface to allow for easy management of upstream providers and their associated models.