backend: harden rootless app lifecycle orchestration

This commit is contained in:
archipelago
2026-06-11 00:24:32 -04:00
parent 09ec64932f
commit c393b96da3
56 changed files with 7543 additions and 1994 deletions
@@ -26,13 +26,7 @@ impl DockerPackageScanner {
/// Scan Docker containers and convert to package data
pub async fn scan_containers(&self) -> Result<HashMap<String, PackageDataEntry>> {
let containers = match self.runtime.list_containers().await {
Ok(c) => c,
Err(e) => {
debug!("Failed to list containers: {}", e);
return Ok(HashMap::new());
}
};
let containers = self.runtime.list_containers().await?;
debug!("Found {} containers", containers.len());
@@ -63,14 +57,6 @@ impl DockerPackageScanner {
"indeedhub-build_ffmpeg-worker_1",
"netbird-server",
"netbird-dashboard",
"saleor-api",
"saleor-worker",
"saleor-db",
"saleor-cache",
"saleor-jaeger",
"saleor-mailpit",
"saleor-storefront",
"saleor-storefront-app",
"buildx_buildkit_default",
];
@@ -298,7 +284,6 @@ fn get_app_tier(app_id: &str) -> &'static str {
"uptime-kuma" => "recommended",
"grafana" => "recommended",
"searxng" => "recommended",
"saleor" => "recommended",
"tailscale" | "netbird" => "recommended",
"portainer" => "recommended",
// Optional: everything else
@@ -519,13 +504,6 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
repo: "https://github.com/netbirdio/netbird".to_string(),
tier: "",
},
"saleor" => AppMetadata {
title: "Saleor".to_string(),
description: "Composable commerce platform with storefront, dashboard, and GraphQL API. The customer storefront opens on port 9011; admin dashboard is on 9010 with admin@example.com credentials stored on the node.".to_string(),
icon: "/assets/img/app-icons/saleor.svg".to_string(),
repo: "https://github.com/saleor/saleor".to_string(),
tier: "",
},
"gitea" => AppMetadata {
title: "Gitea".to_string(),
description: "Self-hosted Git service with repository and package hosting".to_string(),
@@ -732,20 +710,25 @@ async fn reachable_lan_address(app_id: &str, candidate: Option<String>) -> Optio
let Some(port) = url.rsplit(':').next().and_then(|p| p.parse::<u16>().ok()) else {
return None;
};
match tokio::time::timeout(
std::time::Duration::from_secs(2),
tokio::net::TcpStream::connect(("127.0.0.1", port)),
)
.await
{
Ok(Ok(_)) => Some(url),
_ => {
debug!(app_id = %app_id, port, "suppressing unreachable launch URL");
None
}
if launch_port_reachable(port).await {
Some(url)
} else {
debug!(app_id = %app_id, port, "suppressing unreachable launch URL");
None
}
}
async fn launch_port_reachable(port: u16) -> bool {
matches!(
tokio::time::timeout(
std::time::Duration::from_secs(2),
tokio::net::TcpStream::connect(("127.0.0.1", port)),
)
.await,
Ok(Ok(_))
)
}
fn requires_reachable_launch(app_id: &str) -> bool {
matches!(
app_id,
@@ -766,7 +749,6 @@ fn requires_reachable_launch(app_id: &str) -> bool {
| "tailscale"
| "immich"
| "searxng"
| "saleor"
)
}