feat: IndeedHub multi-container stack installer

Installs all 7 containers (postgres, redis, minio, relay, api,
ffmpeg, frontend) on indeedhub-net with proper env vars and volumes.
Fixes pull timeout to cover stderr reader. Catalog registry set to
23.182.128.160:3000.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-12 10:37:18 -04:00
parent 6ff347a503
commit f1243c62e4
2 changed files with 177 additions and 0 deletions

View File

@ -87,6 +87,9 @@ impl RpcHandler {
if matches!(package_id, "mempool" | "mempool-web") {
return self.install_mempool_stack().await;
}
if package_id == "indeedhub" {
return self.install_indeedhub_stack().await;
}
// Dependency checks
let deps = detect_running_deps().await?;

View File

@ -707,4 +707,178 @@ impl RpcHandler {
"container_id": String::from_utf8_lossy(&run.stdout).trim().to_string(),
}))
}
/// Install the IndeedHub multi-container stack.
pub(super) async fn install_indeedhub_stack(&self) -> Result<serde_json::Value> {
let registry = crate::container::registry::load_registries(&self.config.data_dir)
.await
.unwrap_or_default()
.registries
.into_iter()
.find(|r| r.enabled)
.map(|r| r.url)
.unwrap_or_else(|| "23.182.128.160:3000/lfg2025".to_string());
let user_tmp = format!(
"{}/.local/share/containers/tmp",
std::env::var("HOME").unwrap_or_else(|_| "/home/archipelago".to_string())
);
let _ = std::fs::create_dir_all(&user_tmp);
install_log("INSTALL START: indeedhub stack").await;
info!("Installing IndeedHub stack from {}", registry);
// Pull all images
let images = [
format!("{}/postgres:16.13-alpine", registry),
format!("{}/redis:7.4.8-alpine", registry),
format!("{}/minio:RELEASE.2024-11-07T00-52-20Z", registry),
format!("{}/nostr-rs-relay:0.9.0", registry),
format!("{}/indeedhub-api:1.0.0", registry),
format!("{}/indeedhub-ffmpeg:1.0.0", registry),
format!("{}/indeedhub:1.0.0", registry),
];
for img in &images {
info!("Pulling {}", img);
let status = tokio::process::Command::new("podman")
.args(["pull", img, "--tls-verify=false"])
.env("TMPDIR", &user_tmp)
.status()
.await;
if !status.map(|s| s.success()).unwrap_or(false) {
tracing::warn!("Failed to pull {}", img);
}
}
// Create indeedhub-net
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "indeedhub-net"])
.status()
.await;
// Generate secrets
let db_pass = super::config::read_or_generate_secret("indeedhub-db-password").await;
let jwt_secret = super::config::read_or_generate_secret("indeedhub-jwt").await;
let minio_user = "indeeadmin";
let minio_pass = super::config::read_or_generate_secret("indeedhub-minio-password").await;
// 1. Postgres
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-postgres",
"--network", "indeedhub-net", "--network-alias", "postgres",
"--restart", "unless-stopped",
"-e", &format!("POSTGRES_DB=indeedhub"),
"-e", &format!("POSTGRES_USER=indeedhub"),
"-e", &format!("POSTGRES_PASSWORD={}", db_pass),
"-v", "indeedhub-postgres-data:/var/lib/postgresql/data",
&format!("{}/postgres:16.13-alpine", registry)])
.env("TMPDIR", &user_tmp)
.status().await;
// 2. Redis
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-redis",
"--network", "indeedhub-net", "--network-alias", "redis",
"--restart", "unless-stopped",
"-v", "indeedhub-redis-data:/data",
&format!("{}/redis:7.4.8-alpine", registry)])
.env("TMPDIR", &user_tmp)
.status().await;
// 3. MinIO
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-minio",
"--network", "indeedhub-net", "--network-alias", "minio",
"--restart", "unless-stopped",
"-e", &format!("MINIO_ROOT_USER={}", minio_user),
"-e", &format!("MINIO_ROOT_PASSWORD={}", minio_pass),
"-v", "indeedhub-minio-data:/data",
&format!("{}/minio:RELEASE.2024-11-07T00-52-20Z", registry),
"server", "/data"])
.env("TMPDIR", &user_tmp)
.status().await;
// 4. Nostr relay
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-relay",
"--network", "indeedhub-net", "--network-alias", "relay",
"--restart", "unless-stopped",
"-v", "indeedhub-relay-data:/usr/src/app/db",
&format!("{}/nostr-rs-relay:0.9.0", registry)])
.env("TMPDIR", &user_tmp)
.status().await;
// 5. API
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-api",
"--network", "indeedhub-net", "--network-alias", "api",
"--restart", "unless-stopped",
"-e", "PORT=4000",
"-e", &format!("DATABASE_HOST=postgres"),
"-e", &format!("DATABASE_USER=indeedhub"),
"-e", &format!("DATABASE_PASSWORD={}", db_pass),
"-e", &format!("DATABASE_NAME=indeedhub"),
"-e", &format!("REDIS_HOST=redis"),
"-e", &format!("S3_ENDPOINT=http://minio:9000"),
"-e", &format!("AWS_ACCESS_KEY={}", minio_user),
"-e", &format!("AWS_SECRET_KEY={}", minio_pass),
"-e", &format!("S3_PUBLIC_BUCKET_NAME=indeedhub-public"),
"-e", &format!("S3_PUBLIC_BUCKET_URL=/storage"),
"-e", &format!("NOSTR_JWT_SECRET={}", jwt_secret),
"-e", "ENVIRONMENT=production",
&format!("{}/indeedhub-api:1.0.0", registry)])
.env("TMPDIR", &user_tmp)
.status().await;
// 6. FFmpeg worker
let _ = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub-ffmpeg",
"--network", "indeedhub-net",
"--restart", "unless-stopped",
"-e", &format!("DATABASE_HOST=postgres"),
"-e", &format!("DATABASE_USER=indeedhub"),
"-e", &format!("DATABASE_PASSWORD={}", db_pass),
"-e", &format!("DATABASE_NAME=indeedhub"),
"-e", &format!("QUEUE_HOST=redis"),
"-e", &format!("S3_ENDPOINT=http://minio:9000"),
"-e", &format!("AWS_ACCESS_KEY={}", minio_user),
"-e", &format!("AWS_SECRET_KEY={}", minio_pass),
"-e", &format!("AWS_REGION=us-east-1"),
"-e", &format!("S3_PUBLIC_BUCKET_NAME=indeedhub-public"),
"-e", "ENVIRONMENT=production",
"-e", "AES_MASTER_SECRET=0123456789abcdef0123456789abcdef",
&format!("{}/indeedhub-ffmpeg:1.0.0", registry)])
.env("TMPDIR", &user_tmp)
.status().await;
// Wait for backend services to start
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
// 7. Frontend (nginx)
let run = tokio::process::Command::new("podman")
.args(["run", "-d", "--name", "indeedhub",
"--network", "indeedhub-net",
"--restart", "unless-stopped",
"-p", "7778:7777",
&format!("{}/indeedhub:1.0.0", registry)])
.env("TMPDIR", &user_tmp)
.output()
.await
.context("Failed to create indeedhub container")?;
if !run.status.success() {
let err = String::from_utf8_lossy(&run.stderr);
return Err(anyhow::anyhow!("IndeedHub frontend failed: {}", err));
}
install_log("INSTALL OK: indeedhub stack").await;
info!("IndeedHub stack installed");
Ok(serde_json::json!({
"success": true,
"package_id": "indeedhub",
"message": "IndeedHub stack installed (7 containers)",
}))
}
}