fix: add persistent container install/start logging

- Install, start, and failure events logged to
  /var/log/archipelago-container-installs.log with timestamps
- Enables post-mortem debugging of container lifecycle issues
- UI container hooks: try registry pull before local build fallback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-31 11:09:49 +01:00
parent 9a556d7819
commit 808480e334
3 changed files with 25 additions and 8 deletions

View File

@ -16,19 +16,18 @@ use tracing::{debug, info, warn};
const INSTALL_LOG: &str = "/var/log/archipelago-container-installs.log"; const INSTALL_LOG: &str = "/var/log/archipelago-container-installs.log";
/// Append a timestamped line to the persistent install log. /// Append a timestamped line to the persistent install log.
async fn install_log(msg: &str) { pub(super) async fn install_log(msg: &str) {
use tokio::io::AsyncWriteExt;
let ts = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"); let ts = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
let line = format!("[{}] {}\n", ts, msg); let line = format!("[{}] {}\n", ts, msg);
let _ = tokio::fs::OpenOptions::new() if let Ok(mut f) = tokio::fs::OpenOptions::new()
.create(true) .create(true)
.append(true) .append(true)
.open(INSTALL_LOG) .open(INSTALL_LOG)
.await .await
.and_then(|mut f| { {
use tokio::io::AsyncWriteExt; let _ = f.write_all(line.as_bytes()).await;
Box::pin(async move { f.write_all(line.as_bytes()).await }) }
})
.await;
} }
impl RpcHandler { impl RpcHandler {
@ -256,6 +255,7 @@ impl RpcHandler {
}; };
run_args.push(&effective_image); run_args.push(&effective_image);
install_log(&format!("INSTALL RUN: {} — podman run {} (image: {})", package_id, container_name, effective_image)).await;
debug!("Running container with args: {:?}", run_args); debug!("Running container with args: {:?}", run_args);
// Build command with optional custom command/args // Build command with optional custom command/args
@ -283,6 +283,7 @@ impl RpcHandler {
let container_id = String::from_utf8_lossy(&run_output.stdout) let container_id = String::from_utf8_lossy(&run_output.stdout)
.trim() .trim()
.to_string(); .to_string();
install_log(&format!("INSTALL CREATED: {} — container_id={}", package_id, &container_id[..12.min(container_id.len())])).await;
// Post-start health verification: wait up to 60s for container to be running // Post-start health verification: wait up to 60s for container to be running
let mut container_running = false; let mut container_running = false;
@ -305,8 +306,13 @@ impl RpcHandler {
.output() .output()
.await; .await;
let log_output = logs let log_output = logs
.map(|o| String::from_utf8_lossy(&o.stderr).to_string()) .map(|o| {
let stdout = String::from_utf8_lossy(&o.stdout);
let stderr = String::from_utf8_lossy(&o.stderr);
format!("{}{}", stdout, stderr)
})
.unwrap_or_default(); .unwrap_or_default();
install_log(&format!("INSTALL CRASH: {} — container exited. Logs:\n{}", package_id, &log_output.chars().take(1000).collect::<String>())).await;
let _ = tokio::process::Command::new("podman") let _ = tokio::process::Command::new("podman")
.args(["rm", "-f", container_name]) .args(["rm", "-f", container_name])
.output() .output()
@ -324,6 +330,7 @@ impl RpcHandler {
} }
if !container_running { if !container_running {
install_log(&format!("INSTALL TIMEOUT: {} — not running after 60s", package_id)).await;
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"Container {} did not reach running state within 60s. Check logs with: podman logs {}", "Container {} did not reach running state within 60s. Check logs with: podman logs {}",
container_name, container_name container_name, container_name

View File

@ -1,5 +1,6 @@
use super::config::{get_containers_for_app, get_data_dirs_for_app, is_valid_docker_image}; use super::config::{get_containers_for_app, get_data_dirs_for_app, is_valid_docker_image};
use super::dependencies::ordered_containers_for_start; use super::dependencies::ordered_containers_for_start;
use super::install::install_log;
use super::validation::validate_app_id; use super::validation::validate_app_id;
use crate::api::rpc::RpcHandler; use crate::api::rpc::RpcHandler;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -45,6 +46,7 @@ impl RpcHandler {
crate::crash_recovery::clear_user_stopped(&self.config.data_dir, name).await; crate::crash_recovery::clear_user_stopped(&self.config.data_dir, name).await;
} }
install_log(&format!("START: {} (containers: {:?})", package_id, to_start)).await;
let mut errors = Vec::new(); let mut errors = Vec::new();
for name in &to_start { for name in &to_start {
tracing::info!("Starting container: {}", name); tracing::info!("Starting container: {}", name);
@ -56,6 +58,7 @@ impl RpcHandler {
if !out.status.success() { if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string(); let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
tracing::error!("Failed to start {}: {}", name, stderr); tracing::error!("Failed to start {}: {}", name, stderr);
install_log(&format!("START FAIL: {}{}", name, stderr)).await;
errors.push(format!("{}: {}", name, stderr)); errors.push(format!("{}: {}", name, stderr));
} }
} }
@ -84,6 +87,7 @@ impl RpcHandler {
return Err(anyhow::anyhow!("No containers found for {}", package_id)); return Err(anyhow::anyhow!("No containers found for {}", package_id));
} }
install_log(&format!("STOP: {} (containers: {:?})", package_id, containers)).await;
// Mark as user-stopped so health monitor and crash recovery don't auto-restart // Mark as user-stopped so health monitor and crash recovery don't auto-restart
crate::crash_recovery::mark_user_stopped(&self.config.data_dir, package_id).await; crate::crash_recovery::mark_user_stopped(&self.config.data_dir, package_id).await;
for name in &containers { for name in &containers {
@ -129,6 +133,7 @@ impl RpcHandler {
return Err(anyhow::anyhow!("No containers found for {}", package_id)); return Err(anyhow::anyhow!("No containers found for {}", package_id));
} }
install_log(&format!("RESTART: {} (containers: {:?})", package_id, containers)).await;
let mut errors = Vec::new(); let mut errors = Vec::new();
for name in &containers { for name in &containers {
tracing::info!("Restarting container: {}", name); tracing::info!("Restarting container: {}", name);

View File

@ -86,5 +86,10 @@ BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:latest"
LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest" LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest"
ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest" ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest"
# Custom UI containers (companion dashboards for headless services)
BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:latest"
LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest"
ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest"
# Base images # Base images
NGINX_ALPINE_IMAGE="$ARCHY_REGISTRY/nginx:1.27.4-alpine" NGINX_ALPINE_IMAGE="$ARCHY_REGISTRY/nginx:1.27.4-alpine"