Refactor Indeehub integration and enhance deployment documentation

- Updated Indeehub references throughout the codebase, changing the name from "IndeedHub" to "Indeehub" for consistency.
- Implemented a virtual app structure for Indeehub, allowing it to open an external URL without requiring a container.
- Enhanced deployment scripts and documentation to clarify SSH access and password management for Indeehub.
- Improved error handling and retry logic in various components to ensure better user experience during onboarding and app interactions.
- Updated CSS for visual enhancements and added new buttons for improved navigation in the AppLauncherOverlay.
This commit is contained in:
Dorian
2026-03-01 17:53:18 +00:00
parent 2c15311ab6
commit 7a05e11834
34 changed files with 877 additions and 163 deletions
+39 -10
View File
@@ -618,6 +618,11 @@ impl RpcHandler {
return Err(anyhow::anyhow!("Invalid Docker image format"));
}
// Virtual app: Indeehub (no container, opens external URL)
if package_id == "indeedhub" {
return Ok(serde_json::json!({ "success": true }));
}
// Multi-container apps: create full stack
if package_id == "immich" {
return self.install_immich_stack().await;
@@ -637,17 +642,34 @@ impl RpcHandler {
return Err(anyhow::anyhow!("Container {} already exists. Stop and remove it first.", package_id));
}
// Pull the image (with verification in the future)
debug!("Pulling image: {}", docker_image);
let pull_output = tokio::process::Command::new("sudo")
.args(["podman", "pull", docker_image])
.output()
.await
.context("Failed to pull image")?;
// Pull the image (skip for local images - must be built locally first)
let is_local_image = docker_image.starts_with("localhost/");
if !is_local_image {
debug!("Pulling image: {}", docker_image);
let pull_output = tokio::process::Command::new("sudo")
.args(["podman", "pull", docker_image])
.output()
.await
.context("Failed to pull image")?;
if !pull_output.status.success() {
let stderr = String::from_utf8_lossy(&pull_output.stderr);
return Err(anyhow::anyhow!("Failed to pull image: {}", stderr));
if !pull_output.status.success() {
let stderr = String::from_utf8_lossy(&pull_output.stderr);
return Err(anyhow::anyhow!("Failed to pull image: {}", stderr));
}
} else {
// Verify local image exists
let images_output = tokio::process::Command::new("sudo")
.args(["podman", "images", "-q", docker_image])
.output()
.await
.context("Failed to check local image")?;
if String::from_utf8_lossy(&images_output.stdout).trim().is_empty() {
return Err(anyhow::anyhow!(
"Local image {} not found. Run ./deploy-to-archipelago.sh from the Indeehub Prototype project on your Mac—it builds the image on this server and starts the app.",
docker_image
));
}
debug!("Using local image: {}", docker_image);
}
// Create and start container with security constraints
@@ -1708,6 +1730,13 @@ fn get_app_config(
None,
None,
),
"indeedhub" => (
vec!["7777:7777".to_string()],
vec![],
vec!["NGINX_HOST=0.0.0.0".to_string(), "NGINX_PORT=7777".to_string()],
None,
None,
),
_ => (vec![], vec![], vec![], None, None), // No default config, user must configure manually
}
}
@@ -202,6 +202,65 @@ impl DockerPackageScanner {
info!("Detected container: {} ({})", metadata.title, package_state_str(&package_state));
}
// Virtual app: Indeehub (opens external URL, no container required)
if !packages.contains_key("indeedhub") {
let metadata = get_app_metadata("indeedhub");
let lan_address = Some("https://archipelago.indeehub.studio".to_string());
let virtual_pkg = PackageDataEntry {
state: PackageState::Running,
static_files: StaticFiles {
license: "MIT".to_string(),
instructions: metadata.description.clone(),
icon: metadata.icon.clone(),
},
manifest: Manifest {
id: "indeedhub".to_string(),
title: metadata.title.clone(),
version: "0.1.0".to_string(),
description: Description {
short: metadata.description.clone(),
long: metadata.description.clone(),
},
release_notes: "Virtual app (opens archipelago.indeehub.studio)".to_string(),
license: "MIT".to_string(),
wrapper_repo: metadata.repo.clone(),
upstream_repo: metadata.repo.clone(),
support_site: metadata.repo.clone(),
marketing_site: metadata.repo.clone(),
donation_url: None,
author: Some("Indeehub Team".to_string()),
website: lan_address.clone(),
interfaces: Some(Interfaces {
main: Some(MainInterface {
ui: Some("true".to_string()),
tor_config: None,
lan_config: None,
}),
}),
},
installed: Some(InstalledPackageDataEntry {
current_dependents: HashMap::new(),
current_dependencies: HashMap::new(),
last_backup: None,
interface_addresses: {
let mut addresses = HashMap::new();
addresses.insert(
"main".to_string(),
InterfaceAddress {
tor_address: String::new(),
lan_address: lan_address,
},
);
addresses
},
status: ServiceStatus::Running,
}),
install_progress: None,
};
packages.insert("indeedhub".to_string(), virtual_pkg);
info!("Virtual app: Indeehub (archipelago.indeehub.studio)");
}
Ok(packages)
}
}
@@ -360,9 +419,9 @@ fn get_app_metadata(app_id: &str) -> AppMetadata {
repo: "https://github.com/tailscale/tailscale".to_string(),
},
"indeedhub" => AppMetadata {
title: "IndeedHub".to_string(),
title: "Indeehub".to_string(),
description: "Decentralized media streaming platform".to_string(),
icon: "/assets/img/app-icons/indeedhub.png".to_string(),
icon: "https://indeehub.studio/favicon.ico".to_string(),
repo: "https://github.com/indeedhub/indeedhub".to_string(),
},
_ => AppMetadata {