fix: container orchestration stability, AIUI inclusion, lnd-ui port, version 1.3.0
Container stability: - Merge scan results instead of full replacement (prevents UI flapping) - Absence threshold: 3 consecutive missed scans before removing from state - container-list RPC uses cached scanner state for consistency - Increased Podman API timeout 30s → 60s (scanner + health monitor) - Keep crashed containers visible as "exited" instead of podman rm -f - Resolve host-gateway IP via ip route (podman 4.3.x compatibility) ISO build fixes: - AIUI web app inclusion: searches 5 paths + CI step to copy from build server - Claude API proxy: systemctl enable with symlink fallback - AIUI nginx: try_files =404 (was /aiui/index.html redirect loop) - Build version set to 1.3.0 Container fixes: - lnd-ui: nginx listens on 8080 (was 80, Permission denied in rootless) - first-boot: image-versions.sh sourced from correct path with validation - first-boot: host-gateway resolved to actual gateway IP Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
134de9fe3f
commit
c62d7f77b5
@@ -131,7 +131,37 @@ impl RpcHandler {
|
||||
}
|
||||
|
||||
pub(super) async fn handle_container_list(&self) -> Result<serde_json::Value> {
|
||||
// Try to get containers from orchestrator first
|
||||
// Use the scanner's cached state for consistency with WebSocket updates.
|
||||
// This prevents the container-list RPC from returning different results
|
||||
// than the WebSocket-delivered package_data, which caused apps to flicker
|
||||
// between "installed" and "not-installed" in the UI.
|
||||
let (data, _) = self.state_manager.get_snapshot().await;
|
||||
if data.server_info.status_info.containers_scanned && !data.package_data.is_empty() {
|
||||
let containers: Vec<serde_json::Value> = data.package_data.iter().map(|(id, pkg)| {
|
||||
let state = match &pkg.state {
|
||||
crate::data_model::PackageState::Running => "running",
|
||||
crate::data_model::PackageState::Stopped => "stopped",
|
||||
crate::data_model::PackageState::Exited => "exited",
|
||||
crate::data_model::PackageState::Starting => "created",
|
||||
_ => "unknown",
|
||||
};
|
||||
let lan = pkg.installed.as_ref()
|
||||
.and_then(|i| i.interface_addresses.get("main"))
|
||||
.and_then(|a| a.lan_address.as_deref());
|
||||
serde_json::json!({
|
||||
"id": id,
|
||||
"name": id,
|
||||
"state": state,
|
||||
"image": "",
|
||||
"created": "",
|
||||
"ports": [],
|
||||
"lan_address": lan,
|
||||
})
|
||||
}).collect();
|
||||
return Ok(serde_json::json!(containers));
|
||||
}
|
||||
|
||||
// Fallback: scanner hasn't run yet, query podman directly
|
||||
if let Some(orchestrator) = &self.orchestrator {
|
||||
if let Ok(containers) = orchestrator.list_containers().await {
|
||||
if !containers.is_empty() {
|
||||
@@ -140,7 +170,6 @@ impl RpcHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: list containers directly via podman (for bundled apps)
|
||||
let output = tokio::process::Command::new("podman")
|
||||
.args(["ps", "-a", "--format", "json"])
|
||||
.output()
|
||||
@@ -156,11 +185,9 @@ impl RpcHandler {
|
||||
return Ok(serde_json::json!([]));
|
||||
}
|
||||
|
||||
// Parse podman JSON output
|
||||
let podman_containers: Vec<serde_json::Value> = serde_json::from_str(&stdout)
|
||||
.unwrap_or_else(|_| Vec::new());
|
||||
|
||||
// Convert to our ContainerStatus format
|
||||
let containers: Vec<serde_json::Value> = podman_containers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
@@ -173,42 +200,7 @@ impl RpcHandler {
|
||||
"paused" => "paused",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
let name = c.get("Names").and_then(|v| v.as_array()).and_then(|a| a.first()).and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
// Map container name to its UI port (lan_address)
|
||||
let lan_address = match name {
|
||||
"bitcoin-knots" | "bitcoin-ui" => Some("http://localhost:8334"),
|
||||
"lnd" | "archy-lnd-ui" => Some("http://localhost:8081"),
|
||||
"tailscale" => Some("http://localhost:8240"),
|
||||
"homeassistant" => Some("http://localhost:8123"),
|
||||
"archy-mempool-web" | "mempool" => Some("http://localhost:4080"),
|
||||
"btcpay-server" => Some("http://localhost:23000"),
|
||||
"grafana" => Some("http://localhost:3000"),
|
||||
"searxng" => Some("http://localhost:8888"),
|
||||
"ollama" => Some("http://localhost:11434"),
|
||||
"onlyoffice" => Some("http://localhost:9980"),
|
||||
"penpot" => Some("http://localhost:9001"),
|
||||
"nextcloud" => Some("http://localhost:8085"),
|
||||
"vaultwarden" => Some("http://localhost:8082"),
|
||||
"jellyfin" => Some("http://localhost:8096"),
|
||||
"photoprism" => Some("http://localhost:2342"),
|
||||
"immich_server" | "immich" => Some("http://localhost:2283"),
|
||||
"filebrowser" => Some("http://localhost:8083"),
|
||||
"nginx-proxy-manager" => Some("http://localhost:81"),
|
||||
"portainer" => Some("http://localhost:9000"),
|
||||
"uptime-kuma" => Some("http://localhost:3001"),
|
||||
"fedimint" => Some("http://localhost:8175"),
|
||||
"fedimint-gateway" => Some("http://localhost:8176"),
|
||||
"nostr-rs-relay" => Some("http://localhost:18081"),
|
||||
"indeedhub" => Some("http://localhost:7777"),
|
||||
"dwn" => Some("http://localhost:3100"),
|
||||
"endurain" => Some("http://localhost:8080"),
|
||||
"electrs" | "archy-electrs-ui" => Some("http://localhost:50002"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Parse ports from podman JSON (field is "host_port" in snake_case)
|
||||
let ports: Vec<String> = c.get("Ports")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|a| {
|
||||
@@ -220,7 +212,6 @@ impl RpcHandler {
|
||||
}).collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
serde_json::json!({
|
||||
"id": c.get("Id").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
"name": name,
|
||||
@@ -228,7 +219,7 @@ impl RpcHandler {
|
||||
"image": c.get("Image").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
"created": c.get("Created").and_then(|v| v.as_str()).unwrap_or(""),
|
||||
"ports": ports,
|
||||
"lan_address": lan_address,
|
||||
"lan_address": serde_json::Value::Null,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Reference in New Issue
Block a user