Merge remote-tracking branch 'gitea-ai/main' into gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf
This commit is contained in:
@@ -3704,6 +3704,48 @@ app.post('/rpc/v1', (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// Lightning credential rotation (Settings → Lightning credentials).
|
||||
// Stateful so the dev preview can exercise the whole arc without a node:
|
||||
// request a rotation and the steps advance on each poll, ending with the
|
||||
// BTCPay reconnect. Digests only — the real daemon never returns a
|
||||
// macaroon and neither does this.
|
||||
case 'lnd.macaroon-status': {
|
||||
return res.json({
|
||||
result: {
|
||||
installed: true,
|
||||
admin_macaroon_sha256: '52219e90aeba8ac6a98fdac1cc754fe0a2e8407ae6758ffe2cf92039503f5575',
|
||||
issued_at: '2026-08-08 06:03:11',
|
||||
identity_pubkey: '03a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456',
|
||||
channels_open: 5,
|
||||
channels_pending: 1,
|
||||
lnd_error: null,
|
||||
btcpay_uses_internal_lnd: true,
|
||||
// Flip to false to see the "BTCPay is holding an old credential"
|
||||
// warning this feature exists to prevent.
|
||||
btcpay_credential_current: true,
|
||||
rotation: macaroonRotationSnapshot(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case 'lnd.rotate-macaroons': {
|
||||
if (!params?.password) {
|
||||
return res.json({ error: { code: -1, message: 'Node password required to rotate Lightning credentials' } })
|
||||
}
|
||||
if (params.password !== userState.passwordHash && params.password !== MOCK_PASSWORD) {
|
||||
return res.json({ error: { code: -1, message: 'Password verification failed' } })
|
||||
}
|
||||
if (macaroonRotation.running) {
|
||||
return res.json({ error: { code: -1, message: 'A macaroon rotation is already running on this node' } })
|
||||
}
|
||||
startMacaroonRotation()
|
||||
return res.json({ result: { status: 'started' } })
|
||||
}
|
||||
|
||||
case 'lnd.macaroon-rotation-progress': {
|
||||
return res.json({ result: macaroonRotationSnapshot() })
|
||||
}
|
||||
|
||||
case 'lnd.gettransactions': {
|
||||
const pending = walletState.transactions.filter(tx => tx.direction === 'incoming' && tx.num_confirmations < 3).length
|
||||
return res.json({
|
||||
@@ -6075,6 +6117,66 @@ const walletState = sessionBucketProxy('walletState')
|
||||
const userState = sessionBucketProxy('userState')
|
||||
const mockState = sessionBucketProxy('mockState')
|
||||
|
||||
// ── Lightning macaroon rotation (mock) ──────────────────────────────────────
|
||||
// Mirrors the daemon's `RotationProgress`: same step keys and the same five
|
||||
// states, so the Settings section can be driven end-to-end without a node.
|
||||
// Advances one step per poll rather than on a timer, which keeps it
|
||||
// deterministic and makes each intermediate state actually observable.
|
||||
const MACAROON_ROTATION_STEPS = [
|
||||
['preflight', 'Check LND is healthy and record what must survive', '5 channel(s) open, 1 pending — these must be identical afterwards'],
|
||||
['backup', 'Back up the current macaroon material', '8 file(s) copied to /var/lib/archipelago/lnd/macaroon-rotation-20260808T120000Z'],
|
||||
['stop', 'Stop Lightning', null],
|
||||
['remove', 'Remove the old root key and issued macaroons', '8 file(s) removed'],
|
||||
['start', 'Start Lightning and unlock the wallet', 'Lightning is up with freshly minted credentials'],
|
||||
['verify', 'Confirm the node and its channels are unchanged', 'same node, same 5 channel(s)'],
|
||||
['btcpay', 'Reconnect BTCPay Server to the new credentials', 'Connection string updated. BTCPay restarts itself within a minute or two to pick it up.'],
|
||||
]
|
||||
|
||||
const macaroonRotation = { running: false, done: 0, ok: null, startedAt: null, finishedAt: null }
|
||||
|
||||
function startMacaroonRotation() {
|
||||
macaroonRotation.running = true
|
||||
macaroonRotation.done = 0
|
||||
macaroonRotation.ok = null
|
||||
macaroonRotation.startedAt = new Date().toISOString()
|
||||
macaroonRotation.finishedAt = null
|
||||
}
|
||||
|
||||
function macaroonRotationSnapshot() {
|
||||
if (macaroonRotation.running) {
|
||||
macaroonRotation.done += 1
|
||||
if (macaroonRotation.done >= MACAROON_ROTATION_STEPS.length) {
|
||||
macaroonRotation.done = MACAROON_ROTATION_STEPS.length
|
||||
macaroonRotation.running = false
|
||||
macaroonRotation.ok = true
|
||||
macaroonRotation.finishedAt = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
const started = macaroonRotation.startedAt !== null
|
||||
return {
|
||||
running: macaroonRotation.running,
|
||||
ok: macaroonRotation.ok,
|
||||
started_at: macaroonRotation.startedAt,
|
||||
finished_at: macaroonRotation.finishedAt,
|
||||
error: null,
|
||||
steps: MACAROON_ROTATION_STEPS.map(([key, label, detail], i) => {
|
||||
let state = 'pending'
|
||||
if (started) {
|
||||
if (i < macaroonRotation.done) state = 'done'
|
||||
else if (i === macaroonRotation.done && macaroonRotation.running) state = 'running'
|
||||
}
|
||||
return { key, label, state, detail: state === 'done' ? detail : null }
|
||||
}),
|
||||
backup_path: started ? '/var/lib/archipelago/lnd/macaroon-rotation-20260808T120000Z' : null,
|
||||
identity_pubkey: started ? '03a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456' : null,
|
||||
channels_before: started ? 5 : null,
|
||||
channels_after: macaroonRotation.ok ? 5 : null,
|
||||
new_admin_macaroon_sha256: macaroonRotation.ok
|
||||
? '8c19b99de4a8f5c3145d8189500089829174909ca09b48f55e0464239bd8d412'
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
// Seed for the per-session Tor services demo state (tor.list-services /
|
||||
// tor.create-service / tor.delete-service round-trip against this).
|
||||
function defaultTorServices() {
|
||||
|
||||
Reference in New Issue
Block a user