fix(diagnostics): record refused starts and export maintenance ownership

This commit is contained in:
Дмитрий committed 2026-10-13 06:26:46 +05:00
1 parent d86a1676fe
commit 8269a53db7
3 files changed
+53 -1

No files matched your search

@@ -0,0 +1,7 @@
---
area: manager
type: fixed
audience: admins
title: Diagnostics record blocked startup requests and maintenance owner information
---
A start refused before the launcher runs is now logged with its reason. Exported diagnostics include the last recorded maintenance owner, while clearly distinguishing old metadata from a currently held operating-system lock.
+15
View File
@@ -347,6 +347,15 @@ pub fn export_package(root: &Path, meta_dir: &Path, manager_log: &Path, meta: &I
"wow_client_set": meta.client_path.is_some(),
});
add("summary.json", serde_json::to_string_pretty(&summary)?.as_bytes())?;
let owner = fs::read_to_string(meta_dir.join("update-lock-owner.json")).ok()
.and_then(|text| serde_json::from_str::<serde_json::Value>(&redact_json(&text)).ok());
let maintenance = serde_json::json!({
"lockFilePresent": meta_dir.join("update.lock").is_file(),
"lastRecordedOwner": owner,
"note": "The lock file and owner record may remain after completion. Their presence does not establish whether the OS lock is currently held."
});
add("maintenance.json", serde_json::to_string_pretty(&maintenance)?.as_bytes())?;
// Logs. The newest update logs of the repack's own updater are added under their own names.
let mut logs: Vec<(String, PathBuf)> = vec![
@@ -599,6 +608,8 @@ real error
fs::write(root.join("Core/configs/worldserver.conf"), "LoginDatabaseInfo = \"127.0.0.1;3307;acore;SECRETPW;auth\"\nRate.XP.Kill = 1\n").unwrap();
let meta_dir = d.path().join("srv.manager");
fs::create_dir_all(&meta_dir).unwrap();
fs::write(meta_dir.join("update.lock"), b"").unwrap();
fs::write(meta_dir.join("update-lock-owner.json"), r#"{"operation":"checking updates","pid":1234,"started":"2026-10-13T01:00:00Z","token":"owner-secret"}"#).unwrap();
fs::create_dir_all(meta_dir.join("diagnostics")).unwrap();
fs::write(meta_dir.join("diagnostics/schema-validation-CoA-test.json"), r#"{"problems":[{"table":"creature_display_preset","column":"display_id","detail":"default mismatch"}],"password":"schema-secret"}"#).unwrap();
fs::write(meta_dir.join("diagnostics/database-recovery-test.json"), r#"{"objects":[{"database":"acore_characters","kind":"TRIGGER","name":"user_character_trigger"}],"password":"recovery-secret"}"#).unwrap();
@@ -620,6 +631,10 @@ real error
assert!(!all.contains("schema-secret"));
assert!(all.contains("user_character_trigger") && all.contains("acore_characters"));
assert!(!all.contains("recovery-secret"));
assert!(all.contains("checking updates") && all.contains("1234"));
assert!(all.contains("lockFilePresent") && all.contains("does not establish"));
assert!(!all.contains("owner-secret"));
assert_eq!(fs::read(meta_dir.join("update.lock")).unwrap(), b"");
assert!(!all.contains("hunter2") && !all.contains("SECRETPW") && !all.contains("upstream-secret"));
}
}
+31 -1
View File
@@ -90,7 +90,12 @@ fn launcher(root: &Path) -> Result<(PathBuf, PathBuf)> {
/// Run a launcher verb to completion (blocking; call from a worker thread).
pub fn run(root: &Path, verb: Verb) -> Result<DriverOutcome> {
run_inner(root, verb, false)
tracing::info!(?verb, root = %root.display(), "driver: launcher requested");
let result = run_inner(root, verb, false);
if let Err(error) = &result {
tracing::warn!(?verb, root = %root.display(), %error, "driver: launcher request failed before completion");
}
result
}
pub(crate) fn validate_update(root: &Path) -> Result<DriverOutcome> {
@@ -287,6 +292,31 @@ pub(crate) fn startup_failure(root: &Path, started: &DriverOutcome) -> String {
mod tests {
use super::*;
#[test]
fn a_blocked_start_is_logged_before_any_launcher_is_spawned() {
use std::io::Write;
use std::sync::{Arc, Mutex};
struct Capture(Arc<Mutex<Vec<u8>>>);
impl Write for Capture {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> { self.0.lock().unwrap().extend_from_slice(bytes); Ok(bytes.len()) }
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
}
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("server");
std::fs::create_dir(&root).unwrap();
let metadata = crate::registry::metadata_dir_for(&root).unwrap();
let _maintenance = crate::update::operation_lock(&metadata).unwrap();
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = Arc::clone(&buffer);
let subscriber = tracing_subscriber::fmt().without_time().with_ansi(false).with_writer(move || Capture(Arc::clone(&writer))).finish();
let error = tracing::subscriber::with_default(subscriber, || run(&root, Verb::StartAll)).unwrap_err();
assert!(error.to_string().contains("in progress"));
let text = String::from_utf8(buffer.lock().unwrap().clone()).unwrap();
assert!(text.contains("launcher requested") && text.contains("StartAll"));
assert!(text.contains("launcher request failed before completion") && text.contains("in progress"));
assert!(!text.contains("starting launcher verb"));
}
#[test]
fn background_start_disables_cli_in_template_and_rendered_config() {
let folder = tempfile::tempdir().unwrap();