Merge remote-tracking branch 'gitea-ai/main' into gsd/phase-13-aiui-functional-conversational-node-control-and-content-surf
This commit is contained in:
@@ -518,8 +518,46 @@ impl RpcHandler {
|
||||
|
||||
// ── The rotation itself ──────────────────────────────────────────────────────
|
||||
|
||||
/// Hold LND's lifecycle lock for the whole rotation, then do the work.
|
||||
///
|
||||
/// Between "stop LND" and "start LND" this owns a stopped container with its
|
||||
/// credential material deleted — the single worst moment for another actor to
|
||||
/// step in. Two would, unasked: the health monitor restarts any container it
|
||||
/// finds stopped, and the reconciler starts one whose unit is enabled. Either
|
||||
/// brings LND back up mid-deletion, and LND re-mints `macaroons.db` on unlock —
|
||||
/// so the deletion loop would race a live process writing that file, or
|
||||
/// "succeed" against material that had already been regenerated, leaving the
|
||||
/// operator told they had rotated while the old root key was still in service.
|
||||
///
|
||||
/// `app_ops::op_lock` is the mechanism both of those actors already consult
|
||||
/// (`lifecycle_op_in_flight`, via `lifecycle_op_covers_container` in the health
|
||||
/// monitor), and it also serialises against the package.start/stop/restart
|
||||
/// workers, so an operator hitting "Restart" on Lightning mid-rotation queues
|
||||
/// instead of interleaving.
|
||||
///
|
||||
/// Chosen over the `user-stopped` marker that `recreate_wallet_destructively`
|
||||
/// uses for its own window: that marker is a file on disk, so a rotation that
|
||||
/// died between marking and clearing would leave Lightning suppressed
|
||||
/// *permanently*, fixable only by finding and editing JSON on the node. This
|
||||
/// guard releases when it drops, on every path including a panic.
|
||||
async fn run_rotation(
|
||||
orchestrator: Option<Arc<dyn crate::container::ContainerOrchestrator>>,
|
||||
) -> Result<()> {
|
||||
let lock = crate::app_ops::op_lock(LND_CONTAINER);
|
||||
// Fail fast rather than queue. This is a button someone just pressed: a
|
||||
// silent wait behind a start/stop/restart that may itself take minutes reads
|
||||
// as "nothing happened", and the honest answer is short.
|
||||
let _guard = lock.try_lock().map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
"another Lightning start/stop/restart is in progress on this node — \
|
||||
wait for it to finish and try again"
|
||||
)
|
||||
})?;
|
||||
rotate_with_lnd_pinned(orchestrator).await
|
||||
}
|
||||
|
||||
async fn rotate_with_lnd_pinned(
|
||||
orchestrator: Option<Arc<dyn crate::container::ContainerOrchestrator>>,
|
||||
) -> Result<()> {
|
||||
// 1. Preflight — establish what must survive, while LND can still be asked.
|
||||
with_progress(|p| p.set("preflight", StepState::Running, None));
|
||||
@@ -584,7 +622,8 @@ async fn run_rotation(
|
||||
);
|
||||
});
|
||||
|
||||
// 3. Stop.
|
||||
// 3. Stop. Nothing may restart LND from here until step 5 — see the lock
|
||||
// `run_rotation` holds around this whole function.
|
||||
with_progress(|p| p.set("stop", StepState::Running, None));
|
||||
stop_lnd().await.context("stopping LND")?;
|
||||
with_progress(|p| p.set("stop", StepState::Done, None));
|
||||
|
||||
Reference in New Issue
Block a user