feat: rootless podman, session hardening, boot stability, sidebar fix

Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
  RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
  RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)

Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)

Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready

UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
  after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect

Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 13:53:27 +00:00
co-authored by Claude Opus 4.6
parent 934d120243
commit 870ff095d8
48 changed files with 2979 additions and 2196 deletions
+3 -3
View File
@@ -140,9 +140,9 @@ impl RpcHandler {
}
}
// Fallback: list containers directly via sudo podman (for bundled apps)
let output = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "json"])
// Fallback: list containers directly via podman (for bundled apps)
let output = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "json"])
.output()
.await
.context("Failed to list containers via podman")?;
+80 -80
View File
@@ -45,8 +45,8 @@ impl RpcHandler {
// Dependency checks: verify required services are running before install
let has_lnd;
{
let dep_check = tokio::process::Command::new("sudo")
.args(["podman", "ps", "--format", "{{.Names}}"])
let dep_check = tokio::process::Command::new("podman")
.args(["ps", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to check running containers")?;
@@ -101,8 +101,8 @@ impl RpcHandler {
}
// Check if container already exists
let check_output = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name=^{}$", package_id)])
let check_output = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name=^{}$", package_id)])
.output()
.await
.context("Failed to check existing containers")?;
@@ -117,8 +117,8 @@ impl RpcHandler {
let is_local_image = docker_image.starts_with("localhost/");
let has_local_fallback = if !is_local_image {
let local_tag = format!("localhost/{}:latest", package_id);
let check = tokio::process::Command::new("sudo")
.args(["podman", "images", "-q", &local_tag])
let check = tokio::process::Command::new("podman")
.args(["images", "-q", &local_tag])
.output().await.ok();
check.map_or(false, |o| !String::from_utf8_lossy(&o.stdout).trim().is_empty())
} else { false };
@@ -129,8 +129,8 @@ impl RpcHandler {
self.set_install_progress(package_id, 0, 0).await;
// Stream pull progress via piped stderr
let mut child = tokio::process::Command::new("sudo")
.args(["podman", "pull", docker_image])
let mut child = tokio::process::Command::new("podman")
.args(["pull", docker_image])
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
@@ -165,8 +165,8 @@ impl RpcHandler {
debug!("Using local build for {} (skipping registry pull)", package_id);
} else {
// Verify local image exists
let images_output = tokio::process::Command::new("sudo")
.args(["podman", "images", "-q", docker_image])
let images_output = tokio::process::Command::new("podman")
.args(["images", "-q", docker_image])
.output()
.await
.context("Failed to check local image")?;
@@ -187,7 +187,7 @@ impl RpcHandler {
// Create and start container with security constraints
let mut run_args = vec![
"podman", "run",
"run",
"-d", // Detached
"--name", container_name,
"--restart=unless-stopped", // Auto-restart policy
@@ -252,8 +252,8 @@ impl RpcHandler {
run_args.push("--cap-add=NET_RAW");
run_args.push("--device=/dev/net/tun");
} else if needs_archy_net {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "network", "create", "archy-net"])
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "archy-net"])
.output()
.await;
run_args.push("--network=archy-net");
@@ -367,7 +367,7 @@ printtoconsole=1\n", rpc_pass);
debug!("Running container with args: {:?}", run_args);
// Build command with optional custom command
let mut cmd = tokio::process::Command::new("sudo");
let mut cmd = tokio::process::Command::new("podman");
cmd.args(&run_args);
// Add custom command/args if specified
@@ -397,9 +397,9 @@ printtoconsole=1\n", rpc_pass);
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
for domain_idx in 1..=2u8 {
let value = if domain_idx == 1 { host_ip.as_str() } else { "localhost" };
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "exec", "-u", "33", "nextcloud",
"exec", "-u", "33", "nextcloud",
"php", "occ", "config:system:set",
"trusted_domains", &domain_idx.to_string(),
"--value", value,
@@ -415,17 +415,17 @@ printtoconsole=1\n", rpc_pass);
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
tokio::spawn(async move {
let ui_dir = "/opt/archipelago/docker/bitcoin-ui";
let _ = tokio::process::Command::new("sudo")
.args(["podman", "build", "-t", "localhost/bitcoin-ui", ui_dir])
let _ = tokio::process::Command::new("podman")
.args(["build", "-t", "localhost/bitcoin-ui", ui_dir])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
.args(["podman", "rm", "-f", "bitcoin-ui"])
let _ = tokio::process::Command::new("podman")
.args(["rm", "-f", "bitcoin-ui"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "bitcoin-ui",
"run", "-d", "--name", "bitcoin-ui",
"--restart=unless-stopped",
"-p", "8334:80",
"localhost/bitcoin-ui:latest",
@@ -446,8 +446,8 @@ printtoconsole=1\n", rpc_pass);
/// Install Immich stack (postgres + redis + server)
async fn install_immich_stack(&self) -> Result<serde_json::Value> {
let check = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}"])
let check = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to list containers")?;
@@ -456,12 +456,12 @@ printtoconsole=1\n", rpc_pass);
return Err(anyhow::anyhow!("Immich already installed. Stop and remove it first."));
}
if stdout.contains("immich\n") || stdout.lines().any(|l| l.trim() == "immich") {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "stop", "immich"])
let _ = tokio::process::Command::new("podman")
.args(["stop", "immich"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
.args(["podman", "rm", "-f", "immich"])
let _ = tokio::process::Command::new("podman")
.args(["rm", "-f", "immich"])
.output()
.await;
}
@@ -472,8 +472,8 @@ printtoconsole=1\n", rpc_pass);
"ghcr.io/immich-app/immich-server:release",
];
for img in &images {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "pull", img])
let _ = tokio::process::Command::new("podman")
.args(["pull", img])
.output()
.await;
}
@@ -482,14 +482,14 @@ printtoconsole=1\n", rpc_pass);
.args(["mkdir", "-p", "/var/lib/archipelago/immich", "/var/lib/archipelago/immich-db"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
.args(["podman", "network", "create", "immich-net"])
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "immich-net"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "immich_postgres", "--restart", "unless-stopped",
"run", "-d", "--name", "immich_postgres", "--restart", "unless-stopped",
"--network", "immich-net",
"-v", "/var/lib/archipelago/immich-db:/var/lib/postgresql/data",
"-e", "POSTGRES_PASSWORD=immichpass", "-e", "POSTGRES_USER=postgres", "-e", "POSTGRES_DB=immich",
@@ -499,9 +499,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "immich_redis", "--restart", "unless-stopped",
"run", "-d", "--name", "immich_redis", "--restart", "unless-stopped",
"--network", "immich-net",
"docker.io/valkey/valkey:7-alpine",
])
@@ -509,9 +509,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let run = tokio::process::Command::new("sudo")
let run = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "immich_server", "--restart", "unless-stopped",
"run", "-d", "--name", "immich_server", "--restart", "unless-stopped",
"--network", "immich-net", "-p", "2283:2283",
"-v", "/var/lib/archipelago/immich:/usr/src/app/upload",
"-e", "DB_HOSTNAME=immich_postgres", "-e", "DB_USERNAME=postgres",
@@ -537,8 +537,8 @@ printtoconsole=1\n", rpc_pass);
/// Install Penpot stack (postgres + valkey + backend + exporter + frontend)
async fn install_penpot_stack(&self) -> Result<serde_json::Value> {
let check = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}"])
let check = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to list containers")?;
@@ -555,8 +555,8 @@ printtoconsole=1\n", rpc_pass);
"docker.io/penpotapp/frontend:2.4",
];
for img in &images {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "pull", img])
let _ = tokio::process::Command::new("podman")
.args(["pull", img])
.output()
.await;
}
@@ -565,8 +565,8 @@ printtoconsole=1\n", rpc_pass);
.args(["mkdir", "-p", "/var/lib/archipelago/penpot-assets"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
.args(["podman", "network", "create", "penpot-net"])
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "penpot-net"])
.output()
.await;
@@ -580,9 +580,9 @@ printtoconsole=1\n", rpc_pass);
};
let host_ip = &self.config.host_ip;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "penpot-postgres", "--restart", "unless-stopped",
"run", "-d", "--name", "penpot-postgres", "--restart", "unless-stopped",
"--network", "penpot-net",
"-v", "/var/lib/archipelago/penpot-postgres:/var/lib/postgresql/data",
"-e", "POSTGRES_DB=penpot", "-e", "POSTGRES_USER=penpot", "-e", "POSTGRES_PASSWORD=penpot",
@@ -592,9 +592,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "penpot-valkey", "--restart", "unless-stopped",
"run", "-d", "--name", "penpot-valkey", "--restart", "unless-stopped",
"--network", "penpot-net",
"-e", "VALKEY_EXTRA_FLAGS=--maxmemory 128mb --maxmemory-policy volatile-lfu",
"docker.io/valkey/valkey:8.1",
@@ -603,9 +603,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "penpot-backend", "--restart", "unless-stopped",
"run", "-d", "--name", "penpot-backend", "--restart", "unless-stopped",
"--network", "penpot-net",
"-v", "/var/lib/archipelago/penpot-assets:/opt/data/assets",
"-e", &format!("PENPOT_PUBLIC_URI=http://{}:9001", host_ip),
@@ -622,9 +622,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let _ = tokio::process::Command::new("sudo")
let _ = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "penpot-exporter", "--restart", "unless-stopped",
"run", "-d", "--name", "penpot-exporter", "--restart", "unless-stopped",
"--network", "penpot-net",
"-e", &format!("PENPOT_SECRET_KEY={}", secret),
"-e", "PENPOT_PUBLIC_URI=http://penpot-frontend:8080",
@@ -635,9 +635,9 @@ printtoconsole=1\n", rpc_pass);
.await;
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let run = tokio::process::Command::new("sudo")
let run = tokio::process::Command::new("podman")
.args([
"podman", "run", "-d", "--name", "penpot-frontend", "--restart", "unless-stopped",
"run", "-d", "--name", "penpot-frontend", "--restart", "unless-stopped",
"--network", "penpot-net", "-p", "9001:8080",
"-v", "/var/lib/archipelago/penpot-assets:/opt/data/assets",
"-e", &format!("PENPOT_PUBLIC_URI=http://{}:9001", host_ip),
@@ -687,8 +687,8 @@ printtoconsole=1\n", rpc_pass);
};
for name in to_start {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "start", &name])
let _ = tokio::process::Command::new("podman")
.args(["start", &name])
.output()
.await;
}
@@ -710,16 +710,16 @@ printtoconsole=1\n", rpc_pass);
let containers = get_containers_for_app(package_id).await?;
if containers.is_empty() {
let container_name = format!("archy-{}", package_id);
let _ = tokio::process::Command::new("sudo")
.args(["podman", "stop", &container_name])
let _ = tokio::process::Command::new("podman")
.args(["stop", &container_name])
.output()
.await;
return Ok(serde_json::Value::Null);
}
for name in containers {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "stop", &name])
let _ = tokio::process::Command::new("podman")
.args(["stop", &name])
.output()
.await;
}
@@ -741,16 +741,16 @@ printtoconsole=1\n", rpc_pass);
let containers = get_containers_for_app(package_id).await?;
if containers.is_empty() {
let container_name = format!("archy-{}", package_id);
let _ = tokio::process::Command::new("sudo")
.args(["podman", "restart", &container_name])
let _ = tokio::process::Command::new("podman")
.args(["restart", &container_name])
.output()
.await;
return Ok(serde_json::Value::Null);
}
for name in containers {
let _ = tokio::process::Command::new("sudo")
.args(["podman", "restart", &name])
let _ = tokio::process::Command::new("podman")
.args(["restart", &name])
.output()
.await;
}
@@ -786,8 +786,8 @@ printtoconsole=1\n", rpc_pass);
for name in &containers_to_remove {
tracing::info!("Uninstall {}: stopping container {}", package_id, name);
let stop_out = tokio::process::Command::new("sudo")
.args(["podman", "stop", "-t", "10", name])
let stop_out = tokio::process::Command::new("podman")
.args(["stop", "-t", "10", name])
.output()
.await;
match stop_out {
@@ -802,8 +802,8 @@ printtoconsole=1\n", rpc_pass);
}
tracing::info!("Uninstall {}: removing container {}", package_id, name);
let rm_out = tokio::process::Command::new("sudo")
.args(["podman", "rm", "-f", name])
let rm_out = tokio::process::Command::new("podman")
.args(["rm", "-f", name])
.output()
.await;
match rm_out {
@@ -885,8 +885,8 @@ printtoconsole=1\n", rpc_pass);
.and_then(|v| v.as_array())
.ok_or_else(|| anyhow::anyhow!("Missing volumes"))?;
let check_output = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name={}", app_id)])
let check_output = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}", "--filter", &format!("name={}", app_id)])
.output()
.await
.context("Failed to check container")?;
@@ -894,8 +894,8 @@ printtoconsole=1\n", rpc_pass);
let existing = String::from_utf8_lossy(&check_output.stdout);
if existing.trim().is_empty() {
let mut cmd = tokio::process::Command::new("sudo");
cmd.args(["podman", "run", "-d", "--name", app_id]);
let mut cmd = tokio::process::Command::new("podman");
cmd.args(["run", "-d", "--name", app_id]);
for port in ports {
if let (Some(host), Some(container)) = (
@@ -938,8 +938,8 @@ printtoconsole=1\n", rpc_pass);
return Err(anyhow::anyhow!("Failed to create container: {}", stderr));
}
} else {
let output = tokio::process::Command::new("sudo")
.args(["podman", "start", app_id])
let output = tokio::process::Command::new("podman")
.args(["start", app_id])
.output()
.await
.context("Failed to start container")?;
@@ -965,8 +965,8 @@ printtoconsole=1\n", rpc_pass);
.ok_or_else(|| anyhow::anyhow!("Missing app_id"))?;
validate_app_id(app_id)?;
let output = tokio::process::Command::new("sudo")
.args(["podman", "stop", app_id])
let output = tokio::process::Command::new("podman")
.args(["stop", app_id])
.output()
.await
.context("Failed to stop container")?;
@@ -1110,8 +1110,8 @@ fn parse_size_value(s: &str) -> Option<u64> {
/// Get all container names for an app (handles multi-container apps like mempool)
async fn get_containers_for_app(package_id: &str) -> Result<Vec<String>> {
validate_app_id(package_id)?;
let output = tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "{{.Names}}"])
let output = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to list containers")?;
@@ -1197,8 +1197,8 @@ const TRUSTED_REGISTRIES: &[&str] = &[
/// Returns the container name to use as the RPC host (e.g., "bitcoin-knots").
fn detect_bitcoin_container_name() -> String {
// Synchronous check — called from get_app_config which is sync
let output = std::process::Command::new("sudo")
.args(["podman", "ps", "--format", "{{.Names}}"])
let output = std::process::Command::new("podman")
.args(["ps", "--format", "{{.Names}}"])
.output();
if let Ok(out) = output {
let names = String::from_utf8_lossy(&out.stdout);
+12 -12
View File
@@ -422,11 +422,11 @@ async fn detect_usb_hardware_wallets() -> Result<Vec<serde_json::Value>> {
Ok(devices)
}
/// Prune dangling container images via `sudo podman image prune -f`.
/// Prune dangling container images via `podman image prune -f`.
/// Returns estimated bytes freed.
async fn prune_container_images() -> Result<u64> {
let output = tokio::process::Command::new("sudo")
.args(["podman", "image", "prune", "-f"])
let output = tokio::process::Command::new("podman")
.args(["image", "prune", "-f"])
.output()
.await
.context("Failed to run podman image prune")?;
@@ -444,11 +444,11 @@ async fn prune_container_images() -> Result<u64> {
Ok(pruned_count as u64 * 100_000_000) // rough estimate
}
/// Prune container build cache via `sudo podman system prune -f`.
/// Prune container build cache via `podman system prune -f`.
async fn prune_build_cache() -> Result<u64> {
// Just prune volumes and build cache (not containers or images — those are handled above)
let output = tokio::process::Command::new("sudo")
.args(["podman", "volume", "prune", "-f"])
let output = tokio::process::Command::new("podman")
.args(["volume", "prune", "-f"])
.output()
.await
.context("Failed to run podman volume prune")?;
@@ -625,18 +625,18 @@ impl RpcHandler {
// 2. Remove all container images
tracing::info!("Factory reset: pruning all container images");
let _ = tokio::process::Command::new("sudo")
.args(["-u", "archipelago", "podman", "rmi", "--all", "--force"])
let _ = tokio::process::Command::new("podman")
.args(["rmi", "--all", "--force"])
.output()
.await;
// 3. Prune volumes and build cache
let _ = tokio::process::Command::new("sudo")
.args(["-u", "archipelago", "podman", "volume", "prune", "-f"])
let _ = tokio::process::Command::new("podman")
.args(["volume", "prune", "-f"])
.output()
.await;
let _ = tokio::process::Command::new("sudo")
.args(["-u", "archipelago", "podman", "system", "prune", "-af"])
let _ = tokio::process::Command::new("podman")
.args(["system", "prune", "-af"])
.output()
.await;
+4 -4
View File
@@ -173,8 +173,8 @@ impl RpcHandler {
if !system_ok {
// Fall back to container restart
let container_ok = tokio::process::Command::new("sudo")
.args(["podman", "restart", "archy-tor"])
let container_ok = tokio::process::Command::new("podman")
.args(["restart", "archy-tor"])
.status()
.await
.map(|s| s.success())
@@ -339,8 +339,8 @@ impl RpcHandler {
.unwrap_or(false);
if !system_ok {
let container_ok = tokio::process::Command::new("sudo")
.args(["podman", "restart", "archy-tor"])
let container_ok = tokio::process::Command::new("podman")
.args(["restart", "archy-tor"])
.status()
.await
.map(|s| s.success())
+6 -6
View File
@@ -121,8 +121,8 @@ pub async fn remove_pid_marker(data_dir: &Path) {
pub async fn save_container_snapshot(data_dir: &Path) -> Result<()> {
let output = tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "ps", "--format", "json"])
tokio::process::Command::new("podman")
.args(["ps", "--format", "json"])
.output(),
)
.await
@@ -195,8 +195,8 @@ pub async fn recover_containers(containers: &[RunningContainerRecord]) -> Recove
let result = tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "start", &record.name])
tokio::process::Command::new("podman")
.args(["start", &record.name])
.output(),
)
.await;
@@ -244,8 +244,8 @@ fn is_process_running(pid: u32) -> bool {
pub async fn start_stopped_containers() -> RecoveryReport {
let output = match tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--filter", "status=exited", "--filter", "status=created", "--format", "{{.Names}}"])
tokio::process::Command::new("podman")
.args(["ps", "-a", "--filter", "status=exited", "--filter", "status=created", "--format", "{{.Names}}"])
.output(),
)
.await
+2 -2
View File
@@ -55,8 +55,8 @@ async fn auto_cleanup() -> Result<u64> {
let mut freed: u64 = 0;
// Prune dangling images
let output = tokio::process::Command::new("sudo")
.args(["podman", "image", "prune", "-f"])
let output = tokio::process::Command::new("podman")
.args(["image", "prune", "-f"])
.output()
.await;
if let Ok(out) = output {
+6 -6
View File
@@ -184,8 +184,8 @@ impl MemoryTracker {
async fn check_container_memory() -> HashMap<String, u64> {
let output = match tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "stats", "--no-stream", "--format", "{{.Name}} {{.MemUsage}}"])
tokio::process::Command::new("podman")
.args(["stats", "--no-stream", "--format", "{{.Name}} {{.MemUsage}}"])
.output(),
)
.await
@@ -243,8 +243,8 @@ fn parse_memory_string(s: &str) -> Option<u64> {
async fn check_containers() -> Vec<ContainerHealth> {
let output = match tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "ps", "-a", "--format", "json"])
tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "json"])
.output(),
)
.await
@@ -320,8 +320,8 @@ async fn restart_container(name: &str) -> bool {
info!("Auto-restarting unhealthy container: {}", name);
let result = tokio::time::timeout(
std::time::Duration::from_secs(30),
tokio::process::Command::new("sudo")
.args(["podman", "start", name])
tokio::process::Command::new("podman")
.args(["start", name])
.output(),
)
.await;
+2 -2
View File
@@ -212,8 +212,8 @@ async fn read_network_totals() -> Result<(u64, u64)> {
/// Get per-container resource stats via `podman stats --no-stream --format json`.
async fn read_container_stats() -> Result<Vec<ContainerMetrics>> {
let output = tokio::process::Command::new("sudo")
.args(["podman", "stats", "--no-stream", "--format", "json"])
let output = tokio::process::Command::new("podman")
.args(["stats", "--no-stream", "--format", "json"])
.output()
.await
.context("Failed to run podman stats")?;
+1 -1
View File
@@ -13,7 +13,7 @@ type HmacSha256 = Hmac<Sha256>;
const FULL_SESSION_TTL: u64 = 86400; // 24 hours of inactivity
const PENDING_SESSION_TTL: u64 = 300; // 5 minutes
const MAX_TOTP_ATTEMPTS: u8 = 5;
const MAX_CONCURRENT_SESSIONS: usize = 20;
const MAX_CONCURRENT_SESSIONS: usize = 50;
const SESSIONS_FILE: &str = "/var/lib/archipelago/sessions.json";
const REMEMBER_SECRET_FILE: &str = "/var/lib/archipelago/remember_secret";
pub const REMEMBER_TTL: u64 = 30 * 24 * 3600; // 30 days
+6 -6
View File
@@ -306,8 +306,8 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
pub async fn rolling_container_restart() -> Result<RollingRestartReport> {
use std::process::Command;
let output = Command::new("sudo")
.args(["podman", "ps", "--format", "{{.Names}}"])
let output = Command::new("podman")
.args(["ps", "--format", "{{.Names}}"])
.output()
.context("Failed to list containers")?;
let names: Vec<String> = String::from_utf8_lossy(&output.stdout)
@@ -325,8 +325,8 @@ pub async fn rolling_container_restart() -> Result<RollingRestartReport> {
for name in &names {
debug!(container = %name, "Restarting container");
let restart = Command::new("sudo")
.args(["podman", "restart", "--time", "30", name])
let restart = Command::new("podman")
.args(["restart", "--time", "30", name])
.output();
match restart {
@@ -335,8 +335,8 @@ pub async fn rolling_container_restart() -> Result<RollingRestartReport> {
let mut healthy = false;
for _ in 0..12 {
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let check = Command::new("sudo")
.args(["podman", "inspect", name, "--format", "{{.State.Status}}"])
let check = Command::new("podman")
.args(["inspect", name, "--format", "{{.State.Status}}"])
.output();
if let Ok(out) = check {
let status = String::from_utf8_lossy(&out.stdout).trim().to_string();
+3 -3
View File
@@ -102,9 +102,9 @@ impl PodmanClient {
}
fn podman_async(&self) -> TokioCommand {
// Always use sudo podman to access system-wide containers
let mut cmd = TokioCommand::new("sudo");
cmd.arg("podman");
// Rootless podman: run as the current user (no sudo).
// Requires: loginctl enable-linger <user>, containers migrated to user storage.
let cmd = TokioCommand::new("podman");
cmd
}