diff --git a/.gitmodules b/.gitmodules index 755a2411..b79b5f6c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "indeedhub"] path = indeedhub - url = https://git.tx1138.com/lfg2025/indeehub.git + url = http://146.59.87.168:3000/lfg2025/indeehub.git diff --git a/app-catalog/README.md b/app-catalog/README.md index 9e723be2..e3367209 100644 --- a/app-catalog/README.md +++ b/app-catalog/README.md @@ -21,7 +21,7 @@ Add an entry to `catalog.json`: "icon": "/assets/img/app-icons/my-app.svg", "author": "Author", "category": "data", - "dockerImage": "git.tx1138.com/lfg2025/my-app:1.0.0", + "dockerImage": "146.59.87.168:3000/lfg2025/my-app:1.0.0", "repoUrl": "https://github.com/...", "containerConfig": { "ports": ["8080:8080"], diff --git a/app-catalog/catalog.json b/app-catalog/catalog.json index 24b358de..ae5eb198 100644 --- a/app-catalog/catalog.json +++ b/app-catalog/catalog.json @@ -172,7 +172,7 @@ "author": "File Browser", "category": "data", "tier": "core", - "dockerImage": "git.tx1138.com/lfg2025/filebrowser:v2.27.0", + "dockerImage": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0", "repoUrl": "https://github.com/filebrowser/filebrowser", "containerConfig": { "ports": [ @@ -285,7 +285,7 @@ "icon": "/assets/img/app-icons/fedimint.png", "author": "Fedimint", "category": "money", - "dockerImage": "git.tx1138.com/lfg2025/gatewayd:v0.10.0", + "dockerImage": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0", "repoUrl": "https://github.com/fedimint/fedimint", "containerConfig": { "ports": [ diff --git a/apps/btcpay-server/manifest.yml b/apps/btcpay-server/manifest.yml index 4f0d9af0..8f42a95c 100644 --- a/apps/btcpay-server/manifest.yml +++ b/apps/btcpay-server/manifest.yml @@ -13,6 +13,12 @@ app: secret_file: bitcoin-rpc-password - key: BTCPAY_DB_PASS secret_file: btcpay-db-password + # Internal LND node. Generated by the daemon (lnd macaroon as hex + + # tls.cert thumbprint) — see container::lnd::ensure_btcpay_lnd_connection_secret. + # Optional: nodes without LND run btcpay without an internal node. + - key: BTCPAY_BTCLIGHTNING + secret_file: btcpay-lnd-connection + optional: true derived_env: - key: BTCPAY_HOST template: "{{HOST_IP}}:23000" @@ -50,6 +56,10 @@ app: - ASPNETCORE_URLS=http://0.0.0.0:49392 - BTCPAY_PROTOCOL=http - BTCPAY_CHAINS=btc + # Plugins must live on the persistent volume: the image default + # (/root/.btcpayserver/Plugins) is container-local, so every recreate + # silently wiped installed plugins. + - BTCPAY_PLUGINDIR=/datadir/Plugins - BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 - BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 - BTCPAY_BTCRPCUSER=archipelago diff --git a/apps/indeedhub/push-to-registry.sh b/apps/indeedhub/push-to-registry.sh index aa746deb..5818bc0f 100755 --- a/apps/indeedhub/push-to-registry.sh +++ b/apps/indeedhub/push-to-registry.sh @@ -12,7 +12,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FRONTEND_DIR="${INDEEHUB_FRONTEND:-$HOME/Projects/indeehub-frontend}" VERSION="${1:-latest}" -REGISTRY="${REGISTRY:-git.tx1138.com}" +REGISTRY="${REGISTRY:-146.59.87.168:3000}" NAMESPACE="${NAMESPACE:-lfg2025}" IMAGE_NAME="indeedhub" RUNTIME="${RUNTIME:-podman}" diff --git a/core/archipelago/src/api/handler/mod.rs b/core/archipelago/src/api/handler/mod.rs index a86839bd..20d8207f 100644 --- a/core/archipelago/src/api/handler/mod.rs +++ b/core/archipelago/src/api/handler/mod.rs @@ -126,15 +126,15 @@ impl ApiHandler { } /// Server-side fetch of the upstream app catalog so the browser can - /// load it without fighting CORS (git.tx1138.com emits no ACAO) or + /// load it without fighting CORS (upstream Gitea emits no ACAO) or /// CSP (the fallback IP-port URL isn't in `connect-src`). The upstream /// list is derived from the operator's configured container registries /// so switching mirrors in Settings changes the App Store source too — /// each active registry contributes one Gitea `raw/branch/main/catalog.json` /// URL (http or https per `tls_verify`), tried in priority order. - /// If registry config can't be loaded, falls back to the legacy - /// hardcoded pair so the App Store still renders on nodes that haven't - /// persisted a registry config yet. 15s total timeout. + /// If registry config can't be loaded, falls back to the hardcoded OVH + /// URL so the App Store still renders on nodes that haven't persisted + /// a registry config yet. 15s total timeout. async fn handle_app_catalog_proxy(&self) -> Result> { let mut upstreams: Vec = Vec::new(); if let Ok(config) = crate::container::registry::load_registries(&self.config.data_dir).await @@ -155,10 +155,6 @@ impl ApiHandler { "http://146.59.87.168:3000/lfg2025/app-catalog/raw/branch/main/catalog.json" .to_string(), ); - upstreams.push( - "https://git.tx1138.com/lfg2025/app-catalog/raw/branch/main/catalog.json" - .to_string(), - ); } let client = match reqwest::Client::builder() @@ -527,7 +523,7 @@ impl ApiHandler { // App-catalog proxy — fetches catalog.json from the configured // upstream URLs server-side so the browser doesn't hit CORS - // (git.tx1138.com has no ACAO header) or CSP (IP-port upstream + // (upstream Gitea has no ACAO header) or CSP (IP-port upstream // falls outside `connect-src`). Session-authenticated so only // the logged-in node owner can spin up fetches. (Method::GET, "/api/app-catalog") => { diff --git a/core/archipelago/src/container/image_policy.rs b/core/archipelago/src/container/image_policy.rs index e8baf589..ea433d43 100644 --- a/core/archipelago/src/container/image_policy.rs +++ b/core/archipelago/src/container/image_policy.rs @@ -5,11 +5,12 @@ //! 1.8.0 hardening plan). /// Registries images may be pulled from with an explicit host part. +/// (git.tx1138.com was removed 2026-07-10: the host is retired and must +/// never be pulled through again.) pub const TRUSTED_REGISTRIES: &[&str] = &[ "docker.io", "ghcr.io", "localhost", - "git.tx1138.com", "146.59.87.168:3000", ]; @@ -58,13 +59,19 @@ mod tests { "docker.io/library/nginx:1.25", "ghcr.io/owner/app:latest", "localhost/archy-dev:1", - "git.tx1138.com/lfg2025/x:2", "146.59.87.168:3000/archy/bitcoin-knots:28.1", ] { assert!(is_valid_docker_image(img), "{img} should be accepted"); } } + #[test] + fn rejects_retired_tx1138_registry() { + // Retired 2026-07-10 — refs through the dead host must be refused + // at the pull site, not time out against it. + assert!(!is_valid_docker_image("git.tx1138.com/lfg2025/x:2")); + } + #[test] fn accepts_docker_hub_shorthand() { for img in ["nginx", "grafana/grafana:11.2.0", "lightninglabs/lnd:v0.18"] { diff --git a/core/archipelago/src/container/image_versions.rs b/core/archipelago/src/container/image_versions.rs index cf543aed..b52b577b 100644 --- a/core/archipelago/src/container/image_versions.rs +++ b/core/archipelago/src/container/image_versions.rs @@ -234,7 +234,7 @@ pub fn available_update_for_images(pinned: &str, running_image: &str) -> Option< } /// Extract version tag from a full image reference. -/// e.g. "git.tx1138.com/lfg2025/lnd:v0.18.4-beta" → "v0.18.4-beta" +/// e.g. "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta" → "v0.18.4-beta" /// Returns "latest" if no tag or tag is empty. pub fn extract_version_from_image(image: &str) -> String { // Split off the tag after the last colon, but only if it comes after the last slash @@ -328,11 +328,11 @@ mod tests { #[test] fn test_extract_version() { assert_eq!( - extract_version_from_image("git.tx1138.com/lfg2025/lnd:v0.18.4-beta"), + extract_version_from_image("146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta"), "v0.18.4-beta" ); assert_eq!( - extract_version_from_image("git.tx1138.com/lfg2025/grafana:10.2.0"), + extract_version_from_image("146.59.87.168:3000/lfg2025/grafana:10.2.0"), "10.2.0" ); assert_eq!( @@ -340,7 +340,7 @@ mod tests { "latest" ); assert_eq!( - extract_version_from_image("git.tx1138.com/lfg2025/bitcoin-knots:latest"), + extract_version_from_image("146.59.87.168:3000/lfg2025/bitcoin-knots:latest"), "latest" ); } @@ -352,7 +352,7 @@ mod tests { "lfg2025/lnd" ); assert_eq!( - image_without_registry_or_tag("git.tx1138.com/lfg2025/lnd:v0.18.4-beta"), + image_without_registry_or_tag("146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta"), "lfg2025/lnd" ); } @@ -369,7 +369,7 @@ mod tests { assert_eq!( available_update_for_images( "146.59.87.168:3000/lfg2025/nextcloud:29", - "git.tx1138.com/lfg2025/nextcloud:29", + "146.59.87.168:3000/lfg2025/nextcloud:29", ), None ); @@ -389,7 +389,7 @@ mod tests { #[test] fn test_parse_image_versions() { let content = r#" -ARCHY_REGISTRY="git.tx1138.com/lfg2025" +ARCHY_REGISTRY="146.59.87.168:3000/lfg2025" LND_IMAGE="$ARCHY_REGISTRY/lnd:v0.18.4-beta" GRAFANA_IMAGE="$ARCHY_REGISTRY/grafana:10.2.0" # comment @@ -398,11 +398,11 @@ NOT_AN_IMAGE="something" let parsed = parse_image_versions(content); assert_eq!( parsed.get("LND_IMAGE"), - Some(&"git.tx1138.com/lfg2025/lnd:v0.18.4-beta".to_string()) + Some(&"146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta".to_string()) ); assert_eq!( parsed.get("GRAFANA_IMAGE"), - Some(&"git.tx1138.com/lfg2025/grafana:10.2.0".to_string()) + Some(&"146.59.87.168:3000/lfg2025/grafana:10.2.0".to_string()) ); assert!(!parsed.contains_key("NOT_AN_IMAGE")); assert!(!parsed.contains_key("ARCHY_REGISTRY")); diff --git a/core/archipelago/src/container/lnd.rs b/core/archipelago/src/container/lnd.rs index df9ef5f0..8772cdfe 100644 --- a/core/archipelago/src/container/lnd.rs +++ b/core/archipelago/src/container/lnd.rs @@ -670,6 +670,73 @@ fn has_required_lnd_flags(conf: &str, rpc_pass: &str, bitcoin_host: &str) -> boo .all(|needle| conf.lines().any(|line| line.trim() == *needle)) } +/// Secret file consumed by btcpay-server's optional `BTCPAY_BTCLIGHTNING` +/// secret_env (see apps/btcpay-server/manifest.yml). +const BTCPAY_LND_CONNECTION_SECRET: &str = "btcpay-lnd-connection"; + +/// Materialise the BTCPay→internal-LND connection-string secret. +/// +/// LND's datadir is owned by its container subuid (100999 on a stock node), +/// so btcpay cannot bind-mount the macaroon — EACCES across the userns +/// boundary. The connection string therefore carries the macaroon inline as +/// hex, delivered by reference through the podman secret store. +/// +/// No-op when LND isn't provisioned yet (missing tls.cert or macaroon) — +/// btcpay's secret_env entry is `optional`, so it simply starts without an +/// internal Lightning node and picks it up on a later reconcile tick. +/// Rewrites when the pinned cert thumbprint no longer matches (LND TLS cert +/// rotation). Macaroon rotation without cert rotation is not auto-detected +/// (reading the macaroon needs sudo; probing it every tick is not worth the +/// churn) — delete the secret file once to force regeneration. +pub async fn ensure_btcpay_lnd_connection_secret(secrets_dir: &std::path::Path) -> Result<()> { + let cert_path = format!("{DEFAULT_DATA_DIR}/tls.cert"); + let pem = match fs::read_to_string(&cert_path).await { + Ok(s) => s, + Err(_) => return Ok(()), // LND not installed/provisioned yet + }; + let thumbprint = + cert_sha256_thumbprint(&pem).context("computing LND tls.cert thumbprint")?; + + let target = secrets_dir.join(BTCPAY_LND_CONNECTION_SECRET); + // Fast path (no sudo): existing secret already pins the current cert. + if let Ok(existing) = fs::read_to_string(&target).await { + if !existing.trim().is_empty() + && existing.contains(&format!("certthumbprint={thumbprint}")) + { + return Ok(()); + } + } + + let macaroon_path = format!("{DEFAULT_DATA_DIR}/data/chain/bitcoin/mainnet/admin.macaroon"); + if !file_exists_as_root(&macaroon_path).await { + return Ok(()); // wallet not created yet; next tick retries + } + let macaroon = read_file_as_root(&macaroon_path).await?; + let value = format!( + "type=lnd-rest;server=https://lnd:8080/;macaroon={};certthumbprint={}", + hex::encode(macaroon), + thumbprint + ); + crate::container::secrets::write_secret_file(&target, &value) + .context("writing btcpay-lnd-connection secret") +} + +/// SHA256 over the DER certificate body (matches +/// `openssl x509 -fingerprint -sha256` without colons) — the format BTCPay's +/// `certthumbprint=` connection-string parameter expects. +fn cert_sha256_thumbprint(pem: &str) -> Result { + use sha2::{Digest, Sha256}; + let b64: String = pem + .lines() + .filter(|l| !l.starts_with("-----")) + .collect::>() + .join(""); + let der = base64::engine::general_purpose::STANDARD + .decode(b64.trim()) + .context("decoding tls.cert PEM body")?; + Ok(hex::encode_upper(Sha256::digest(&der))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/archipelago/src/container/prod_orchestrator.rs b/core/archipelago/src/container/prod_orchestrator.rs index a04ff1ab..7e3ca2e5 100644 --- a/core/archipelago/src/container/prod_orchestrator.rs +++ b/core/archipelago/src/container/prod_orchestrator.rs @@ -2251,7 +2251,7 @@ impl ProdContainerOrchestrator { // Reconcile recreates route through here too: an unreachable // registry must not brick an app whose exact image:tag is // already in local storage (boot reconcile of archy-btcpay-db - // /archy-nbxplorer with git.tx1138.com dead, 2026-07-10). + // /archy-nbxplorer with the (since-retired) upstream registry dead, 2026-07-10). // Same exists-first semantics as // ensure_resolved_source_available and the quadlets' // Pull=never. @@ -3162,6 +3162,17 @@ impl ProdContainerOrchestrator { .await .context("ensuring bitcoin tx-relay credentials")?; } + if app_id == "btcpay-server" { + // Derived secret (LND macaroon + cert thumbprint), consumed by an + // `optional` secret_env — btcpay must still start when LND is + // absent or the derivation fails, so log-and-continue. + if let Err(e) = + crate::container::lnd::ensure_btcpay_lnd_connection_secret(&self.secrets_dir) + .await + { + tracing::warn!(error = %e, "btcpay-lnd-connection secret not generated; btcpay will run without the internal LND node"); + } + } // Other app secrets (fmcd-password, fedimint-gateway-hash, …) are now // declared as `generated_secrets` in their manifests and materialised // generically in `resolve_dynamic_env` — no per-app code here. @@ -4837,7 +4848,7 @@ app: name: File Browser version: 1.0.0 container: - image: git.tx1138.com/lfg2025/filebrowser:v2.27.0 + image: 146.59.87.168:3000/lfg2025/filebrowser:v2.27.0 custom_args: - --config - /data/.filebrowser.json @@ -4861,7 +4872,7 @@ app: name: LND version: 1.0.0 container: - image: git.tx1138.com/lfg2025/lnd:v0.18.4-beta + image: 146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta secret_env: - key: BITCOIND_RPCPASS secret_file: bitcoin-rpc-password @@ -4948,7 +4959,7 @@ app: async fn install_fresh_skips_pull_when_image_local() { // An unreachable registry must not brick a reconcile recreate whose // exact image:tag is already in local storage (archy-btcpay-db / - // archy-nbxplorer boot reconcile with git.tx1138.com dead, + // archy-nbxplorer boot reconcile with the (since-retired) upstream registry dead, // 2026-07-10). let rt = Arc::new(MockRuntime::default()); rt.mark_image_present("docker.io/bitcoin/knots:28"); diff --git a/core/archipelago/src/container/registry.rs b/core/archipelago/src/container/registry.rs index c413e25f..72d2338c 100644 --- a/core/archipelago/src/container/registry.rs +++ b/core/archipelago/src/container/registry.rs @@ -11,12 +11,17 @@ use tokio::fs; const REGISTRY_FILE: &str = "config/registries.json"; const OVH_REGISTRY_URL: &str = "146.59.87.168:3000/lfg2025"; -const TX1138_REGISTRY_URL: &str = "git.tx1138.com/lfg2025"; +/// Retired registry host (release server retired 2026-06-13; the registry +/// frontend was fully dead by 2026-07-10 — 500 on every /v2 manifest read). +/// Never a default, never force-enabled; stripped from saved configs on +/// load. The literal exists ONLY so the strip can match — nothing may pull +/// through this host. +const RETIRED_TX1138_HOST: &str = "git.tx1138.com"; /// A single container registry. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Registry { - /// Registry URL (e.g., "git.tx1138.com/lfg2025" or "146.59.87.168:3000/lfg2025"). + /// Registry URL (e.g., "146.59.87.168:3000/lfg2025"). pub url: String, /// Human-readable name. pub name: String, @@ -43,22 +48,13 @@ pub struct RegistryConfig { impl Default for RegistryConfig { fn default() -> Self { Self { - registries: vec![ - Registry { - url: OVH_REGISTRY_URL.to_string(), - name: "Server 1 (OVH)".to_string(), - tls_verify: false, - enabled: true, - priority: 0, - }, - Registry { - url: TX1138_REGISTRY_URL.to_string(), - name: "Server 2 (tx1138)".to_string(), - tls_verify: true, - enabled: true, - priority: 10, - }, - ], + registries: vec![Registry { + url: OVH_REGISTRY_URL.to_string(), + name: "Server 1 (OVH)".to_string(), + tls_verify: false, + enabled: true, + priority: 0, + }], } } } @@ -72,7 +68,7 @@ impl RegistryConfig { } /// Rewrite an image reference to use a specific registry. - /// E.g., "git.tx1138.com/lfg2025/bitcoin-knots:latest" with registry "146.59.87.168:3000/lfg2025" + /// E.g., "docker.io/lfg2025/bitcoin-knots:latest" with registry "146.59.87.168:3000/lfg2025" /// becomes "146.59.87.168:3000/lfg2025/bitcoin-knots:latest". pub fn rewrite_image(&self, image: &str, registry: &Registry) -> String { // Extract the image name (last component after the org/namespace) @@ -83,7 +79,7 @@ impl RegistryConfig { } /// Extract the image name from a full image reference. -/// "git.tx1138.com/lfg2025/bitcoin-knots:latest" -> "bitcoin-knots:latest" +/// "146.59.87.168:3000/lfg2025/bitcoin-knots:latest" -> "bitcoin-knots:latest" /// "docker.io/gitea/gitea:1.23" -> "gitea:1.23" fn extract_image_name(image: &str) -> &str { // Split by '/' and take the last segment (image:tag) @@ -118,6 +114,12 @@ pub async fn load_registries(data_dir: &Path) -> Result { config .registries .retain(|r| !r.url.contains("23.182.128.160")); + // Same treatment for the retired tx1138 registry (was Server 2 in older + // defaults): strip it on load so nothing ever pulls through the dead + // host again. + config + .registries + .retain(|r| !r.url.contains(RETIRED_TX1138_HOST)); let mut changed = config.registries.len() != before; // Migrate: any default registry URL that isn't already in the @@ -178,12 +180,6 @@ fn force_ovh_registry_primary(config: &mut RegistryConfig) { registry.enabled = true; registry.priority = 0; } - TX1138_REGISTRY_URL => { - registry.name = "Server 2 (tx1138)".to_string(); - registry.tls_verify = true; - registry.enabled = true; - registry.priority = 10; - } _ => { if registry.priority <= 10 { registry.priority = registry.priority.saturating_add(20); @@ -216,7 +212,7 @@ mod tests { #[test] fn test_extract_image_name() { assert_eq!( - extract_image_name("git.tx1138.com/lfg2025/bitcoin-knots:latest"), + extract_image_name("146.59.87.168:3000/lfg2025/bitcoin-knots:latest"), "bitcoin-knots:latest" ); assert_eq!( @@ -229,11 +225,11 @@ mod tests { #[test] fn test_rewrite_image() { let config = RegistryConfig::default(); - // Default primary is now the OVH VPS (index 0). A tx1138-hardcoded - // image rewrites to OVH when asked for the primary mirror. + // An image hardcoded to some other registry rewrites to OVH when + // asked for the primary mirror. let primary = &config.registries[0]; assert_eq!( - config.rewrite_image("git.tx1138.com/lfg2025/bitcoin-knots:latest", primary), + config.rewrite_image("docker.io/lfg2025/bitcoin-knots:latest", primary), "146.59.87.168:3000/lfg2025/bitcoin-knots:latest" ); } @@ -242,15 +238,51 @@ mod tests { fn test_active_registries_sorted() { let config = RegistryConfig::default(); let active = config.active_registries(); - assert_eq!(active.len(), 2); - assert!(active[0].priority <= active[1].priority); + assert_eq!(active.len(), 1); + assert_eq!(active[0].url, OVH_REGISTRY_URL); } #[tokio::test] async fn test_load_default() { let tmp = TempDir::new().unwrap(); let config = load_registries(tmp.path()).await.unwrap(); - assert_eq!(config.registries.len(), 2); + assert_eq!(config.registries.len(), 1); + } + + #[tokio::test] + async fn test_load_strips_retired_tx1138_registry() { + // Nodes provisioned before the retirement have the tx1138 registry + // baked into their saved config (was Server 2). It must be stripped + // on load and never re-added by the defaults merge. + let tmp = TempDir::new().unwrap(); + let config = RegistryConfig { + registries: vec![ + Registry { + url: format!("{RETIRED_TX1138_HOST}/lfg2025"), + name: "Server 2 (tx1138)".into(), + tls_verify: true, + enabled: true, + priority: 10, + }, + Registry { + url: OVH_REGISTRY_URL.into(), + name: "Server 1 (OVH)".into(), + tls_verify: false, + enabled: true, + priority: 0, + }, + ], + }; + save_registries(tmp.path(), &config).await.unwrap(); + let loaded = load_registries(tmp.path()).await.unwrap(); + assert!( + !loaded + .registries + .iter() + .any(|r| r.url.contains(RETIRED_TX1138_HOST)), + "retired tx1138 registry must be stripped on load; got {:?}", + loaded.registries + ); } #[tokio::test] @@ -266,6 +298,6 @@ mod tests { }); save_registries(tmp.path(), &config).await.unwrap(); let loaded = load_registries(tmp.path()).await.unwrap(); - assert_eq!(loaded.registries.len(), 3); + assert_eq!(loaded.registries.len(), 2); } } diff --git a/core/archipelago/src/container/secrets.rs b/core/archipelago/src/container/secrets.rs index b866d954..74eb72da 100644 --- a/core/archipelago/src/container/secrets.rs +++ b/core/archipelago/src/container/secrets.rs @@ -102,6 +102,17 @@ fn random_base64(bytes: usize) -> String { base64::engine::general_purpose::STANDARD.encode(buf) } +/// Write an externally computed secret value (0600, atomic). For derived +/// secrets that aren't random generators — e.g. the btcpay internal-LND +/// connection string assembled in `container::lnd`. +pub(crate) fn write_secret_file(path: &Path, value: &str) -> Result<()> { + if let Some(dir) = path.parent() { + fs::create_dir_all(dir) + .with_context(|| format!("creating secrets dir {}", dir.display()))?; + } + write_secret(path, value) +} + /// Atomically write a `0600` secret: a temp file in the same dir (so the rename /// is atomic), fsynced, then renamed over the target. fn write_secret(path: &Path, value: &str) -> Result<()> { diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index 10fe1246..b1c9f9e6 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -2132,9 +2132,9 @@ mod tests { fn test_manifest_origin_parses_https() { assert_eq!( manifest_origin( - "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/manifest.json" + "https://releases.example.com/lfg2025/archy/raw/branch/main/releases/manifest.json" ), - Some("https://git.tx1138.com".to_string()) + Some("https://releases.example.com".to_string()) ); } @@ -2151,7 +2151,7 @@ mod tests { #[test] fn test_manifest_origin_rejects_garbage() { assert_eq!(manifest_origin("not a url"), None); - assert_eq!(manifest_origin("ftp://git.tx1138.com/x"), None); + assert_eq!(manifest_origin("ftp://releases.example.com/x"), None); } #[test] @@ -2165,7 +2165,7 @@ mod tests { name: "archipelago".into(), current_version: "1.7.25-alpha".into(), new_version: "1.7.26-alpha".into(), - download_url: "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.26-alpha/archipelago".into(), + download_url: "https://releases.example.com/lfg2025/archy/raw/branch/main/releases/v1.7.26-alpha/archipelago".into(), sha256: "x".into(), size_bytes: 1, blake3: None, @@ -2174,7 +2174,7 @@ mod tests { name: "frontend".into(), current_version: "1.7.25-alpha".into(), new_version: "1.7.26-alpha".into(), - download_url: "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.26-alpha/frontend.tar.gz".into(), + download_url: "https://releases.example.com/lfg2025/archy/raw/branch/main/releases/v1.7.26-alpha/frontend.tar.gz".into(), sha256: "y".into(), size_bytes: 2, blake3: None, @@ -2218,6 +2218,8 @@ mod tests { label: "Server 1 (OVH)".to_string(), }, UpdateMirror { + // Deliberately the retired host: this fixture exists to prove + // load_mirrors strips it. url: "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/manifest.json" .to_string(), label: "Server 2 (tx1138)".to_string(), @@ -2406,7 +2408,7 @@ mod tests { rollback_available: false, schedule: UpdateSchedule::Manual, manifest_mirror: Some( - "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/manifest.json" + "https://releases.example.com/lfg2025/archy/raw/branch/main/releases/manifest.json" .to_string(), ), manifest_signed: false, diff --git a/core/container/src/manifest.rs b/core/container/src/manifest.rs index 85a51cf8..b1720350 100644 --- a/core/container/src/manifest.rs +++ b/core/container/src/manifest.rs @@ -279,6 +279,12 @@ pub struct DerivedEnv { pub struct SecretEnv { pub key: String, pub secret_file: String, + /// When true, a missing/unreadable/empty secret skips this entry instead + /// of failing the whole resolution. For integrations that exist on some + /// nodes only (btcpay's internal-LND connection string: nodes without + /// LND must still run btcpay, just without the internal node). + #[serde(default)] + pub optional: bool, } /// A fully resolved secret env entry, produced at apply time. `value` lives @@ -1353,10 +1359,18 @@ impl ContainerConfig { ) -> Result, ManifestError> { let mut out = Vec::with_capacity(self.secret_env.len()); for e in &self.secret_env { - let v = provider.read(&e.secret_file)?; + let v = match provider.read(&e.secret_file) { + Ok(v) => v, + Err(_) if e.optional => continue, + Err(err) => return Err(err), + }; // An empty secret produces e.g. `-rpcpassword=` and crashes - // the container on auth before logs are useful. Fail loud. + // the container on auth before logs are useful. Fail loud — + // unless the entry is optional, where empty means "not set". if v.trim().is_empty() { + if e.optional { + continue; + } return Err(ManifestError::Invalid(format!( "secret_env {} resolved to empty value (file: {})", e.key, e.secret_file @@ -2034,10 +2048,12 @@ app: SecretEnv { key: "FM_BITCOIND_PASSWORD".to_string(), secret_file: "bitcoin-rpc-password".to_string(), + optional: false, }, SecretEnv { key: "FM_GATEWAY_PASSWORD".to_string(), secret_file: "fedimint-gateway-password".to_string(), + optional: false, }, ], generated_secrets: vec![], @@ -2079,6 +2095,7 @@ app: secret_env: vec![SecretEnv { key: "BITCOIN_RPC_PASS".to_string(), secret_file: "bitcoin-rpc-password".to_string(), + optional: false, }], generated_secrets: vec![], generated_certs: vec![], @@ -2099,6 +2116,51 @@ app: } } + #[test] + fn resolve_secret_env_skips_missing_or_empty_optional_entries() { + let c = ContainerConfig { + image: Some("x:latest".to_string()), + image_signature: None, + pull_policy: "if-not-present".to_string(), + build: None, + network: None, + network_aliases: vec![], + custom_args: vec![], + entrypoint: None, + derived_env: vec![], + secret_env: vec![ + SecretEnv { + key: "REQUIRED".to_string(), + secret_file: "present".to_string(), + optional: false, + }, + SecretEnv { + key: "OPT_MISSING".to_string(), + secret_file: "does-not-exist".to_string(), + optional: true, + }, + SecretEnv { + key: "OPT_EMPTY".to_string(), + secret_file: "empty".to_string(), + optional: true, + }, + ], + generated_secrets: vec![], + generated_certs: vec![], + data_uid: None, + secret_env_refs: vec![], + secret_env_hash: None, + }; + let p = MapSecretsProvider { + data: HashMap::from([ + ("present".to_string(), "value".to_string()), + ("empty".to_string(), " \n".to_string()), + ]), + }; + let out = c.resolve_secret_env(&p).unwrap(); + assert_eq!(out, vec!["REQUIRED=value".to_string()]); + } + #[test] fn unsafe_manifest_values_are_rejected() { let cases = [ diff --git a/core/container/src/podman_client.rs b/core/container/src/podman_client.rs index 0c584e85..d69f5b0f 100644 --- a/core/container/src/podman_client.rs +++ b/core/container/src/podman_client.rs @@ -885,8 +885,9 @@ mod tests { assert!(!image_uses_insecure_registry( "23.182.128.160:3000/lfg2025/filebrowser:v2.27.0" )); + // HTTPS registries never match the insecure list. assert!(!image_uses_insecure_registry( - "git.tx1138.com/lfg2025/bitcoin-knots:latest" + "ghcr.io/lfg2025/bitcoin-knots:latest" )); assert!(!image_uses_insecure_registry( "docker.io/library/nginx:latest" diff --git a/docs/1.8.0-RELEASE-HARDENING-PLAN.md b/docs/1.8.0-RELEASE-HARDENING-PLAN.md index 18c6ba54..e3f39431 100644 --- a/docs/1.8.0-RELEASE-HARDENING-PLAN.md +++ b/docs/1.8.0-RELEASE-HARDENING-PLAN.md @@ -304,7 +304,7 @@ media (latest artifact only one minor behind). - [ ] 🟠 **Sign + checksum the ISO.** Pipeline ends at `xorriso` with no `SHA256SUMS`, no GPG/minisign, no Secure Boot (`BOOTX64.EFI` is unsigned though `grub-efi-amd64-signed` is installed). Emit + sign checksums; wire signed Secure Boot. -- [ ] 🟠 **Registries over HTTPS in the image too** — `146.59.87.168:3000` / `git.tx1138.com` +- [ ] 🟠 **Registries over HTTPS in the image too** — `146.59.87.168:3000` are baked `insecure=true`/`tls_verify:false` (`:216`, `:2308`). (Ties to §A.) - [ ] 🟡 **Add `unattended-upgrades` + a default-deny nftables firewall** (allow 22/80/443 + mesh/WG). Neither exists today; OS packages drift until reflash and there is no host diff --git a/docs/container-architecture.html b/docs/container-architecture.html index 2a0fe468..ce1e6e96 100644 --- a/docs/container-architecture.html +++ b/docs/container-architecture.html @@ -842,7 +842,7 @@

Registry

    -
  • Private registry at git.tx1138.com/lfg2025/
  • +
  • Private registry at 146.59.87.168:3000/lfg2025/
  • HTTPS (self-hosted Gitea)
  • All images pre-pulled into registry; nodes pull on first boot
diff --git a/docs/dht-distribution-design.md b/docs/dht-distribution-design.md index 4746596f..a209c4c7 100644 --- a/docs/dht-distribution-design.md +++ b/docs/dht-distribution-design.md @@ -65,7 +65,7 @@ origin": ### IndeeHub (the streaming target) - Original platform (not a fork). Working source: `~/Projects/Indeedhub Prototype/` - (Vue 3 + NestJS). Submodule `git.tx1138.com/lfg2025/indeehub.git` (host retired — + (Vue 3 + NestJS). Submodule `146.59.87.168:3000/lfg2025/indeehub.git` (repointed off the retired host — needs a live remote). In `archy`: image-only, `apps/indeedhub/manifest.yml` pulls `146.59.87.168:3000/lfg2025/indeedhub:1.0.0` (+ `-api`, `-ffmpeg`, postgres, redis, minio, nostr-rs-relay). diff --git a/image-recipe/_archived/.gitea-workflows/build-iso-dev.yml b/image-recipe/_archived/.gitea-workflows/build-iso-dev.yml index 9fa87689..a05c7cd8 100644 --- a/image-recipe/_archived/.gitea-workflows/build-iso-dev.yml +++ b/image-recipe/_archived/.gitea-workflows/build-iso-dev.yml @@ -112,7 +112,7 @@ jobs: run: | sudo mkdir -p /etc/containers/registries.conf.d echo '[[registry]] - location = "git.tx1138.com" + location = "146.59.87.168:3000" insecure = true' | sudo tee /etc/containers/registries.conf.d/archipelago.conf - name: Build unbundled ISO diff --git a/image-recipe/_archived/build-auto-installer-iso.sh b/image-recipe/_archived/build-auto-installer-iso.sh index 3868dabb..f9b8da41 100755 --- a/image-recipe/_archived/build-auto-installer-iso.sh +++ b/image-recipe/_archived/build-auto-installer-iso.sh @@ -221,7 +221,7 @@ location = "146.59.87.168:3000" insecure = true [[registry]] -location = "git.tx1138.com" +location = "146.59.87.168:3000" insecure = true REGCONF fi @@ -1158,7 +1158,7 @@ fi # If built against a newer GLIBC, the binary will fail at runtime. # Rebuild with: FROM debian:13 AS builder echo " Extracting NostrVPN binary..." -_NVPN_IMG="${NOSTR_VPN_IMAGE:-git.tx1138.com/lfg2025/nostr-vpn:v0.3.7}" +_NVPN_IMG="${NOSTR_VPN_IMAGE:-146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7}" NVPN_IMAGE_ID="$($CONTAINER_CMD images -q "$_NVPN_IMG" 2>/dev/null)" if [ -z "$NVPN_IMAGE_ID" ]; then $CONTAINER_CMD pull "$_NVPN_IMG" 2>/dev/null || true @@ -1189,11 +1189,11 @@ fi # Extract nostr-rs-relay binary from container image (native system service for VPN signaling) echo " Extracting nostr-rs-relay binary..." -RELAY_IMAGE="$($CONTAINER_CMD images -q git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null)" +RELAY_IMAGE="$($CONTAINER_CMD images -q 146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null)" if [ -z "$RELAY_IMAGE" ]; then - $CONTAINER_CMD pull git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null || true + $CONTAINER_CMD pull 146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null || true fi -RELAY_CONTAINER=$($CONTAINER_CMD create git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null) || true +RELAY_CONTAINER=$($CONTAINER_CMD create 146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null) || true if [ -n "$RELAY_CONTAINER" ]; then $CONTAINER_CMD cp "$RELAY_CONTAINER:/usr/local/bin/nostr-rs-relay" "$ARCH_DIR/bin/nostr-rs-relay" 2>/dev/null && \ chmod +x "$ARCH_DIR/bin/nostr-rs-relay" && \ @@ -2319,7 +2319,7 @@ location = "146.59.87.168:3000" insecure = true [[registry]] -location = "git.tx1138.com" +location = "146.59.87.168:3000" insecure = true REGCONF chown -R 1000:1000 /mnt/target/home/archipelago/.config @@ -2330,7 +2330,7 @@ cat > /mnt/target/var/lib/archipelago/config/registries.json <<'DYNREG' { "registries": [ {"url": "146.59.87.168:3000/lfg2025", "name": "Archipelago Primary", "tls_verify": false, "enabled": true, "priority": 0}, - {"url": "git.tx1138.com/lfg2025", "name": "Archipelago Fallback", "tls_verify": true, "enabled": true, "priority": 10} + {"url": "146.59.87.168:3000/lfg2025", "name": "Archipelago Fallback", "tls_verify": true, "enabled": true, "priority": 10} ] } DYNREG @@ -2475,7 +2475,7 @@ if [ -d "$REPO_DIR/.git" ]; then exit 0 # Already cloned fi echo "[update] Cloning Archipelago repo for self-updates..." -su - archipelago -c "git clone https://git.tx1138.com/lfg2025/archy $REPO_DIR" 2>/dev/null || { +su - archipelago -c "git clone https://146.59.87.168:3000/lfg2025/archy $REPO_DIR" 2>/dev/null || { echo "[update] Git clone failed (network?). Updates will retry on next boot." exit 0 } diff --git a/image-recipe/scripts/install-podman.sh b/image-recipe/scripts/install-podman.sh index 0977bd8e..e772a4e0 100755 --- a/image-recipe/scripts/install-podman.sh +++ b/image-recipe/scripts/install-podman.sh @@ -40,10 +40,10 @@ EOF # Configure registries (use Docker Hub and quay.io) mkdir -p /home/archipelago/.config/containers/registries.conf.d cat > /home/archipelago/.config/containers/registries.conf <

Easy sources

-

Use images from Docker Hub, GHCR, git.tx1138.com, the VPS2 Gitea registry, or localhost. Good first candidates: Excalidraw, Stirling PDF, FreshRSS, Wallabag, HedgeDoc, CyberChef, Mealie, or PairDrop.

+

Use images from Docker Hub, GHCR, the VPS2 Gitea registry (146.59.87.168:3000), or localhost. Good first candidates: Excalidraw, Stirling PDF, FreshRSS, Wallabag, HedgeDoc, CyberChef, Mealie, or PairDrop.

diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index 68016e9f..b48d5757 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -24,7 +24,7 @@ const CATALOG_TTL = 60 * 60 * 1000 // 1 hour cache /** Catalog URLs tried in order. First success wins. * Primary is the backend proxy (`/api/app-catalog`) — server-side fetch - * bypasses CORS on git.tx1138.com and CSP restrictions on the IP-port + * bypasses CORS on the upstream Gitea and CSP restrictions on the IP-port * fallback. If the backend is offline (mid-restart etc.) we fall back * to the static copy baked into the frontend build. */ const CATALOG_URLS = [ diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index 165e38c5..d0b2bc61 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -1405,7 +1405,7 @@ init()

Updates survive network hiccups. Downloads now resume from exactly where a dropped connection left off, and retry up to 6 times with increasing gaps between attempts, instead of restarting from byte zero or giving up.

The download progress bar now shows real progress. Instead of a fake number that creeps to 95% and freezes, you see the actual bytes arriving, and it continues to update correctly even if you navigate away and come back.

-

Update check itself retries on slow responses. If git.tx1138.com is momentarily overloaded, the node tries three times with a five-second wait between attempts before concluding you're up to date.

+

Update check itself retries on slow responses. If the release server is momentarily overloaded, the node tries three times with a five-second wait between attempts before concluding you're up to date.

@@ -1591,7 +1591,7 @@ init() Apr 11, 2026
-

Migrated container registry to git.tx1138.com

+

Migrated container registry to the self-hosted Gitea

diff --git a/releases/app-catalog.json b/releases/app-catalog.json index d4a0b8e9..720df197 100644 --- a/releases/app-catalog.json +++ b/releases/app-catalog.json @@ -1,67 +1,70 @@ { + "schema": 1, + "updated": "2026-07-10", "apps": { "adguardhome": { - "image": "146.59.87.168:3000/lfg2025/adguardhome:v0.107.55", - "version": "v0.107.55" + "version": "v0.107.55", + "image": "146.59.87.168:3000/lfg2025/adguardhome:v0.107.55" }, "aiui": { + "version": "0.1.0", "manifest": { "app": { + "id": "aiui", + "name": "AI Assistant", + "version": "0.1.0", + "description": "Conversational AI interface for Archipelago. Quarantined \u2014 communicates only via context broker.", + "internal": true, "container": { "image": "localhost/archipelago-aiui:latest", "pull_policy": "always" }, - "description": "Conversational AI interface for Archipelago. Quarantined — communicates only via context broker.", - "health_check": { - "endpoint": "http://localhost:80", - "interval": "60s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "aiui", - "internal": true, - "name": "AI Assistant", - "ports": [ - { - "bind": "127.0.0.1", - "container": 80, - "host": 5180, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 1, - "disk_limit": "1Gi", - "memory_limit": "512Mi" + "memory_limit": "512Mi", + "disk_limit": "1Gi" }, "security": { - "apparmor_profile": "aiui", "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, "readonly_root": true, + "no_new_privileges": true, + "user": 1000, "seccomp_profile": "default", - "user": 1000 + "network_policy": "isolated", + "apparmor_profile": "aiui" }, - "version": "0.1.0" + "ports": [ + { + "host": 5180, + "container": 80, + "protocol": "tcp", + "bind": "127.0.0.1" + } + ], + "health_check": { + "type": "http", + "endpoint": "http://localhost:80", + "path": "/", + "interval": "60s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "0.1.0" + } }, "archy-btcpay-db": { + "version": "15.17", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "none", - "sync_required": false - }, + "id": "archy-btcpay-db", + "name": "BTCPay Postgres", + "version": "15.17", + "description": "Postgres backend for BTCPay and NBXplorer.", "container": { - "data_uid": "100998:100998", - "image": "git.tx1138.com/lfg2025/postgres:15.17", - "network": "archy-net", + "image": "146.59.87.168:3000/lfg2025/postgres:15.17", "pull_policy": "if-not-present", + "network": "archy-net", + "data_uid": "100998:100998", "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -74,24 +77,9 @@ "storage": "20Gi" } ], - "description": "Postgres backend for BTCPay and NBXplorer.", - "environment": [ - "POSTGRES_DB=btcpay", - "POSTGRES_USER=btcpay" - ], - "health_check": { - "endpoint": "localhost:5432", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "archy-btcpay-db", - "name": "BTCPay Postgres", - "ports": [], "resources": { - "disk_limit": "20Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "20Gi" }, "security": { "capabilities": [ @@ -101,36 +89,51 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "15.17", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/postgres-btcpay", "target": "/var/lib/postgresql/data", - "type": "bind" + "options": [ + "rw" + ] } - ] - } - }, - "version": "15.17" - }, - "archy-mempool-db": { - "manifest": { - "app": { + ], + "environment": [ + "POSTGRES_DB=btcpay", + "POSTGRES_USER=btcpay" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:5432", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, "bitcoin_integration": { "rpc_access": "none", "sync_required": false - }, + } + } + } + }, + "archy-mempool-db": { + "version": "11.4.10", + "manifest": { + "app": { + "id": "archy-mempool-db", + "name": "Mempool MariaDB", + "version": "11.4.10", + "description": "MariaDB backend for the mempool explorer stack.", "container": { - "data_uid": "100998:100998", - "image": "git.tx1138.com/lfg2025/mariadb:11.4.10", - "network": "archy-net", + "image": "146.59.87.168:3000/lfg2025/mariadb:11.4.10", "pull_policy": "if-not-present", + "network": "archy-net", + "data_uid": "100998:100998", "secret_env": [ { "key": "MYSQL_PASSWORD", @@ -147,24 +150,9 @@ "storage": "20Gi" } ], - "description": "MariaDB backend for the mempool explorer stack.", - "environment": [ - "MYSQL_DATABASE=mempool", - "MYSQL_USER=mempool" - ], - "health_check": { - "endpoint": "localhost:3306", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "archy-mempool-db", - "name": "Mempool MariaDB", - "ports": [], "resources": { - "disk_limit": "20Gi", - "memory_limit": "512Mi" + "memory_limit": "512Mi", + "disk_limit": "20Gi" }, "security": { "capabilities": [ @@ -174,89 +162,104 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "11.4.10", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/mysql-mempool", "target": "/var/lib/mysql", - "type": "bind" + "options": [ + "rw" + ] } - ] - } - }, - "version": "11.4.10" - }, - "archy-mempool-web": { - "manifest": { - "app": { + ], + "environment": [ + "MYSQL_DATABASE=mempool", + "MYSQL_USER=mempool" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:3306", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, "bitcoin_integration": { "rpc_access": "none", "sync_required": false - }, + } + } + } + }, + "archy-mempool-web": { + "version": "3.0.1", + "manifest": { + "app": { + "id": "archy-mempool-web", + "name": "Mempool Web", + "version": "3.0.1", + "description": "Frontend web UI for mempool explorer.", + "container_name": "mempool", "container": { "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", - "network": "archy-net", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "archy-net" }, - "container_name": "mempool", "dependencies": [ { "app_id": "mempool-api", "version": ">=3.0.0" } ], - "description": "Frontend web UI for mempool explorer.", - "environment": [ - "FRONTEND_HTTP_PORT=8080", - "BACKEND_MAINNET_HTTP_HOST=mempool-api" - ], - "health_check": { - "endpoint": "http://127.0.0.1:8080", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "archy-mempool-web", - "name": "Mempool Web", - "ports": [ - { - "container": 8080, - "host": 4080, - "protocol": "tcp" - } - ], "resources": { "memory_limit": "512Mi" }, "security": { "capabilities": [], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "3.0.1" + "ports": [ + { + "host": 4080, + "container": 8080, + "protocol": "tcp" + } + ], + "environment": [ + "FRONTEND_HTTP_PORT=8080", + "BACKEND_MAINNET_HTTP_HOST=mempool-api" + ], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:8080", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "none", + "sync_required": false + } } - }, - "version": "3.0.1" + } }, "archy-nbxplorer": { + "version": "2.6.0", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - }, + "id": "archy-nbxplorer", + "name": "NBXplorer", + "version": "2.6.0", + "description": "BTCPay blockchain indexer service.", "container": { - "image": "git.tx1138.com/lfg2025/nbxplorer:2.6.0", - "network": "archy-net", + "image": "146.59.87.168:3000/lfg2025/nbxplorer:2.6.0", "pull_policy": "if-not-present", + "network": "archy-net", "secret_env": [ { "key": "NBXPLORER_BTCRPCPASSWORD", @@ -278,7 +281,32 @@ "version": ">=15.17" } ], - "description": "BTCPay blockchain indexer service.", + "resources": { + "memory_limit": "2Gi", + "disk_limit": "20Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 32838, + "container": 32838, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/nbxplorer", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "NBXPLORER_DATADIR=/data", "NBXPLORER_NETWORK=mainnet", @@ -291,73 +319,46 @@ "NBXPLORER_POSTGRES=Username=btcpay;Password=${BTCPAY_DB_PASS};Host=archy-btcpay-db;Port=5432;Database=nbxplorer" ], "health_check": { + "type": "http", "endpoint": "http://localhost:32838", - "interval": "30s", "path": "/", - "retries": 5, + "interval": "30s", "timeout": "30s", - "type": "http" + "retries": 5 }, - "id": "archy-nbxplorer", - "name": "NBXplorer", - "ports": [ - { - "container": 32838, - "host": 32838, - "protocol": "tcp" - } - ], - "resources": { - "disk_limit": "20Gi", - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "2.6.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/nbxplorer", - "target": "/data", - "type": "bind" - } - ] + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true + } } - }, - "version": "2.6.0" + } }, "bitcoin-core": { + "version": "latest", "manifest": { "app": { - "bitcoin_integration": { - "pruning_support": true, - "rpc_access": "admin", - "sync_required": true, - "testnet_support": false - }, + "id": "bitcoin-core", + "name": "Bitcoin Core", + "version": "28.4.0", + "description": "Reference Bitcoin Core node with dynamic prune/full-mode startup based on host disk.", + "container_name": "bitcoin-core", "container": { + "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", + "pull_policy": "if-not-present", + "network": "archy-net", + "entrypoint": [ + "sh", + "-lc" + ], "custom_args": [ "BITCOIND=\"$(command -v bitcoind || true)\"; if [ -z \"$BITCOIND\" ]; then\n BITCOIND=\"$(find /opt -path '*/bin/bitcoind' -type f 2>/dev/null | sort | tail -n 1)\";\nfi; if [ -z \"$BITCOIND\" ]; then\n echo \"bitcoind not found in image\" >&2;\n exit 127;\nfi; RPC_USER=\"$(printenv BITCOIN_RPC_USER)\"; RPC_PASS=\"$(printenv BITCOIN_RPC_PASS)\"; RPC_TXRELAY_AUTH=\"$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)\"; DISK_GB_VALUE=\"$(printenv DISK_GB || true)\"; RPC_HEADROOM=\"-rpcthreads=16 -rpcworkqueue=256\"; RPC_TXRELAY_FLAGS=\"-rpcwhitelistdefault=0\"; if [ -n \"$RPC_TXRELAY_AUTH\" ]; then\n RPC_TXRELAY_FLAGS=\"$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips\";\nfi; if [ \"${DISK_GB_VALUE:-0}\" -lt 1000 ]; then\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -printtoconsole=0 -server=1 -prune=550 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=1024 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nelse\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -printtoconsole=0 -server=1 -txindex=1 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=4096 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nfi" ], - "data_uid": "100101:100101", "derived_env": [ { "key": "DISK_GB", "template": "{{DISK_GB}}" } ], - "entrypoint": [ - "sh", - "-lc" - ], - "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -367,44 +368,18 @@ "key": "BITCOIN_RPC_TXRELAY_RPCAUTH", "secret_file": "bitcoin-rpc-txrelay-rpcauth" } - ] + ], + "data_uid": "100101:100101" }, - "container_name": "bitcoin-core", "dependencies": [ { "storage": "500Gi" } ], - "description": "Reference Bitcoin Core node with dynamic prune/full-mode startup based on host disk.", - "environment": [ - "BITCOIN_RPC_USER=archipelago" - ], - "health_check": { - "endpoint": "localhost:8332", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "bitcoin-core", - "name": "Bitcoin Core", - "ports": [ - { - "bind": "127.0.0.1", - "container": 8332, - "host": 8332, - "protocol": "tcp" - }, - { - "container": 8333, - "host": 8333, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 0, - "disk_limit": "500Gi", - "memory_limit": "4Gi" + "memory_limit": "4Gi", + "disk_limit": "500Gi" }, "security": { "capabilities": [ @@ -414,93 +389,119 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "28.4.0", + "ports": [ + { + "host": 8332, + "container": 8332, + "protocol": "tcp", + "bind": "127.0.0.1" + }, + { + "host": 8333, + "container": 8333, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/bitcoin", "target": "/home/bitcoin/.bitcoin", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "BITCOIN_RPC_USER=archipelago" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8332", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true, + "testnet_support": false, + "pruning_support": true + } } }, - "version": "latest", "versions": [ { - "default": true, + "version": "latest", "image": "146.59.87.168:3000/lfg2025/bitcoin:latest", - "version": "latest" + "default": true }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:31.0", - "version": "31.0" + "version": "31.0", + "image": "146.59.87.168:3000/lfg2025/bitcoin:31.0" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:30.2", - "version": "30.2" + "version": "30.2", + "image": "146.59.87.168:3000/lfg2025/bitcoin:30.2" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:29.3", - "version": "29.3" + "version": "29.3", + "image": "146.59.87.168:3000/lfg2025/bitcoin:29.3" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:29.2", - "version": "29.2" + "version": "29.2", + "image": "146.59.87.168:3000/lfg2025/bitcoin:29.2" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", - "version": "28.4.0" + "version": "28.4.0", + "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin:27.2", - "version": "27.2" + "version": "27.2", + "image": "146.59.87.168:3000/lfg2025/bitcoin:27.2" }, { - "deprecated": true, + "version": "26.2", "image": "146.59.87.168:3000/lfg2025/bitcoin:26.2", - "version": "26.2" + "deprecated": true }, { - "deprecated": true, + "version": "25.2", "image": "146.59.87.168:3000/lfg2025/bitcoin:25.2", - "version": "25.2" + "deprecated": true } ] }, "bitcoin-knots": { + "version": "latest", "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", "manifest": { "app": { - "bitcoin_integration": { - "pruning_support": true, - "rpc_access": "admin", - "sync_required": true, - "testnet_support": false - }, + "id": "bitcoin-knots", + "name": "Bitcoin Knots", + "version": "28.1.0", + "description": "Full Bitcoin Knots node with dynamic prune/full-mode startup based on host disk.", + "container_name": "bitcoin-knots", "container": { + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", + "pull_policy": "if-not-present", + "network": "archy-net", + "entrypoint": [ + "sh", + "-lc" + ], "custom_args": [ "BITCOIND=\"$(command -v bitcoind || true)\"; if [ -z \"$BITCOIND\" ]; then\n BITCOIND=\"$(find /opt -path '*/bin/bitcoind' -type f 2>/dev/null | sort | tail -n 1)\";\nfi; if [ -z \"$BITCOIND\" ]; then\n echo \"bitcoind not found in image\" >&2;\n exit 127;\nfi; RPC_USER=\"$(printenv BITCOIN_RPC_USER)\"; RPC_PASS=\"$(printenv BITCOIN_RPC_PASS)\"; RPC_TXRELAY_AUTH=\"$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)\"; DISK_GB_VALUE=\"$(printenv DISK_GB || true)\"; RPC_HEADROOM=\"-rpcthreads=16 -rpcworkqueue=256\"; RPC_TXRELAY_FLAGS=\"-rpcwhitelistdefault=0\"; if [ -n \"$RPC_TXRELAY_AUTH\" ]; then\n RPC_TXRELAY_FLAGS=\"$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips\";\nfi; if [ \"${DISK_GB_VALUE:-0}\" -lt 1000 ]; then\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -printtoconsole=0 -server=1 -prune=550 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=2048 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nelse\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -printtoconsole=0 -server=1 -txindex=1 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=4096 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nfi" ], - "data_uid": "100101:100101", "derived_env": [ { "key": "DISK_GB", "template": "{{DISK_GB}}" } ], - "entrypoint": [ - "sh", - "-lc" - ], - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -510,44 +511,18 @@ "key": "BITCOIN_RPC_TXRELAY_RPCAUTH", "secret_file": "bitcoin-rpc-txrelay-rpcauth" } - ] + ], + "data_uid": "100101:100101" }, - "container_name": "bitcoin-knots", "dependencies": [ { "storage": "500Gi" } ], - "description": "Full Bitcoin Knots node with dynamic prune/full-mode startup based on host disk.", - "environment": [ - "BITCOIN_RPC_USER=archipelago" - ], - "health_check": { - "endpoint": "localhost:8332", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "bitcoin-knots", - "name": "Bitcoin Knots", - "ports": [ - { - "bind": "127.0.0.1", - "container": 8332, - "host": 8332, - "protocol": "tcp" - }, - { - "container": 8333, - "host": 8333, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 0, - "disk_limit": "500Gi", - "memory_limit": "8Gi" + "memory_limit": "8Gi", + "disk_limit": "500Gi" }, "security": { "capabilities": [ @@ -557,51 +532,83 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "28.1.0", + "ports": [ + { + "host": 8332, + "container": 8332, + "protocol": "tcp", + "bind": "127.0.0.1" + }, + { + "host": 8333, + "container": 8333, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/bitcoin", "target": "/home/bitcoin/.bitcoin", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "BITCOIN_RPC_USER=archipelago" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8332", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true, + "testnet_support": false, + "pruning_support": true + } } }, - "version": "latest", "versions": [ { - "default": true, + "version": "latest", "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508", - "version": "latest" + "default": true }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508", - "version": "29.3.knots20260508" + "version": "29.3.knots20260508", + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260507", - "version": "29.3.knots20260507" + "version": "29.3.knots20260507", + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260507" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210", - "version": "29.3.knots20260210" + "version": "29.3.knots20260210", + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210" }, { - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.2.knots20251110", - "version": "29.2.knots20251110" + "version": "29.2.knots20251110", + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.2.knots20251110" } ] }, "bitcoin-ui": { + "version": "1.7.84-alpha", "image": "146.59.87.168:3000/lfg2025/bitcoin-ui:1.7.84-alpha", "manifest": { "app": { + "id": "bitcoin-ui", + "name": "Bitcoin UI", + "version": "1.0.0", + "description": "Archipelago-native HTTP proxy + static site for interacting with the\nBitcoin Core / Bitcoin Knots JSON-RPC. Runs nginx inside a container\nand reverse-proxies /bitcoin-rpc/ to 127.0.0.1:8332 on the host. The\nupstream Authorization header is substituted from\n/var/lib/archipelago/secrets/bitcoin-rpc-password by the prod\norchestrator's pre-start hook, rendered into an nginx.conf that is\nbind-mounted read-only at container start.\n", "container": { "build": { "context": "/opt/archipelago/docker/bitcoin-ui", @@ -614,44 +621,44 @@ "app_id": "bitcoin-core" } ], - "description": "Archipelago-native HTTP proxy + static site for interacting with the\nBitcoin Core / Bitcoin Knots JSON-RPC. Runs nginx inside a container\nand reverse-proxies /bitcoin-rpc/ to 127.0.0.1:8332 on the host. The\nupstream Authorization header is substituted from\n/var/lib/archipelago/secrets/bitcoin-rpc-password by the prod\norchestrator's pre-start hook, rendered into an nginx.conf that is\nbind-mounted read-only at container start.\n", - "environment": [], - "health_check": { - "endpoint": "http://127.0.0.1:8334", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "bitcoin-ui", - "name": "Bitcoin UI", - "ports": [], "resources": { "memory_limit": "128Mi" }, "security": { - "network_policy": "host", - "readonly_root": false + "readonly_root": false, + "network_policy": "host" }, - "version": "1.0.0", + "ports": [], "volumes": [ { - "options": [ - "ro" - ], + "type": "bind", "source": "/var/lib/archipelago/bitcoin-ui/nginx.conf", "target": "/etc/nginx/conf.d/default.conf", - "type": "bind" + "options": [ + "ro" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:8334", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "1.7.84-alpha" + } }, "botfights": { + "version": "1.1.0", "manifest": { "app": { + "id": "botfights", + "name": "BotFights", + "version": "1.1.0", + "description": "Bot competition arena with 2-player arcade fighting mode. AI bots battle in trivia challenges while humans duke it out with controllers. Built for Bitcoiners.", "category": "community", "container": { "image": "146.59.87.168:3000/lfg2025/botfights:1.1.0", @@ -662,35 +669,71 @@ "storage": "500Mi" } ], - "description": "Bot competition arena with 2-player arcade fighting mode. AI bots battle in trivia challenges while humans duke it out with controllers. Built for Bitcoiners.", + "resources": { + "cpu_limit": 2, + "memory_limit": "512Mi", + "disk_limit": "500Mi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1001, + "seccomp_profile": "default", + "network_policy": "bridge", + "apparmor_profile": "default" + }, + "ports": [ + { + "host": 9100, + "container": 9100, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "botfights-data", + "target": "/app/server/data" + }, + { + "type": "tmpfs", + "target": "/tmp", + "options": [ + "rw", + "noexec", + "nosuid", + "size=64m" + ] + } + ], "environment": [ "NODE_ENV=production" ], "health_check": { + "type": "http", "endpoint": "http://localhost:9100", - "interval": "30s", "path": "/api/health", - "retries": 3, - "start_period": "30s", + "interval": "30s", "timeout": "10s", - "type": "http" + "retries": 3, + "start_period": "30s" }, - "id": "botfights", "interfaces": { "main": { - "description": "Bot arena and arcade fighter with controller support", "name": "Web UI", - "path": "/", + "description": "Bot arena and arcade fighter with controller support", + "type": "ui", "port": 9100, "protocol": "http", - "type": "ui" + "path": "/" } }, "metadata": { "author": "Dorian", + "repo": "https://botfights.net", "icon": "/assets/img/app-icons/botfights.svg", "license": "MIT", - "repo": "https://botfights.net", "tags": [ "bitcoin", "gaming", @@ -700,77 +743,31 @@ "competition", "controller" ] - }, - "name": "BotFights", - "ports": [ - { - "container": 9100, - "host": 9100, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "500Mi", - "memory_limit": "512Mi" - }, - "security": { - "apparmor_profile": "default", - "capabilities": [], - "network_policy": "bridge", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1001 - }, - "version": "1.1.0", - "volumes": [ - { - "source": "botfights-data", - "target": "/app/server/data", - "type": "bind" - }, - { - "options": [ - "rw", - "noexec", - "nosuid", - "size=64m" - ], - "target": "/tmp", - "type": "tmpfs" - } - ] + } } - }, - "version": "1.1.0" + } }, "btcpay": { + "version": "2.3.9", "image": "docker.io/btcpayserver/btcpayserver:2.3.9", "images": { - "archy-btcpay-db": "146.59.87.168:3000/lfg2025/postgres:15.17", + "btcpay-server": "docker.io/btcpayserver/btcpayserver:2.3.9", "archy-nbxplorer": "146.59.87.168:3000/lfg2025/nbxplorer:2.6.0", - "btcpay-server": "docker.io/btcpayserver/btcpayserver:2.3.9" - }, - "version": "2.3.9" + "archy-btcpay-db": "146.59.87.168:3000/lfg2025/postgres:15.17" + } }, "btcpay-server": { + "version": "2.3.9", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - }, + "id": "btcpay-server", + "name": "BTCPay Server", + "version": "2.3.9", + "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", "container": { - "derived_env": [ - { - "key": "BTCPAY_HOST", - "template": "{{HOST_IP}}:23000" - } - ], "image": "docker.io/btcpayserver/btcpayserver:2.3.9", - "network": "archy-net", "pull_policy": "if-not-present", + "network": "archy-net", "secret_env": [ { "key": "BTCPAY_BTCRPCPASSWORD", @@ -779,6 +776,17 @@ { "key": "BTCPAY_DB_PASS", "secret_file": "btcpay-db-password" + }, + { + "key": "BTCPAY_BTCLIGHTNING", + "secret_file": "btcpay-lnd-connection", + "optional": true + } + ], + "derived_env": [ + { + "key": "BTCPAY_HOST", + "template": "{{HOST_IP}}:23000" } ] }, @@ -796,84 +804,85 @@ "version": ">=2.6.0" } ], - "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", + "resources": { + "cpu_limit": 2, + "memory_limit": "2Gi", + "disk_limit": "20Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 23000, + "container": 49392, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/btcpay", + "target": "/datadir", + "options": [ + "rw" + ] + } + ], "environment": [ "ASPNETCORE_URLS=http://0.0.0.0:49392", "BTCPAY_PROTOCOL=http", "BTCPAY_CHAINS=btc", + "BTCPAY_PLUGINDIR=/datadir/Plugins", "BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838", "BTCPAY_BTCRPCURL=http://bitcoin-knots:8332", "BTCPAY_BTCRPCUSER=archipelago", "BTCPAY_POSTGRES=Username=btcpay;Password=${BTCPAY_DB_PASS};Host=archy-btcpay-db;Port=5432;Database=btcpay" ], "health_check": { + "type": "http", "endpoint": "http://localhost:49392", - "interval": "30s", "path": "/", - "retries": 5, + "interval": "30s", "timeout": "30s", - "type": "http" + "retries": 5 }, - "id": "btcpay-server", - "interfaces": { - "main": { - "description": "BTCPay Server dashboard", - "name": "Web UI", - "path": "/", - "port": 23000, - "protocol": "http", - "type": "ui" - } + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true }, "lightning_integration": { - "invoice_management": true, - "payment_processing": false + "payment_processing": false, + "invoice_management": true + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "BTCPay Server dashboard", + "type": "ui", + "port": 23000, + "protocol": "http", + "path": "/" + } }, "metadata": { "launch": { "open_in_new_tab": true } - }, - "name": "BTCPay Server", - "ports": [ - { - "container": 49392, - "host": 23000, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "20Gi", - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "2.3.9", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/btcpay", - "target": "/datadir", - "type": "bind" - } - ] + } } - }, - "version": "2.3.9" + } }, "core-lightning": { + "version": "23.08.2", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - }, + "id": "core-lightning", + "name": "Core Lightning (CLN)", + "version": "23.08.2", + "description": "Lightning Network implementation in C. Lightweight alternative to LND.", "container": { "image": "elementsproject/lightningd:v23.08.2", "image_signature": "cosign://...", @@ -885,7 +894,44 @@ "version": ">=26.0" } ], - "description": "Lightning Network implementation in C. Lightweight alternative to LND.", + "resources": { + "cpu_limit": 1, + "memory_limit": "512Mi", + "disk_limit": "5Gi" + }, + "security": { + "capabilities": [ + "NET_BIND_SERVICE" + ], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "core-lightning" + }, + "ports": [ + { + "host": 9736, + "container": 9735, + "protocol": "tcp" + }, + { + "host": 9835, + "container": 9835, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/core-lightning", + "target": "/home/clightning/.lightning", + "options": [ + "rw" + ] + } + ], "environment": [ "BITCOIND_RPCURL=http://bitcoin-core:8332", "BITCOIND_RPCUSER=${BITCOIN_RPC_USER}", @@ -893,68 +939,35 @@ "NETWORK=bitcoin" ], "health_check": { + "type": "exec", "endpoint": "lightning-cli getinfo", "interval": "30s", - "retries": 3, "timeout": "5s", - "type": "exec" + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true }, - "id": "core-lightning", "lightning_integration": { "channel_management": true, "payment_routing": true - }, - "name": "Core Lightning (CLN)", - "ports": [ - { - "container": 9735, - "host": 9736, - "protocol": "tcp" - }, - { - "container": 9835, - "host": 9835, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 1, - "disk_limit": "5Gi", - "memory_limit": "512Mi" - }, - "security": { - "apparmor_profile": "core-lightning", - "capabilities": [ - "NET_BIND_SERVICE" - ], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "23.08.2", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/core-lightning", - "target": "/home/clightning/.lightning", - "type": "bind" - } - ] + } } - }, - "version": "23.08.2" + } }, "cryptpad": { - "image": "146.59.87.168:3000/lfg2025/cryptpad:2024.12.0", - "version": "2024.12.0" + "version": "2024.12.0", + "image": "146.59.87.168:3000/lfg2025/cryptpad:2024.12.0" }, "did-wallet": { + "version": "1.0.0", "manifest": { "app": { + "id": "did-wallet", + "name": "Web5 DID Wallet", + "version": "1.0.0", + "description": "Web5 wallet with Decentralized Identifier (DID) support. Manage your digital identity and Web5 assets.", "container": { "image": "archipelago/did-wallet:1.0.0", "image_signature": "cosign://...", @@ -965,65 +978,65 @@ "storage": "2Gi" } ], - "description": "Web5 wallet with Decentralized Identifier (DID) support. Manage your digital identity and Web5 assets.", + "resources": { + "cpu_limit": 1, + "memory_limit": "512Mi", + "disk_limit": "2Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "did-wallet" + }, + "ports": [ + { + "host": 8088, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/did-wallet", + "target": "/app/wallet", + "options": [ + "rw" + ] + } + ], "environment": [ "WALLET_STORAGE=/app/wallet" ], "health_check": { - "endpoint": "http://localhost:8083", - "interval": "30s", + "type": "http", + "endpoint": "http://127.0.0.1:8080", "path": "/health", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "did-wallet", - "name": "Web5 DID Wallet", - "ports": [ - { - "container": 8080, - "host": 8083, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 1, - "disk_limit": "2Gi", - "memory_limit": "512Mi" - }, - "security": { - "apparmor_profile": "did-wallet", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "1.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/did-wallet", - "target": "/app/wallet", - "type": "bind" - } - ], "web5_integration": { - "bitcoin_integration": true, "did_support": true, - "wallet_functionality": true + "wallet_functionality": true, + "bitcoin_integration": true } } - }, - "version": "1.0.0" + } }, "electrs-ui": { + "version": "latest", "image": "146.59.87.168:3000/lfg2025/electrs-ui:latest", "manifest": { "app": { + "id": "electrs-ui", + "name": "Electrs UI", + "version": "1.0.0", + "description": "Archipelago-native HTTP frontend for electrs/electrumx status. Runs\nnginx inside a container, serves static assets, and proxies\n/electrs-status to the archipelago backend on 127.0.0.1:5678.\n", "container": { "build": { "context": "/opt/archipelago/docker/electrs-ui", @@ -1032,53 +1045,48 @@ } }, "dependencies": [], - "description": "Archipelago-native HTTP frontend for electrs/electrumx status. Runs\nnginx inside a container, serves static assets, and proxies\n/electrs-status to the archipelago backend on 127.0.0.1:5678.\n", - "environment": [], - "health_check": { - "endpoint": "http://127.0.0.1:50002", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "electrs-ui", - "name": "Electrs UI", - "ports": [], "resources": { "memory_limit": "64Mi" }, "security": { - "network_policy": "host", - "readonly_root": false + "readonly_root": false, + "network_policy": "host" }, - "version": "1.0.0", - "volumes": [] + "ports": [], + "volumes": [], + "environment": [], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:50002", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "latest" + } }, "electrumx": { + "version": "v1.18.0", "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", "manifest": { "app": { - "bitcoin_integration": { - "pruning_support": false, - "rpc_access": "read-only", - "sync_required": true - }, + "id": "electrumx", + "name": "ElectrumX", + "version": "1.18.0", + "description": "Electrum server indexing Bitcoin chain data for lightweight wallet queries.", "container": { - "custom_args": [ - "export DAEMON_URL=\"http://archipelago:$(printenv BITCOIN_RPC_PASS)@bitcoin-knots:8332/\"; exec electrumx_server" - ], + "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", + "pull_policy": "if-not-present", + "network": "archy-net", "data_uid": "1000:1000", "entrypoint": [ "sh", "-lc" ], - "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", - "network": "archy-net", - "pull_policy": "if-not-present", + "custom_args": [ + "export DAEMON_URL=\"http://archipelago:$(printenv BITCOIN_RPC_PASS)@bitcoin-knots:8332/\"; exec electrumx_server" + ], "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -1096,7 +1104,35 @@ }, "bitcoin:archival" ], - "description": "Electrum server indexing Bitcoin chain data for lightweight wallet queries.", + "resources": { + "cpu_limit": 0, + "memory_limit": "6Gi", + "disk_limit": "50Gi" + }, + "security": { + "capabilities": [ + "DAC_OVERRIDE" + ], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 50001, + "container": 50001, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/electrumx", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "COIN=Bitcoin", "DB_DIRECTORY=/data", @@ -1104,72 +1140,51 @@ "CACHE_MB=1024", "MAX_SEND=10000000" ], - "health_check": { - "endpoint": "localhost:50001", - "interval": "30s", - "retries": 3, - "start_period": "10m", - "timeout": "5s", - "type": "tcp" - }, - "id": "electrumx", "interfaces": { "main": { - "description": "ElectrumX server status and connection details", "name": "Web UI", + "description": "ElectrumX server status and connection details", + "type": "ui", "port": 50002, - "protocol": "http", - "type": "ui" + "protocol": "http" } }, - "name": "ElectrumX", - "ports": [ - { - "container": 50001, - "host": 50001, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 0, - "disk_limit": "50Gi", - "memory_limit": "6Gi" + "health_check": { + "type": "tcp", + "endpoint": "localhost:50001", + "interval": "30s", + "timeout": "5s", + "retries": 3, + "start_period": "10m" }, - "security": { - "capabilities": [ - "DAC_OVERRIDE" - ], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "1.18.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/electrumx", - "target": "/data", - "type": "bind" - } - ] + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true, + "pruning_support": false + } } - }, - "version": "v1.18.0" + } }, "fedimint": { + "version": "v0.10.0", "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - }, + "id": "fedimint", + "name": "Fedimint Guardian", + "version": "0.10.0", + "description": "Federated Bitcoin minting service with built-in Guardian UI. Privacy-preserving Bitcoin custody.", "container": { + "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", + "pull_policy": "if-not-present", + "network": "archy-net", + "entrypoint": [ + "sh", + "-lc" + ], "custom_args": [ "until state=\"$(curl -sS --connect-timeout 5 -m 45 -u \"$FM_BITCOIND_USERNAME:$FM_BITCOIND_PASSWORD\" -H \"Content-Type: application/json\" --data-binary '{\"jsonrpc\":\"1.0\",\"id\":\"fedimint-wait\",\"method\":\"getblockchaininfo\",\"params\":[]}' \"$FM_BITCOIND_URL/\")\" && echo \"$state\" | grep -q '\"initialblockdownload\":false'; do\n echo \"Waiting for Bitcoin RPC sync at $FM_BITCOIND_URL...\";\n sleep 30;\ndone;\nexec fedimintd" ], - "data_uid": "1000:1000", "derived_env": [ { "key": "FM_P2P_URL", @@ -1180,19 +1195,13 @@ "template": "ws://{{HOST_MDNS}}:8174" } ], - "entrypoint": [ - "sh", - "-lc" - ], - "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "FM_BITCOIND_PASSWORD", "secret_file": "bitcoin-rpc-password" } - ] + ], + "data_uid": "1000:1000" }, "dependencies": [ { @@ -1203,7 +1212,43 @@ "storage": "20Gi" } ], - "description": "Federated Bitcoin minting service with built-in Guardian UI. Privacy-preserving Bitcoin custody.", + "resources": { + "cpu_limit": 4, + "memory_limit": "4Gi", + "disk_limit": "20Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 8173, + "container": 8173, + "protocol": "tcp" + }, + { + "host": 8174, + "container": 8174, + "protocol": "tcp" + }, + { + "host": 8177, + "container": 8175, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/fedimint", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "FM_DATA_DIR=/data", "FM_BITCOIND_URL=http://bitcoin-knots:8332", @@ -1214,120 +1259,65 @@ "FM_BIND_UI=0.0.0.0:8175" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8175", - "interval": "30s", "path": "/", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "fedimint", "interfaces": { "main": { - "description": "Fedimint Guardian wait/proxy UI", "name": "Guardian UI", - "path": "/", + "description": "Fedimint Guardian wait/proxy UI", + "type": "ui", "port": 8175, "protocol": "http", - "type": "ui" + "path": "/" } }, - "name": "Fedimint Guardian", - "ports": [ - { - "container": 8173, - "host": 8173, - "protocol": "tcp" - }, - { - "container": 8174, - "host": 8174, - "protocol": "tcp" - }, - { - "container": 8175, - "host": 8177, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 4, - "disk_limit": "20Gi", - "memory_limit": "4Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": true - }, - "version": "0.10.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/fedimint", - "target": "/data", - "type": "bind" - } - ] + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + } } - }, - "version": "v0.10.0" + } }, "fedimint-clientd": { + "version": "0.8.0", "manifest": { "app": { + "id": "fedimint-clientd", + "name": "Fedimint Client", + "version": "0.8.0", + "description": "Fedimint ecash client daemon (fmcd). Lets the node hold Fedimint ecash and join federations; the wallet talks to it over a local REST API.", "container": { - "data_uid": "1000:1000", + "image": "146.59.87.168:3000/lfg2025/fmcd:0.8.1", + "pull_policy": "if-not-present", + "network": "archy-net", "generated_secrets": [ { - "kind": "hex16", - "name": "fmcd-password" + "name": "fmcd-password", + "kind": "hex16" } ], - "image": "146.59.87.168:3000/lfg2025/fmcd:0.8.1", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "FMCD_PASSWORD", "secret_file": "fmcd-password" } - ] + ], + "data_uid": "1000:1000" }, "dependencies": [ { "storage": "2Gi" } ], - "description": "Fedimint ecash client daemon (fmcd). Lets the node hold Fedimint ecash and join federations; the wallet talks to it over a local REST API.", - "environment": [ - "FMCD_ADDR=0.0.0.0:8080", - "FMCD_MODE=rest", - "FMCD_DATA_DIR=/data", - "FMCD_INVITE_CODE=fed11qgqyj3mfwfhksw309uuxywtxxfjrjc35xuexverpxdsnxcnrxucxvenzveskgc3kvvun2c34xp3k2ep38yunzdpexcekxe3hvd3rvvmx8pnrvdenx5mnzvtzqqqjqt0t6pc3s5z0ynqjw9s4njf6svwgu59kweawc0vvrddcjeemw6yyn4pcdp" - ], - "health_check": { - "endpoint": "localhost:8080", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "fedimint-clientd", - "name": "Fedimint Client", - "ports": [ - { - "container": 8080, - "host": 8178, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 1, - "disk_limit": "2Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "2Gi" }, "security": { "capabilities": [ @@ -1337,50 +1327,68 @@ "SETUID", "SETGID" ], - "network_policy": "bridge", - "readonly_root": true + "readonly_root": true, + "network_policy": "bridge" }, - "version": "0.8.0", + "ports": [ + { + "host": 8178, + "container": 8080, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/fmcd", "target": "/data", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "FMCD_ADDR=0.0.0.0:8080", + "FMCD_MODE=rest", + "FMCD_DATA_DIR=/data", + "FMCD_INVITE_CODE=fed11qgqyj3mfwfhksw309uuxywtxxfjrjc35xuexverpxdsnxcnrxucxvenzveskgc3kvvun2c34xp3k2ep38yunzdpexcekxe3hvd3rvvmx8pnrvdenx5mnzvtzqqqjqt0t6pc3s5z0ynqjw9s4njf6svwgu59kweawc0vvrddcjeemw6yyn4pcdp" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8080", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "0.8.0" + } }, "fedimint-gateway": { + "version": "v0.10.0", "image": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - }, + "id": "fedimint-gateway", + "name": "Fedimint Gateway", + "version": "0.10.0", + "description": "Fedimint gateway service with automatic LND-or-LDK backend selection.", "container": { - "custom_args": [ - "if [ -f /lnd/tls.cert ] && [ -f /lnd/data/chain/bitcoin/mainnet/admin.macaroon ]; then\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" lnd --lnd-rpc-host lnd:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/data/chain/bitcoin/mainnet/admin.macaroon;\nelse\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway;\nfi" - ], - "data_uid": "1000:1000", + "image": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0", + "pull_policy": "if-not-present", + "network": "archy-net", "entrypoint": [ "sh", "-lc" ], + "custom_args": [ + "if [ -f /lnd/tls.cert ] && [ -f /lnd/data/chain/bitcoin/mainnet/admin.macaroon ]; then\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" lnd --lnd-rpc-host lnd:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/data/chain/bitcoin/mainnet/admin.macaroon;\nelse\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway;\nfi" + ], "generated_secrets": [ { - "kind": "bcrypt", - "name": "fedimint-gateway-hash" + "name": "fedimint-gateway-hash", + "kind": "bcrypt" } ], - "image": "git.tx1138.com/lfg2025/gatewayd:v0.10.0", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "FM_BITCOIND_PASSWORD", @@ -1390,7 +1398,8 @@ "key": "FEDI_HASH", "secret_file": "fedimint-gateway-hash" } - ] + ], + "data_uid": "1000:1000" }, "dependencies": [ { @@ -1402,110 +1411,91 @@ "version": ">=0.10.0" } ], - "description": "Fedimint gateway service with automatic LND-or-LDK backend selection.", + "resources": { + "cpu_limit": 2, + "memory_limit": "2Gi", + "disk_limit": "10Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 8176, + "container": 8176, + "protocol": "tcp" + }, + { + "host": 9737, + "container": 9737, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/fedimint-gateway", + "target": "/data", + "options": [ + "rw" + ] + }, + { + "type": "bind", + "source": "/var/lib/archipelago/lnd", + "target": "/lnd", + "options": [ + "ro" + ] + } + ], "environment": [ "FM_BITCOIND_USERNAME=archipelago" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8176", - "interval": "30s", "path": "/", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "fedimint-gateway", - "name": "Fedimint Gateway", - "ports": [ - { - "container": 8176, - "host": 8176, - "protocol": "tcp" - }, - { - "container": 9737, - "host": 9737, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "10Gi", - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": true - }, - "version": "0.10.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/fedimint-gateway", - "target": "/data", - "type": "bind" - }, - { - "options": [ - "ro" - ], - "source": "/var/lib/archipelago/lnd", - "target": "/lnd", - "type": "bind" - } - ] + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + } } - }, - "version": "v0.10.0" + } }, "filebrowser": { + "version": "v2.27.0", "image": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "none", - "sync_required": false - }, + "id": "filebrowser", + "name": "File Browser", + "version": "2.27.0", + "description": "Baseline Archipelago file manager service.", "container": { + "image": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0", + "pull_policy": "if-not-present", + "network": "archy-net", "custom_args": [ "--config", "/data/.filebrowser.json" ], - "data_uid": "100000:100000", - "image": "git.tx1138.com/lfg2025/filebrowser:v2.27.0", - "network": "archy-net", - "pull_policy": "if-not-present" + "data_uid": "100000:100000" }, "dependencies": [ { "storage": "10Gi" } ], - "description": "Baseline Archipelago file manager service.", - "environment": [], - "health_check": { - "endpoint": "http://localhost:80", - "interval": "30s", - "path": "/health", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "filebrowser", - "name": "File Browser", - "ports": [ - { - "container": 80, - "host": 8083, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "10Gi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -1516,39 +1506,62 @@ "DAC_OVERRIDE", "NET_BIND_SERVICE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "2.27.0", + "ports": [ + { + "host": 8083, + "container": 80, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/filebrowser", "target": "/srv", - "type": "bind" - }, - { "options": [ "rw" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/filebrowser-data", "target": "/data", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "http", + "endpoint": "http://localhost:80", + "path": "/health", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "none", + "sync_required": false + } } - }, - "version": "v2.27.0" + } }, "fips": { - "image": "146.59.87.168:3000/lfg2025/fips:v0.1.0", - "version": "v0.1.0" + "version": "v0.1.0", + "image": "146.59.87.168:3000/lfg2025/fips:v0.1.0" }, "fips-ui": { + "version": "1.0.0", "manifest": { "app": { + "id": "fips-ui", + "name": "FIPS Mesh", + "version": "1.0.0", + "description": "Archipelago-native dashboard for the FIPS mesh transport. Runs nginx\ninside a container with host networking, serves a static dashboard on\n:8336, and reverse-proxies /rpc/v1 to the archipelago backend on\n127.0.0.1:5678. All FIPS controls (status, seed anchors, reconnect,\nrestart, and stable-channel daemon updates) go through the existing\nfips.* RPC methods, authenticated by the browser's own archipelago\nsession \u2014 there is no separate secret to manage.\n", "container": { "build": { "context": "/opt/archipelago/docker/fips-ui", @@ -1556,35 +1569,35 @@ "tag": "localhost/fips-ui:local" } }, - "description": "Archipelago-native dashboard for the FIPS mesh transport. Runs nginx\ninside a container with host networking, serves a static dashboard on\n:8336, and reverse-proxies /rpc/v1 to the archipelago backend on\n127.0.0.1:5678. All FIPS controls (status, seed anchors, reconnect,\nrestart, and stable-channel daemon updates) go through the existing\nfips.* RPC methods, authenticated by the browser's own archipelago\nsession — there is no separate secret to manage.\n", - "environment": [], - "health_check": { - "endpoint": "http://127.0.0.1:8336", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "fips-ui", - "name": "FIPS Mesh", - "ports": [], "resources": { "memory_limit": "128Mi" }, "security": { - "network_policy": "host", - "readonly_root": false + "readonly_root": false, + "network_policy": "host" }, - "version": "1.0.0", - "volumes": [] + "ports": [], + "volumes": [], + "environment": [], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:8336", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "1.0.0" + } }, "gitea": { + "version": "1.23", "manifest": { "app": { + "id": "gitea", + "name": "Gitea", + "version": "1.23", + "description": "Self-hosted Git service with built-in container registry, CI/CD, and package hosting.", "category": "development", "container": { "image": "docker.io/gitea/gitea:1.23", @@ -1595,74 +1608,9 @@ "storage": "500Mi" } ], - "description": "Self-hosted Git service with built-in container registry, CI/CD, and package hosting.", - "environment": [ - "GITEA__database__DB_TYPE=sqlite3", - "GITEA__server__SSH_PORT=2222", - "GITEA__server__SSH_LISTEN_PORT=22", - "GITEA__server__LFS_START_SERVER=true", - "GITEA__packages__ENABLED=true", - "GITEA__repository__ENABLE_PUSH_CREATE_USER=true", - "GITEA__repository__ENABLE_PUSH_CREATE_ORG=true" - ], - "health_check": { - "endpoint": "http://localhost:3000", - "interval": "120s", - "path": "/", - "retries": 5, - "timeout": "30s", - "type": "http" - }, - "id": "gitea", - "interfaces": { - "main": { - "description": "Gitea web interface", - "name": "Web UI", - "path": "/", - "port": 3001, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "features": [ - "Git repositories with web UI", - "Built-in container/package registry", - "Issue tracking and pull requests", - "CI/CD via Gitea Actions", - "Lightweight SQLite deployment" - ], - "icon": "/assets/img/app-icons/gitea.svg", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://gitea.com", - "tier": "optional" - }, - "name": "Gitea", - "nginx_proxy": { - "extra_headers": [ - "proxy_hide_header X-Frame-Options", - "proxy_hide_header Content-Security-Policy" - ], - "listen": 3000, - "proxy_pass": "http://127.0.0.1:3001" - }, - "ports": [ - { - "container": 3000, - "host": 3001, - "protocol": "tcp" - }, - { - "container": 22, - "host": 2222, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "500Mi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "500Mi" }, "security": { "capabilities": [ @@ -1673,49 +1621,144 @@ "DAC_OVERRIDE", "NET_BIND_SERVICE" ], - "network_policy": "bridge", + "readonly_root": false, "no_new_privileges": false, - "readonly_root": false + "network_policy": "bridge" }, - "version": "1.23", - "volumes": [ + "ports": [ { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/gitea/data", - "target": "/data", - "type": "bind" + "host": 3001, + "container": 3000, + "protocol": "tcp" }, { + "host": 2222, + "container": 22, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/gitea/data", + "target": "/data", "options": [ "rw" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/gitea/config", "target": "/etc/gitea", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "GITEA__database__DB_TYPE=sqlite3", + "GITEA__server__SSH_PORT=2222", + "GITEA__server__SSH_LISTEN_PORT=22", + "GITEA__server__LFS_START_SERVER=true", + "GITEA__packages__ENABLED=true", + "GITEA__repository__ENABLE_PUSH_CREATE_USER=true", + "GITEA__repository__ENABLE_PUSH_CREATE_ORG=true" + ], + "health_check": { + "type": "http", + "endpoint": "http://localhost:3000", + "path": "/", + "interval": "120s", + "timeout": "30s", + "retries": 5 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "Gitea web interface", + "type": "ui", + "port": 3001, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/gitea.svg", + "repo": "https://gitea.com", + "tier": "optional", + "launch": { + "open_in_new_tab": true + }, + "features": [ + "Git repositories with web UI", + "Built-in container/package registry", + "Issue tracking and pull requests", + "CI/CD via Gitea Actions", + "Lightweight SQLite deployment" + ] + }, + "nginx_proxy": { + "listen": 3000, + "proxy_pass": "http://127.0.0.1:3001", + "extra_headers": [ + "proxy_hide_header X-Frame-Options", + "proxy_hide_header Content-Security-Policy" + ] + } } - }, - "version": "1.23" + } }, "grafana": { + "version": "10.2.0", "image": "146.59.87.168:3000/lfg2025/grafana:10.2.0", "manifest": { "app": { + "id": "grafana", + "name": "Grafana", + "version": "10.2.0", + "description": "Analytics and monitoring platform. Visualize metrics and create dashboards.", "container": { - "data_uid": "472:472", "image": "grafana/grafana:10.2.0", "image_signature": "cosign://...", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "data_uid": "472:472" }, "dependencies": [ { "storage": "5Gi" } ], - "description": "Analytics and monitoring platform. Visualize metrics and create dashboards.", + "resources": { + "cpu_limit": 2, + "memory_limit": "1Gi", + "disk_limit": "5Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "grafana" + }, + "ports": [ + { + "host": 3000, + "container": 3000, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/grafana", + "target": "/var/lib/grafana", + "options": [ + "rw" + ] + } + ], "environment": [ "GF_SECURITY_ADMIN_USER=admin", "GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}", @@ -1723,117 +1766,46 @@ "GF_INSTALL_PLUGINS=" ], "health_check": { + "type": "http", "endpoint": "http://localhost:3000", - "interval": "30s", "path": "/api/health", - "retries": 5, + "interval": "30s", "timeout": "30s", - "type": "http" + "retries": 5 }, - "id": "grafana", "metadata": { "launch": { "open_in_new_tab": true } - }, - "name": "Grafana", - "ports": [ - { - "container": 3000, - "host": 3000, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "5Gi", - "memory_limit": "1Gi" - }, - "security": { - "apparmor_profile": "grafana", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "10.2.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/grafana", - "target": "/var/lib/grafana", - "type": "bind" - } - ] + } } - }, - "version": "10.2.0" + } }, "homeassistant": { + "version": "2024.1", "image": "146.59.87.168:3000/lfg2025/home-assistant:2024.1", "manifest": { "app": { + "id": "homeassistant", + "name": "Home Assistant", + "version": "2024.1.0", + "description": "Open source home automation platform. Control and monitor your smart home devices.", "container": { "image": "146.59.87.168:3000/lfg2025/home-assistant:2024.1", - "network": "pasta", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "pasta" }, "dependencies": [ { "storage": "10Gi" } ], - "description": "Open source home automation platform. Control and monitor your smart home devices.", - "devices": [], - "environment": [ - "TZ=UTC" - ], - "health_check": { - "endpoint": "localhost:8123", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "homeassistant", - "interfaces": { - "main": { - "description": "Home Assistant dashboard", - "name": "Web UI", - "path": "/", - "port": 8123, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "author": "Home Assistant", - "category": "home", - "icon": "/assets/img/app-icons/homeassistant.png", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://github.com/home-assistant/core" - }, - "name": "Home Assistant", - "ports": [ - { - "container": 8123, - "host": 8123, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 2, - "disk_limit": "10Gi", - "memory_limit": "512Mi" + "memory_limit": "512Mi", + "disk_limit": "10Gi" }, "security": { - "apparmor_profile": "home-assistant", "capabilities": [ "CHOWN", "FOWNER", @@ -1843,40 +1815,82 @@ "NET_BIND_SERVICE", "NET_RAW" ], - "network_policy": "isolated", - "no_new_privileges": true, "readonly_root": false, + "no_new_privileges": true, + "user": 1000, "seccomp_profile": "default", - "user": 1000 + "network_policy": "isolated", + "apparmor_profile": "home-assistant" }, - "version": "2024.1.0", + "ports": [ + { + "host": 8123, + "container": 8123, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/home-assistant", "target": "/config", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "devices": [], + "environment": [ + "TZ=UTC" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8123", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "Home Assistant dashboard", + "type": "ui", + "port": 8123, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/homeassistant.png", + "category": "home", + "author": "Home Assistant", + "repo": "https://github.com/home-assistant/core", + "launch": { + "open_in_new_tab": true + } + } } - }, - "version": "2024.1" + } }, "immich": { + "version": "release", "image": "146.59.87.168:3000/lfg2025/immich-server:release", "images": { + "immich_server": "146.59.87.168:3000/lfg2025/immich-server:release", "immich_postgres": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", - "immich_redis": "146.59.87.168:3000/lfg2025/redis:7.4.8", - "immich_server": "146.59.87.168:3000/lfg2025/immich-server:release" + "immich_redis": "146.59.87.168:3000/lfg2025/redis:7.4.8" }, "manifest": { "app": { + "id": "immich", + "name": "Immich", + "version": "2.7.4", + "description": "Self-hosted photo and video backup with mobile apps and search.", + "container_name": "immich_server", "container": { "image": "146.59.87.168:3000/lfg2025/immich-server:release", - "network": "archy-net", "pull_policy": "if-not-present", + "network": "archy-net", "secret_env": [ { "key": "DB_PASSWORD", @@ -1884,7 +1898,6 @@ } ] }, - "container_name": "immich_server", "dependencies": [ { "app_id": "immich-postgres" @@ -1896,7 +1909,36 @@ "storage": "200Gi" } ], - "description": "Self-hosted photo and video backup with mobile apps and search.", + "resources": { + "memory_limit": "2Gi", + "disk_limit": "200Gi" + }, + "security": { + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "FOWNER" + ], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 2283, + "container": 2283, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/immich", + "target": "/usr/src/app/upload", + "options": [ + "rw" + ] + } + ], "environment": [ "DB_HOSTNAME=immich_postgres", "DB_USERNAME=postgres", @@ -1905,79 +1947,51 @@ "UPLOAD_LOCATION=/usr/src/app/upload" ], "health_check": { + "type": "http", "endpoint": "http://localhost:2283", - "interval": "30s", "path": "/api/server/ping", - "retries": 20, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 20 }, - "id": "immich", "interfaces": { "main": { - "description": "Immich photo library", "name": "Web UI", - "path": "/", + "description": "Immich photo library", + "type": "ui", "port": 2283, "protocol": "http", - "type": "ui" + "path": "/" } }, "metadata": { "launch": { "open_in_new_tab": true } - }, - "name": "Immich", - "ports": [ - { - "container": 2283, - "host": 2283, - "protocol": "tcp" - } - ], - "resources": { - "disk_limit": "200Gi", - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [ - "CHOWN", - "DAC_OVERRIDE", - "FOWNER" - ], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "2.7.4", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/immich", - "target": "/usr/src/app/upload", - "type": "bind" - } - ] + } } - }, - "version": "release" + } }, "immich-postgres": { + "version": "14-vectorchord0.4.3-pgvectors0.2.0", "manifest": { "app": { + "id": "immich-postgres", + "name": "Immich Postgres", + "version": "14-vectorchord0.4.3-pgvectors0.2.0", + "description": "Postgres (pgvecto.rs / vectorchord) backend for Immich.", + "container_name": "immich_postgres", "container": { + "image": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", + "pull_policy": "if-not-present", + "network": "archy-net", "data_uid": "100998:100998", "generated_secrets": [ { - "kind": "hex32", - "name": "immich-db-password" + "name": "immich-db-password", + "kind": "hex32" } ], - "image": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -1985,30 +1999,14 @@ } ] }, - "container_name": "immich_postgres", "dependencies": [ { "storage": "40Gi" } ], - "description": "Postgres (pgvecto.rs / vectorchord) backend for Immich.", - "environment": [ - "POSTGRES_USER=postgres", - "POSTGRES_DB=immich" - ], - "health_check": { - "endpoint": "localhost:5432", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "immich-postgres", - "name": "Immich Postgres", - "ports": [], "resources": { - "disk_limit": "40Gi", - "memory_limit": "2Gi" + "memory_limit": "2Gi", + "disk_limit": "40Gi" }, "security": { "capabilities": [ @@ -2018,46 +2016,49 @@ "SETGID", "SETUID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "14-vectorchord0.4.3-pgvectors0.2.0", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/immich-db", "target": "/var/lib/postgresql/data", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "POSTGRES_USER=postgres", + "POSTGRES_DB=immich" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:5432", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "14-vectorchord0.4.3-pgvectors0.2.0" + } }, "immich-redis": { + "version": "7-alpine", "manifest": { "app": { - "container": { - "image": "146.59.87.168:3000/lfg2025/valkey:7-alpine", - "network": "archy-net", - "pull_policy": "if-not-present" - }, - "container_name": "immich_redis", - "dependencies": [], - "description": "Valkey (Redis-compatible) cache for Immich.", - "environment": [], - "health_check": { - "endpoint": "localhost:6379", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, "id": "immich-redis", "name": "Immich Redis", - "ports": [], + "version": "7-alpine", + "description": "Valkey (Redis-compatible) cache for Immich.", + "container_name": "immich_redis", + "container": { + "image": "146.59.87.168:3000/lfg2025/valkey:7-alpine", + "pull_policy": "if-not-present", + "network": "archy-net" + }, + "dependencies": [], "resources": { "memory_limit": "128Mi" }, @@ -2066,16 +2067,24 @@ "SETGID", "SETUID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "7-alpine", - "volumes": [] + "ports": [], + "volumes": [], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:6379", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "7-alpine" + } }, "indeedhub": { + "version": "1.0.0", "image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", "images": { "indeedhub": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", @@ -2084,13 +2093,17 @@ }, "manifest": { "app": { + "id": "indeedhub", + "name": "IndeeHub", + "version": "1.0.0", + "description": "Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity.", "category": "community", + "container_name": "indeedhub", "container": { "image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", - "network": "indeedhub-net", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "indeedhub-net" }, - "container_name": "indeedhub", "dependencies": [ { "app_id": "indeedhub-api" @@ -2099,16 +2112,50 @@ "storage": "1Gi" } ], - "description": "Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity.", - "environment": [], - "health_check": { - "endpoint": "localhost:7777", - "interval": "30s", - "retries": 5, - "start_period": "30s", - "timeout": "5s", - "type": "tcp" + "resources": { + "memory_limit": "512Mi", + "disk_limit": "1Gi" }, + "security": { + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "SETGID", + "SETUID" + ], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 7778, + "container": 7777, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "tmpfs", + "target": "/run", + "options": [ + "rw", + "nosuid", + "nodev", + "size=16m" + ] + }, + { + "type": "tmpfs", + "target": "/var/cache/nginx", + "options": [ + "rw", + "nosuid", + "nodev", + "size=32m" + ] + } + ], + "environment": [], "hooks": { "post_install": [ { @@ -2121,8 +2168,8 @@ }, { "copy_from_host": { - "dest": "/usr/share/nginx/html/nostr-provider.js", - "src": "web-ui/nostr-provider.js" + "src": "web-ui/nostr-provider.js", + "dest": "/usr/share/nginx/html/nostr-provider.js" } }, { @@ -2141,22 +2188,30 @@ } ] }, - "id": "indeedhub", + "health_check": { + "type": "tcp", + "endpoint": "localhost:7777", + "interval": "30s", + "timeout": "5s", + "retries": 5, + "start_period": "30s" + }, "interfaces": { "main": { - "description": "Stream Bitcoin documentaries with Nostr identity", "name": "Web UI", - "path": "/", + "description": "Stream Bitcoin documentaries with Nostr identity", + "type": "ui", "port": 7778, "protocol": "http", - "type": "ui" + "path": "/" } }, "metadata": { "author": "Indeehub Team", "icon": "/assets/img/app-icons/indeedhub.png", - "license": "MIT", + "website": "https://indeedhub.com", "repo": "https://github.com/indeedhub/indeedhub", + "license": "MIT", "tags": [ "bitcoin", "documentary", @@ -2164,75 +2219,34 @@ "media", "education", "nostr" - ], - "website": "https://indeedhub.com" - }, - "name": "IndeeHub", - "ports": [ - { - "container": 7777, - "host": 7778, - "protocol": "tcp" - } - ], - "resources": { - "disk_limit": "1Gi", - "memory_limit": "512Mi" - }, - "security": { - "capabilities": [ - "CHOWN", - "DAC_OVERRIDE", - "SETGID", - "SETUID" - ], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "1.0.0", - "volumes": [ - { - "options": [ - "rw", - "nosuid", - "nodev", - "size=16m" - ], - "target": "/run", - "type": "tmpfs" - }, - { - "options": [ - "rw", - "nosuid", - "nodev", - "size=32m" - ], - "target": "/var/cache/nginx", - "type": "tmpfs" - } - ] + ] + } } - }, - "version": "1.0.0" + } }, "indeedhub-api": { + "version": "1.0.0", "manifest": { "app": { + "id": "indeedhub-api", + "name": "IndeedHub API", + "version": "1.0.0", + "description": "IndeedHub backend API (Nostr auth, media, payments).", "category": "community", + "container_name": "indeedhub-api", "container": { - "generated_secrets": [ - { - "kind": "hex32", - "name": "indeedhub-jwt" - } - ], "image": "146.59.87.168:3000/lfg2025/indeedhub-api:1.0.0", + "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "api" ], - "pull_policy": "if-not-present", + "generated_secrets": [ + { + "name": "indeedhub-jwt", + "kind": "hex32" + } + ], "secret_env": [ { "key": "DATABASE_PASSWORD", @@ -2248,7 +2262,6 @@ } ] }, - "container_name": "indeedhub-api", "dependencies": [ { "app_id": "indeedhub-postgres" @@ -2260,7 +2273,16 @@ "app_id": "indeedhub-minio" } ], - "description": "IndeedHub backend API (Nostr auth, media, payments).", + "resources": { + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [], + "volumes": [], "environment": [ "PORT=4000", "DATABASE_HOST=postgres", @@ -2280,37 +2302,29 @@ "ENVIRONMENT=production" ], "health_check": { + "type": "tcp", "endpoint": "localhost:4000", "interval": "30s", - "retries": 10, "timeout": "5s", - "type": "tcp" - }, - "id": "indeedhub-api", - "name": "IndeedHub API", - "ports": [], - "resources": { - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "1.0.0", - "volumes": [] + "retries": 10 + } } - }, - "version": "1.0.0" + } }, "indeedhub-ffmpeg": { + "version": "1.0.0", "manifest": { "app": { + "id": "indeedhub-ffmpeg", + "name": "IndeedHub FFmpeg Worker", + "version": "1.0.0", + "description": "IndeedHub background media transcoding worker.", "category": "community", + "container_name": "indeedhub-ffmpeg", "container": { "image": "146.59.87.168:3000/lfg2025/indeedhub-ffmpeg:1.0.0", - "network": "indeedhub-net", "pull_policy": "if-not-present", + "network": "indeedhub-net", "secret_env": [ { "key": "DATABASE_PASSWORD", @@ -2322,13 +2336,21 @@ } ] }, - "container_name": "indeedhub-ffmpeg", "dependencies": [ { "app_id": "indeedhub-api" } ], - "description": "IndeedHub background media transcoding worker.", + "resources": { + "memory_limit": "4Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [], + "volumes": [], "environment": [ "DATABASE_HOST=postgres", "DATABASE_PORT=5432", @@ -2343,45 +2365,37 @@ "S3_PRIVATE_BUCKET_NAME=indeedhub-private", "ENVIRONMENT=production", "AES_MASTER_SECRET=0123456789abcdef0123456789abcdef" - ], - "id": "indeedhub-ffmpeg", - "name": "IndeedHub FFmpeg Worker", - "ports": [], - "resources": { - "memory_limit": "4Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "1.0.0", - "volumes": [] + ] } - }, - "version": "1.0.0" + } }, "indeedhub-minio": { + "version": "RELEASE.2024-11-07T00-52-20Z", "manifest": { "app": { + "id": "indeedhub-minio", + "name": "IndeedHub MinIO", + "version": "RELEASE.2024-11-07T00-52-20Z", + "description": "MinIO S3-compatible object storage for IndeedHub media.", "category": "community", + "container_name": "indeedhub-minio", "container": { + "image": "146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z", + "pull_policy": "if-not-present", + "network": "indeedhub-net", + "network_aliases": [ + "minio" + ], "custom_args": [ "server", "/data" ], "generated_secrets": [ { - "kind": "hex32", - "name": "indeedhub-minio-password" + "name": "indeedhub-minio-password", + "kind": "hex32" } ], - "image": "146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z", - "network": "indeedhub-net", - "network_aliases": [ - "minio" - ], - "pull_policy": "if-not-present", "secret_env": [ { "key": "MINIO_ROOT_PASSWORD", @@ -2389,68 +2403,68 @@ } ] }, - "container_name": "indeedhub-minio", "dependencies": [ { "storage": "50Gi" } ], - "description": "MinIO S3-compatible object storage for IndeedHub media.", + "resources": { + "memory_limit": "1Gi", + "disk_limit": "50Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [], + "volumes": [ + { + "type": "volume", + "source": "indeedhub-minio-data", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "MINIO_ROOT_USER=indeeadmin" ], "health_check": { + "type": "http", "endpoint": "http://localhost:9000", - "interval": "30s", "path": "/minio/health/live", - "retries": 5, + "interval": "30s", "timeout": "5s", - "type": "http" - }, - "id": "indeedhub-minio", - "name": "IndeedHub MinIO", - "ports": [], - "resources": { - "disk_limit": "50Gi", - "memory_limit": "1Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "RELEASE.2024-11-07T00-52-20Z", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "indeedhub-minio-data", - "target": "/data", - "type": "volume" - } - ] + "retries": 5 + } } - }, - "version": "RELEASE.2024-11-07T00-52-20Z" + } }, "indeedhub-postgres": { + "version": "16.13-alpine", "manifest": { "app": { + "id": "indeedhub-postgres", + "name": "IndeedHub Postgres", + "version": "16.13-alpine", + "description": "Postgres database backend for IndeedHub.", "category": "community", + "container_name": "indeedhub-postgres", "container": { - "generated_secrets": [ - { - "kind": "hex32", - "name": "indeedhub-db-password" - } - ], "image": "146.59.87.168:3000/lfg2025/postgres:16.13-alpine", + "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "postgres" ], - "pull_policy": "if-not-present", + "generated_secrets": [ + { + "name": "indeedhub-db-password", + "kind": "hex32" + } + ], "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -2458,30 +2472,14 @@ } ] }, - "container_name": "indeedhub-postgres", "dependencies": [ { "storage": "10Gi" } ], - "description": "Postgres database backend for IndeedHub.", - "environment": [ - "POSTGRES_USER=indeedhub", - "POSTGRES_DB=indeedhub" - ], - "health_check": { - "endpoint": "localhost:5432", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "indeedhub-postgres", - "name": "IndeedHub Postgres", - "ports": [], "resources": { - "disk_limit": "10Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -2491,54 +2489,57 @@ "SETGID", "SETUID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "16.13-alpine", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "volume", "source": "indeedhub-postgres-data", "target": "/var/lib/postgresql/data", - "type": "volume" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "POSTGRES_USER=indeedhub", + "POSTGRES_DB=indeedhub" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:5432", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "16.13-alpine" + } }, "indeedhub-redis": { + "version": "7.4.8-alpine", "manifest": { "app": { + "id": "indeedhub-redis", + "name": "IndeedHub Redis", + "version": "7.4.8-alpine", + "description": "Redis queue/cache backend for IndeedHub.", "category": "community", + "container_name": "indeedhub-redis", "container": { "image": "146.59.87.168:3000/lfg2025/redis:7.4.8-alpine", + "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "redis" - ], - "pull_policy": "if-not-present" + ] }, - "container_name": "indeedhub-redis", "dependencies": [ { "storage": "1Gi" } ], - "description": "Redis queue/cache backend for IndeedHub.", - "environment": [], - "health_check": { - "endpoint": "localhost:6379", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "indeedhub-redis", - "name": "IndeedHub Redis", - "ports": [], "resources": { "memory_limit": "256Mi" }, @@ -2547,129 +2548,107 @@ "SETGID", "SETUID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "7.4.8-alpine", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "volume", "source": "indeedhub-redis-data", "target": "/data", - "type": "volume" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:6379", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "7.4.8-alpine" + } }, "indeedhub-relay": { + "version": "0.9.0", "manifest": { "app": { + "id": "indeedhub-relay", + "name": "IndeedHub Nostr Relay", + "version": "0.9.0", + "description": "nostr-rs-relay backing IndeedHub's Nostr identity + comments.", "category": "community", + "container_name": "indeedhub-relay", "container": { "image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0", + "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "relay" - ], - "pull_policy": "if-not-present" + ] }, - "container_name": "indeedhub-relay", "dependencies": [ { "storage": "2Gi" } ], - "description": "nostr-rs-relay backing IndeedHub's Nostr identity + comments.", - "environment": [], - "health_check": { - "endpoint": "localhost:8080", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "indeedhub-relay", - "name": "IndeedHub Nostr Relay", - "ports": [], "resources": { - "disk_limit": "2Gi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "2Gi" }, "security": { "capabilities": [], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "0.9.0", + "ports": [], "volumes": [ { - "options": [ - "rw" - ], + "type": "volume", "source": "indeedhub-relay-data", "target": "/usr/src/app/db", - "type": "volume" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8080", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "0.9.0" + } }, "jellyfin": { + "version": "10.8.13", "image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13", "manifest": { "app": { + "id": "jellyfin", + "name": "Jellyfin", + "version": "10.8.13", + "description": "Free media server. Stream movies, music, and photos.", "container": { "image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13", - "network": "pasta", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "pasta" }, "dependencies": [ { "storage": "10Gi" } ], - "description": "Free media server. Stream movies, music, and photos.", - "environment": [], - "health_check": { - "endpoint": "localhost:8096", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "jellyfin", - "interfaces": { - "main": { - "description": "Jellyfin media dashboard", - "name": "Web UI", - "path": "/", - "port": 8096, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "author": "Jellyfin", - "category": "data", - "icon": "/assets/img/app-icons/jellyfin.webp", - "repo": "https://github.com/jellyfin/jellyfin" - }, - "name": "Jellyfin", - "ports": [ - { - "container": 8096, - "host": 8096, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "10Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -2679,39 +2658,69 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "10.8.13", + "ports": [ + { + "host": 8096, + "container": 8096, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/jellyfin/config", "target": "/config", - "type": "bind" - }, - { "options": [ "rw" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/jellyfin/cache", "target": "/cache", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:8096", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "Jellyfin media dashboard", + "type": "ui", + "port": 8096, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/jellyfin.webp", + "category": "data", + "author": "Jellyfin", + "repo": "https://github.com/jellyfin/jellyfin" + } } - }, - "version": "10.8.13" + } }, "lightning-stack": { + "version": "0.12.0", "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - }, + "id": "lightning-stack", + "name": "Lightning Stack", + "version": "0.12.0", + "description": "Complete Lightning Network implementation. Includes LND, CLN, and management tools.", "container": { "image": "lightninglabs/lightning-stack:v0.12.0", "image_signature": "cosign://...", @@ -2726,7 +2735,49 @@ "storage": "50Gi" } ], - "description": "Complete Lightning Network implementation. Includes LND, CLN, and management tools.", + "resources": { + "cpu_limit": 4, + "memory_limit": "4Gi", + "disk_limit": "50Gi" + }, + "security": { + "capabilities": [ + "NET_BIND_SERVICE" + ], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "lightning-stack" + }, + "ports": [ + { + "host": 9738, + "container": 9735, + "protocol": "tcp" + }, + { + "host": 10010, + "container": 10009, + "protocol": "tcp" + }, + { + "host": 8091, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/lightning-stack", + "target": "/root/.lightning", + "options": [ + "rw" + ] + } + ], "environment": [ "BITCOIND_HOST=bitcoin-core", "BITCOIND_RPCUSER=${BITCOIN_RPC_USER}", @@ -2734,92 +2785,50 @@ "NETWORK=mainnet" ], "health_check": { - "endpoint": "http://localhost:8087", - "interval": "30s", + "type": "http", + "endpoint": "http://127.0.0.1:8080", "path": "/v1/getinfo", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "lightning-stack", - "lightning_integration": { - "channel_management": true, - "payment_routing": true - }, - "name": "Lightning Stack", - "ports": [ - { - "container": 9735, - "host": 9737, - "protocol": "tcp" - }, - { - "container": 10009, - "host": 10010, - "protocol": "tcp" - }, - { - "container": 8080, - "host": 8087, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 4, - "disk_limit": "50Gi", - "memory_limit": "4Gi" - }, - "security": { - "apparmor_profile": "lightning-stack", - "capabilities": [ - "NET_BIND_SERVICE" - ], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "0.12.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/lightning-stack", - "target": "/root/.lightning", - "type": "bind" - } - ] - } - }, - "version": "0.12.0" - }, - "lnd": { - "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", - "manifest": { - "app": { "bitcoin_integration": { "rpc_access": "admin", "sync_required": true }, + "lightning_integration": { + "channel_management": true, + "payment_routing": true + } + } + } + }, + "lnd": { + "version": "v0.18.4-beta", + "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", + "manifest": { + "app": { + "id": "lnd", + "name": "LND", + "version": "0.18.4", + "description": "Lightning Network implementation by Lightning Labs. Enables instant, low-cost Bitcoin payments.", "container": { - "data_uid": "100000:100000", + "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", + "pull_policy": "if-not-present", + "network": "archy-net", "derived_env": [ { "key": "BITCOIND_HOST", "template": "{{BITCOIN_HOST}}" } ], - "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIND_RPCPASS", "secret_file": "bitcoin-rpc-password" } - ] + ], + "data_uid": "100000:100000" }, "dependencies": [ { @@ -2827,45 +2836,10 @@ "version": ">=26.0" } ], - "description": "Lightning Network implementation by Lightning Labs. Enables instant, low-cost Bitcoin payments.", - "environment": [ - "BITCOIND_RPCUSER=archipelago", - "NETWORK=mainnet" - ], - "health_check": { - "endpoint": "localhost:10009", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "lnd", - "lightning_integration": { - "channel_management": true, - "payment_routing": true - }, - "name": "LND", - "ports": [ - { - "container": 9735, - "host": 9735, - "protocol": "tcp" - }, - { - "container": 10009, - "host": 10009, - "protocol": "tcp" - }, - { - "container": 8080, - "host": 18080, - "protocol": "tcp" - } - ], "resources": { "cpu_limit": 2, - "disk_limit": "10Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -2876,28 +2850,67 @@ "DAC_OVERRIDE", "NET_RAW" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "0.18.4", + "ports": [ + { + "host": 9735, + "container": 9735, + "protocol": "tcp" + }, + { + "host": 10009, + "container": 10009, + "protocol": "tcp" + }, + { + "host": 18080, + "container": 8080, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/lnd", "target": "/root/.lnd", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "BITCOIND_RPCUSER=archipelago", + "NETWORK=mainnet" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:10009", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + }, + "lightning_integration": { + "channel_management": true, + "payment_routing": true + } } - }, - "version": "v0.18.4-beta" + } }, "lnd-ui": { + "version": "latest", "image": "146.59.87.168:3000/lfg2025/lnd-ui:latest", "manifest": { "app": { + "id": "lnd-ui", + "name": "LND UI", + "version": "1.0.0", + "description": "Archipelago-native HTTP frontend for LND. Runs nginx inside a\ncontainer and serves static assets. LND connection info is fetched\nvia an absolute URL that the host nginx routes to the archipelago\nbackend on 127.0.0.1:5678, so no upstream auth is baked in.\n", "container": { "build": { "context": "/opt/archipelago/docker/lnd-ui", @@ -2910,51 +2923,47 @@ "app_id": "lnd" } ], - "description": "Archipelago-native HTTP frontend for LND. Runs nginx inside a\ncontainer and serves static assets. LND connection info is fetched\nvia an absolute URL that the host nginx routes to the archipelago\nbackend on 127.0.0.1:5678, so no upstream auth is baked in.\n", - "environment": [], - "health_check": { - "endpoint": "http://127.0.0.1:18083", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "lnd-ui", - "name": "LND UI", - "ports": [ - { - "container": 80, - "host": 18083, - "protocol": "tcp" - } - ], "resources": { "memory_limit": "64Mi" }, "security": { - "network_policy": "bridge", - "readonly_root": false + "readonly_root": false, + "network_policy": "bridge" }, - "version": "1.0.0", - "volumes": [] + "ports": [ + { + "host": 18083, + "container": 80, + "protocol": "tcp" + } + ], + "volumes": [], + "environment": [], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:18083", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + } } - }, - "version": "latest" + } }, "mempool": { + "version": "v3.0.1", "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", "images": { - "archy-mempool-db": "146.59.87.168:3000/lfg2025/mariadb:11.4.10", "archy-mempool-web": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", - "mempool-api": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0" + "mempool-api": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0", + "archy-mempool-db": "146.59.87.168:3000/lfg2025/mariadb:11.4.10" }, "manifest": { "app": { - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - }, + "id": "mempool", + "name": "Mempool Explorer", + "version": "3.0.0", + "description": "Bitcoin mempool and blockchain explorer. Real-time transaction and block visualization.", "container": { "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", "image_signature": "cosign://...", @@ -2970,7 +2979,37 @@ }, "bitcoin:archival" ], - "description": "Bitcoin mempool and blockchain explorer. Real-time transaction and block visualization.", + "resources": { + "cpu_limit": 2, + "memory_limit": "2Gi", + "disk_limit": "20Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "mempool" + }, + "ports": [ + { + "host": 4080, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/mempool", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "MEMPOOL_BACKEND=electrum", "MEMPOOL_BITCOIN_HOST=bitcoin-core", @@ -2979,69 +3018,38 @@ "MEMPOOL_BITCOIN_PASSWORD=${BITCOIN_RPC_PASSWORD}" ], "health_check": { + "type": "http", "endpoint": "http://localhost:4080", - "interval": "30s", "path": "/api/health", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "mempool", - "name": "Mempool Explorer", - "ports": [ - { - "container": 8080, - "host": 4080, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "20Gi", - "memory_limit": "2Gi" - }, - "security": { - "apparmor_profile": "mempool", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "3.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/mempool", - "target": "/data", - "type": "bind" - } - ] - } - }, - "version": "v3.0.1" - }, - "mempool-api": { - "manifest": { - "app": { "bitcoin_integration": { - "pruning_support": false, "rpc_access": "read-only", "sync_required": true - }, + } + } + } + }, + "mempool-api": { + "version": "3.0.0", + "manifest": { + "app": { + "id": "mempool-api", + "name": "Mempool API", + "version": "3.0.0", + "description": "Backend API for mempool explorer.", "container": { + "image": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0", + "pull_policy": "if-not-present", + "network": "archy-net", "derived_env": [ { "key": "CORE_RPC_HOST", "template": "{{BITCOIN_HOST}}" } ], - "image": "git.tx1138.com/lfg2025/mempool-backend:v3.0.0", - "network": "archy-net", - "pull_policy": "if-not-present", "secret_env": [ { "key": "CORE_RPC_PASSWORD", @@ -3068,7 +3076,32 @@ }, "bitcoin:archival" ], - "description": "Backend API for mempool explorer.", + "resources": { + "memory_limit": "2Gi", + "disk_limit": "20Gi" + }, + "security": { + "capabilities": [], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [ + { + "host": 8999, + "container": 8999, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/mempool", + "target": "/data", + "options": [ + "rw" + ] + } + ], "environment": [ "MEMPOOL_BACKEND=electrum", "ELECTRUM_HOST=electrumx", @@ -3082,49 +3115,29 @@ "DATABASE_USERNAME=mempool" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8999", - "interval": "30s", "path": "/api/v1/backend-info", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "mempool-api", - "name": "Mempool API", - "ports": [ - { - "container": 8999, - "host": 8999, - "protocol": "tcp" - } - ], - "resources": { - "disk_limit": "20Gi", - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "3.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/mempool", - "target": "/data", - "type": "bind" - } - ] + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true, + "pruning_support": false + } } - }, - "version": "3.0.0" + } }, "morphos-server": { + "version": "1.0.0", "manifest": { "app": { + "id": "morphos-server", + "name": "MorphOS Server", + "version": "1.0.0", + "description": "MorphOS server platform. Decentralized application server.", "container": { "image": "archipelago/morphos-server:1.0.0", "image_signature": "cosign://...", @@ -3135,73 +3148,73 @@ "storage": "5Gi" } ], - "description": "MorphOS server platform. Decentralized application server.", + "resources": { + "cpu_limit": 2, + "memory_limit": "2Gi", + "disk_limit": "5Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "morphos-server" + }, + "ports": [ + { + "host": 8089, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/morphos-server", + "target": "/app/data", + "options": [ + "rw" + ] + } + ], "environment": [ "MORPHOS_ENV=production", "MORPHOS_DATA_DIR=/app/data" ], "health_check": { - "endpoint": "http://localhost:8086", - "interval": "30s", + "type": "http", + "endpoint": "http://127.0.0.1:8080", "path": "/health", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" - }, - "id": "morphos-server", - "name": "MorphOS Server", - "ports": [ - { - "container": 8080, - "host": 8086, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "5Gi", - "memory_limit": "2Gi" - }, - "security": { - "apparmor_profile": "morphos-server", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "1.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/morphos-server", - "target": "/app/data", - "type": "bind" - } - ] + "retries": 3 + } } - }, - "version": "1.0.0" + } }, "netbird": { + "version": "2.38.0", "manifest": { "app": { + "id": "netbird", + "name": "NetBird", + "version": "2.38.0", + "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.", "category": "networking", + "container_name": "netbird", "container": { + "image": "docker.io/library/nginx:1.27-alpine", + "pull_policy": "if-not-present", + "network": "netbird-net", "generated_certs": [ { "crt": "/var/lib/archipelago/netbird/tls.crt", "key": "/var/lib/archipelago/netbird/tls.key" } - ], - "image": "docker.io/library/nginx:1.27-alpine", - "network": "netbird-net", - "pull_policy": "if-not-present" + ] }, - "container_name": "netbird", "dependencies": [ { "app_id": "netbird-server" @@ -3213,55 +3226,6 @@ "storage": "1Gi" } ], - "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point — a TLS proxy in front of the dashboard + server.", - "environment": [], - "files": [ - { - "content": "server {\n listen 443 ssl;\n server_name _;\n\n # netbird's dashboard needs a secure context (window.crypto.subtle for\n # OIDC PKCE), so the proxy terminates TLS with a self-signed cert (#15).\n ssl_certificate /etc/nginx/tls.crt;\n ssl_certificate_key /etc/nginx/tls.key;\n\n # Rootless Podman can hand a container a new IP across restarts/reboots.\n # nginx resolves a literal upstream name ONCE at startup and caches it,\n # so after the IP moves every request 502s with \"host unreachable\"\n # (issue #15, observed live on .198: nginx pinned to a dead\n # netbird-dashboard IP). Fix: point `resolver` at the netbird-net\n # gateway (Podman's aardvark DNS) and use VARIABLE upstreams, which\n # forces nginx to re-resolve the container names at request time.\n resolver {{NETWORK_GATEWAY}} valid=10s ipv6=off;\n\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_http_version 1.1;\n\n location ~ ^/(relay|ws-proxy/) {\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_read_timeout 1d;\n }\n\n location ~ ^/(api|oauth2)(/|$) {\n # The dashboard is a SPA whose API/OIDC base URL is baked at build\n # time to one host:port. A single box is reached via several\n # addresses, so those fetches are cross-origin and the browser\n # blocks them with no Access-Control-Allow-Origin (#15, live on\n # .198). Reflect the caller's Origin and answer the CORS preflight.\n if ($request_method = OPTIONS) {\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n add_header Access-Control-Max-Age 86400 always;\n add_header Content-Length 0;\n return 204;\n }\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n }\n\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService|management\\.ProxyService)/ {\n set $nb_server netbird-server;\n grpc_pass grpc://$nb_server:80;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n }\n\n # OIDC callback routes are client-side SPA routes with NO prebuilt page\n # in the dashboard bundle, so proxying them straight through 404s —\n # which crashes the dashboard's auth init and shows \"Unauthenticated\"\n # with dead buttons (#15, live on .198: /nb-auth + /nb-silent-auth\n # returned 404). Serve index.html at these paths (URL unchanged) so\n # react-oidc boots and completes the login / silent-SSO.\n location ~ ^/(nb-auth|nb-silent-auth) {\n set $nb_dashboard netbird-dashboard;\n rewrite ^.*$ /index.html break;\n proxy_pass http://$nb_dashboard:80;\n }\n\n location / {\n set $nb_dashboard netbird-dashboard;\n proxy_pass http://$nb_dashboard:80;\n }\n}\n", - "overwrite": true, - "path": "/var/lib/archipelago/netbird/nginx.conf" - } - ], - "health_check": { - "endpoint": "localhost:443", - "interval": "30s", - "retries": 5, - "start_period": "20s", - "timeout": "5s", - "type": "tcp" - }, - "id": "netbird", - "interfaces": { - "main": { - "description": "Manage your self-hosted NetBird mesh VPN", - "name": "Dashboard", - "path": "/", - "port": 8087, - "protocol": "https", - "type": "ui" - } - }, - "metadata": { - "author": "NetBird", - "icon": "/assets/img/app-icons/netbird.svg", - "license": "BSD-3-Clause", - "repo": "https://github.com/netbirdio/netbird", - "tags": [ - "networking", - "vpn", - "wireguard", - "mesh" - ], - "website": "https://netbird.io" - }, - "name": "NetBird", - "ports": [ - { - "container": 443, - "host": 8087, - "protocol": "tcp" - } - ], "resources": { "memory_limit": "256Mi" }, @@ -3273,45 +3237,101 @@ "SETUID", "NET_BIND_SERVICE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "2.38.0", + "ports": [ + { + "host": 8087, + "container": 443, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "ro" - ], + "type": "bind", "source": "/var/lib/archipelago/netbird/nginx.conf", "target": "/etc/nginx/conf.d/default.conf", - "type": "bind" - }, - { "options": [ "ro" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/netbird/tls.crt", "target": "/etc/nginx/tls.crt", - "type": "bind" - }, - { "options": [ "ro" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/netbird/tls.key", "target": "/etc/nginx/tls.key", - "type": "bind" + "options": [ + "ro" + ] } - ] + ], + "environment": [], + "files": [ + { + "path": "/var/lib/archipelago/netbird/nginx.conf", + "overwrite": true, + "content": "server {\n listen 443 ssl;\n server_name _;\n\n # netbird's dashboard needs a secure context (window.crypto.subtle for\n # OIDC PKCE), so the proxy terminates TLS with a self-signed cert (#15).\n ssl_certificate /etc/nginx/tls.crt;\n ssl_certificate_key /etc/nginx/tls.key;\n\n # Rootless Podman can hand a container a new IP across restarts/reboots.\n # nginx resolves a literal upstream name ONCE at startup and caches it,\n # so after the IP moves every request 502s with \"host unreachable\"\n # (issue #15, observed live on .198: nginx pinned to a dead\n # netbird-dashboard IP). Fix: point `resolver` at the netbird-net\n # gateway (Podman's aardvark DNS) and use VARIABLE upstreams, which\n # forces nginx to re-resolve the container names at request time.\n resolver {{NETWORK_GATEWAY}} valid=10s ipv6=off;\n\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_http_version 1.1;\n\n location ~ ^/(relay|ws-proxy/) {\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_read_timeout 1d;\n }\n\n location ~ ^/(api|oauth2)(/|$) {\n # The dashboard is a SPA whose API/OIDC base URL is baked at build\n # time to one host:port. A single box is reached via several\n # addresses, so those fetches are cross-origin and the browser\n # blocks them with no Access-Control-Allow-Origin (#15, live on\n # .198). Reflect the caller's Origin and answer the CORS preflight.\n if ($request_method = OPTIONS) {\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n add_header Access-Control-Max-Age 86400 always;\n add_header Content-Length 0;\n return 204;\n }\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n }\n\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService|management\\.ProxyService)/ {\n set $nb_server netbird-server;\n grpc_pass grpc://$nb_server:80;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n }\n\n # OIDC callback routes are client-side SPA routes with NO prebuilt page\n # in the dashboard bundle, so proxying them straight through 404s \u2014\n # which crashes the dashboard's auth init and shows \"Unauthenticated\"\n # with dead buttons (#15, live on .198: /nb-auth + /nb-silent-auth\n # returned 404). Serve index.html at these paths (URL unchanged) so\n # react-oidc boots and completes the login / silent-SSO.\n location ~ ^/(nb-auth|nb-silent-auth) {\n set $nb_dashboard netbird-dashboard;\n rewrite ^.*$ /index.html break;\n proxy_pass http://$nb_dashboard:80;\n }\n\n location / {\n set $nb_dashboard netbird-dashboard;\n proxy_pass http://$nb_dashboard:80;\n }\n}\n" + } + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:443", + "interval": "30s", + "timeout": "5s", + "retries": 5, + "start_period": "20s" + }, + "interfaces": { + "main": { + "name": "Dashboard", + "description": "Manage your self-hosted NetBird mesh VPN", + "type": "ui", + "port": 8087, + "protocol": "https", + "path": "/" + } + }, + "metadata": { + "author": "NetBird", + "icon": "/assets/img/app-icons/netbird.svg", + "website": "https://netbird.io", + "repo": "https://github.com/netbirdio/netbird", + "license": "BSD-3-Clause", + "tags": [ + "networking", + "vpn", + "wireguard", + "mesh" + ] + } } - }, - "version": "2.38.0" + } }, "netbird-dashboard": { + "version": "2.38.0", "manifest": { "app": { + "id": "netbird-dashboard", + "name": "NetBird Dashboard", + "version": "2.38.0", + "description": "NetBird management dashboard (SPA). Internal stack member served through the netbird proxy.", "category": "networking", + "container_name": "netbird-dashboard", "container": { + "image": "docker.io/netbirdio/dashboard:v2.38.0", + "pull_policy": "if-not-present", + "network": "netbird-net", + "network_aliases": [ + "netbird-dashboard" + ], "derived_env": [ { "key": "NETBIRD_MGMT_API_ENDPOINT", @@ -3325,21 +3345,29 @@ "key": "AUTH_AUTHORITY", "template": "https://{{HOST_IP}}:8087/oauth2" } - ], - "image": "docker.io/netbirdio/dashboard:v2.38.0", - "network": "netbird-net", - "network_aliases": [ - "netbird-dashboard" - ], - "pull_policy": "if-not-present" + ] }, - "container_name": "netbird-dashboard", "dependencies": [ { "app_id": "netbird-server" } ], - "description": "NetBird management dashboard (SPA). Internal stack member served through the netbird proxy.", + "resources": { + "memory_limit": "256Mi" + }, + "security": { + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "SETGID", + "SETUID", + "NET_BIND_SERVICE" + ], + "readonly_root": false, + "network_policy": "isolated" + }, + "ports": [], + "volumes": [], "environment": [ "AUTH_AUDIENCE=netbird-dashboard", "AUTH_CLIENT_ID=netbird-dashboard", @@ -3353,124 +3381,65 @@ "LETSENCRYPT_DOMAIN=none" ], "health_check": { + "type": "tcp", "endpoint": "localhost:80", "interval": "30s", - "retries": 5, - "start_period": "20s", "timeout": "5s", - "type": "tcp" + "retries": 5, + "start_period": "20s" }, - "id": "netbird-dashboard", "metadata": { "author": "NetBird", "icon": "/assets/img/app-icons/netbird.svg", - "license": "BSD-3-Clause", + "website": "https://netbird.io", "repo": "https://github.com/netbirdio/dashboard", + "license": "BSD-3-Clause", "tags": [ "networking", "vpn", "dashboard" - ], - "website": "https://netbird.io" - }, - "name": "NetBird Dashboard", - "ports": [], - "resources": { - "memory_limit": "256Mi" - }, - "security": { - "capabilities": [ - "CHOWN", - "DAC_OVERRIDE", - "SETGID", - "SETUID", - "NET_BIND_SERVICE" - ], - "network_policy": "isolated", - "readonly_root": false - }, - "version": "2.38.0", - "volumes": [] + ] + } } - }, - "version": "2.38.0" + } }, "netbird-server": { + "version": "0.71.2", "manifest": { "app": { + "id": "netbird-server", + "name": "NetBird Server", + "version": "0.71.2", + "description": "NetBird combined management / signal / relay server with an embedded identity provider and STUN. Backend for the self-hosted NetBird mesh VPN.", "category": "networking", + "container_name": "netbird-server", "container": { - "custom_args": [ - "--config", - "/etc/netbird/config.yaml" - ], - "generated_secrets": [ - { - "kind": "base64", - "name": "netbird-relay-auth-secret" - }, - { - "kind": "base64", - "name": "netbird-store-encryption-key" - } - ], "image": "docker.io/netbirdio/netbird-server:0.71.2", + "pull_policy": "if-not-present", "network": "netbird-net", "network_aliases": [ "netbird-server" ], - "pull_policy": "if-not-present" + "generated_secrets": [ + { + "name": "netbird-relay-auth-secret", + "kind": "base64" + }, + { + "name": "netbird-store-encryption-key", + "kind": "base64" + } + ], + "custom_args": [ + "--config", + "/etc/netbird/config.yaml" + ] }, - "container_name": "netbird-server", "dependencies": [ { "storage": "1Gi" } ], - "description": "NetBird combined management / signal / relay server with an embedded identity provider and STUN. Backend for the self-hosted NetBird mesh VPN.", - "environment": [], - "files": [ - { - "content": "server:\n listenAddress: \":80\"\n exposedAddress: \"https://{{HOST_IP}}:8087\"\n stunPorts:\n - 3478\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n authSecret: \"{{secret:netbird-relay-auth-secret}}\"\n dataDir: \"/var/lib/netbird\"\n auth:\n issuer: \"https://{{HOST_IP}}:8087/oauth2\"\n localAuthDisabled: false\n signKeyRefreshEnabled: false\n dashboardRedirectURIs:\n - \"https://{{HOST_IP}}:8087/nb-auth\"\n - \"https://{{HOST_IP}}:8087/nb-silent-auth\"\n dashboardPostLogoutRedirectURIs:\n - \"https://{{HOST_IP}}:8087/\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n store:\n engine: \"sqlite\"\n encryptionKey: \"{{secret:netbird-store-encryption-key}}\"\n", - "overwrite": true, - "path": "/var/lib/archipelago/netbird/config.yaml" - } - ], - "health_check": { - "endpoint": "localhost:80", - "interval": "30s", - "retries": 10, - "start_period": "30s", - "timeout": "5s", - "type": "tcp" - }, - "id": "netbird-server", - "metadata": { - "author": "NetBird", - "icon": "/assets/img/app-icons/netbird.svg", - "license": "BSD-3-Clause", - "repo": "https://github.com/netbirdio/netbird", - "tags": [ - "networking", - "vpn", - "wireguard", - "mesh" - ], - "website": "https://netbird.io" - }, - "name": "NetBird Server", - "ports": [ - { - "container": 80, - "host": 8086, - "protocol": "tcp" - }, - { - "container": 3478, - "host": 3478, - "protocol": "udp" - } - ], "resources": { "memory_limit": "1Gi" }, @@ -3478,86 +3447,93 @@ "capabilities": [ "NET_BIND_SERVICE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "0.71.2", - "volumes": [ + "ports": [ { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/netbird/data", - "target": "/var/lib/netbird", - "type": "bind" + "host": 8086, + "container": 80, + "protocol": "tcp" }, { + "host": 3478, + "container": 3478, + "protocol": "udp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/netbird/data", + "target": "/var/lib/netbird", "options": [ - "ro" - ], + "rw" + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/netbird/config.yaml", "target": "/etc/netbird/config.yaml", - "type": "bind" + "options": [ + "ro" + ] } - ] + ], + "environment": [], + "files": [ + { + "path": "/var/lib/archipelago/netbird/config.yaml", + "overwrite": true, + "content": "server:\n listenAddress: \":80\"\n exposedAddress: \"https://{{HOST_IP}}:8087\"\n stunPorts:\n - 3478\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n authSecret: \"{{secret:netbird-relay-auth-secret}}\"\n dataDir: \"/var/lib/netbird\"\n auth:\n issuer: \"https://{{HOST_IP}}:8087/oauth2\"\n localAuthDisabled: false\n signKeyRefreshEnabled: false\n dashboardRedirectURIs:\n - \"https://{{HOST_IP}}:8087/nb-auth\"\n - \"https://{{HOST_IP}}:8087/nb-silent-auth\"\n dashboardPostLogoutRedirectURIs:\n - \"https://{{HOST_IP}}:8087/\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n store:\n engine: \"sqlite\"\n encryptionKey: \"{{secret:netbird-store-encryption-key}}\"\n" + } + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:80", + "interval": "30s", + "timeout": "5s", + "retries": 10, + "start_period": "30s" + }, + "metadata": { + "author": "NetBird", + "icon": "/assets/img/app-icons/netbird.svg", + "website": "https://netbird.io", + "repo": "https://github.com/netbirdio/netbird", + "license": "BSD-3-Clause", + "tags": [ + "networking", + "vpn", + "wireguard", + "mesh" + ] + } } - }, - "version": "0.71.2" + } }, "nextcloud": { + "version": "29", "image": "146.59.87.168:3000/lfg2025/nextcloud:29", "manifest": { "app": { + "id": "nextcloud", + "name": "Nextcloud", + "version": "29", + "description": "Your own private cloud. File sync, calendars, contacts.", "container": { "image": "146.59.87.168:3000/lfg2025/nextcloud:29", - "network": "pasta", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "pasta" }, "dependencies": [ { "storage": "10Gi" } ], - "description": "Your own private cloud. File sync, calendars, contacts.", - "environment": [], - "health_check": { - "endpoint": "localhost:80", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "nextcloud", - "interfaces": { - "main": { - "description": "Nextcloud file and collaboration dashboard", - "name": "Web UI", - "path": "/", - "port": 8085, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "author": "Nextcloud", - "category": "data", - "icon": "/assets/img/app-icons/nextcloud.webp", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://github.com/nextcloud/server" - }, - "name": "Nextcloud", - "ports": [ - { - "container": 80, - "host": 8085, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "10Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -3567,44 +3543,111 @@ "DAC_OVERRIDE", "NET_BIND_SERVICE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "29", + "ports": [ + { + "host": 8085, + "container": 80, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/nextcloud", "target": "/var/www/html", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:80", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "Nextcloud file and collaboration dashboard", + "type": "ui", + "port": 8085, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/nextcloud.webp", + "category": "data", + "author": "Nextcloud", + "repo": "https://github.com/nextcloud/server", + "launch": { + "open_in_new_tab": true + } + } } - }, - "version": "29" + } }, "nginx-proxy-manager": { - "image": "146.59.87.168:3000/lfg2025/nginx-proxy-manager:latest", - "version": "latest" + "version": "latest", + "image": "146.59.87.168:3000/lfg2025/nginx-proxy-manager:latest" }, "nostr-rs-relay": { + "version": "0.9.0", "image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0", "manifest": { "app": { + "id": "nostr-rs-relay", + "name": "Nostr Relay (Rust)", + "version": "0.8.0", + "description": "High-performance Nostr relay written in Rust. Host your own decentralized social media relay and earn networking profits.", "container": { - "data_uid": "1000:1000", "image": "scsibug/nostr-rs-relay:0.8.9", "image_signature": "cosign://...", - "pull_policy": "verify-signature" + "pull_policy": "verify-signature", + "data_uid": "1000:1000" }, "dependencies": [ { "storage": "10Gi" } ], - "description": "High-performance Nostr relay written in Rust. Host your own decentralized social media relay and earn networking profits.", + "resources": { + "cpu_limit": 2, + "memory_limit": "1Gi", + "disk_limit": "10Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "nostr-relay" + }, + "ports": [ + { + "host": 18081, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/nostr-relay", + "target": "/usr/src/app/db", + "options": [ + "rw" + ] + } + ], "environment": [ "RELAY_NAME=Archipelago Nostr Relay", "RELAY_DESCRIPTION=Self-hosted Nostr relay on Archipelago", @@ -3612,79 +3655,49 @@ "MAX_SUBSCRIPTIONS=100" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8080", - "interval": "30s", "path": "/", - "retries": 5, + "interval": "30s", "timeout": "30s", - "type": "http" + "retries": 5 }, - "id": "nostr-rs-relay", - "name": "Nostr Relay (Rust)", "nostr_integration": { - "event_storage": "sqlite", + "relay_type": "public", "monetization_enabled": true, - "relay_type": "public" - }, - "ports": [ - { - "container": 8080, - "host": 18081, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "10Gi", - "memory_limit": "1Gi" - }, - "security": { - "apparmor_profile": "nostr-relay", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "0.8.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/nostr-relay", - "target": "/usr/src/app/db", - "type": "bind" - } - ] + "event_storage": "sqlite" + } } - }, - "version": "0.9.0" + } }, "nostr-vpn": { - "image": "146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7", - "version": "v0.3.7" + "version": "v0.3.7", + "image": "146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7" }, "ollama": { - "image": "146.59.87.168:3000/lfg2025/ollama:latest", - "version": "latest" + "version": "latest", + "image": "146.59.87.168:3000/lfg2025/ollama:latest" }, "penpot": { + "version": "2.4", "image": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "images": { + "penpot-frontend": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "penpot-backend": "146.59.87.168:3000/lfg2025/penpot-backend:2.4", "penpot-exporter": "146.59.87.168:3000/lfg2025/penpot-exporter:2.4", - "penpot-frontend": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "penpot-postgres": "146.59.87.168:3000/lfg2025/postgres:15", "penpot-valkey": "146.59.87.168:3000/lfg2025/valkey:8.1" - }, - "version": "2.4" + } }, "photoprism": { + "version": "240915", "image": "146.59.87.168:3000/lfg2025/photoprism:240915", "manifest": { "app": { + "id": "photoprism", + "name": "PhotoPrism", + "version": "240915", + "description": "AI-powered photo management with facial recognition.", "container": { "image": "146.59.87.168:3000/lfg2025/photoprism:240915", "pull_policy": "if-not-present" @@ -3694,49 +3707,9 @@ "storage": "10Gi" } ], - "description": "AI-powered photo management with facial recognition.", - "environment": [ - "PHOTOPRISM_ADMIN_PASSWORD=archipelago", - "PHOTOPRISM_DEFAULT_LOCALE=en" - ], - "health_check": { - "endpoint": "localhost:2342", - "interval": "60s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "photoprism", - "interfaces": { - "main": { - "description": "PhotoPrism photo library", - "name": "Web UI", - "path": "/", - "port": 2342, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "author": "PhotoPrism", - "category": "data", - "icon": "/assets/img/app-icons/photoprism.svg", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://github.com/photoprism/photoprism" - }, - "name": "PhotoPrism", - "ports": [ - { - "container": 2342, - "host": 2342, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "10Gi", - "memory_limit": "1Gi" + "memory_limit": "1Gi", + "disk_limit": "10Gi" }, "security": { "capabilities": [ @@ -3744,75 +3717,82 @@ "SETUID", "SETGID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "240915", + "ports": [ + { + "host": 2342, + "container": 2342, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/photoprism", "target": "/photoprism/storage", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "PHOTOPRISM_ADMIN_PASSWORD=archipelago", + "PHOTOPRISM_DEFAULT_LOCALE=en" + ], + "health_check": { + "type": "tcp", + "endpoint": "localhost:2342", + "interval": "60s", + "timeout": "5s", + "retries": 3 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "PhotoPrism photo library", + "type": "ui", + "port": 2342, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/photoprism.svg", + "category": "data", + "author": "PhotoPrism", + "repo": "https://github.com/photoprism/photoprism", + "launch": { + "open_in_new_tab": true + } + } } - }, - "version": "240915" + } }, "portainer": { + "version": "2.19.4", "image": "146.59.87.168:3000/lfg2025/portainer:2.19.4", "manifest": { "app": { + "id": "portainer", + "name": "Portainer", + "version": "2.19.4", + "description": "Container management web UI for the local Podman socket.", "category": "development", "container": { - "data_uid": "1000:1000", "image": "146.59.87.168:3000/lfg2025/portainer:2.19.4", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "data_uid": "1000:1000" }, "dependencies": [ { "storage": "1Gi" } ], - "description": "Container management web UI for the local Podman socket.", - "environment": [], - "id": "portainer", - "interfaces": { - "main": { - "description": "Portainer web interface", - "name": "Web UI", - "path": "/", - "port": 9000, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "features": [ - "Container management dashboard", - "Local Podman socket access", - "Compose stack storage" - ], - "icon": "/assets/img/app-icons/portainer.webp", - "launch": { - "open_in_new_tab": true - }, - "tier": "optional" - }, - "name": "Portainer", - "ports": [ - { - "container": 9000, - "host": 9000, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "1Gi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "1Gi" }, "security": { "capabilities": [ @@ -3821,44 +3801,77 @@ "SETGID", "DAC_OVERRIDE" ], - "network_policy": "isolated", + "readonly_root": false, "no_new_privileges": true, - "readonly_root": false + "network_policy": "isolated" }, - "version": "2.19.4", + "ports": [ + { + "host": 9000, + "container": 9000, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/portainer", "target": "/data", - "type": "bind" - }, - { "options": [ "rw" - ], + ] + }, + { + "type": "bind", "source": "/var/lib/archipelago/portainer/compose", "target": "/data/compose", - "type": "bind" - }, - { "options": [ "rw" - ], + ] + }, + { + "type": "bind", "source": "/run/user/1000/podman/podman.sock", "target": "/var/run/docker.sock", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "interfaces": { + "main": { + "name": "Web UI", + "description": "Portainer web interface", + "type": "ui", + "port": 9000, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/portainer.webp", + "tier": "optional", + "launch": { + "open_in_new_tab": true + }, + "features": [ + "Container management dashboard", + "Local Podman socket access", + "Compose stack storage" + ] + } } - }, - "version": "2.19.4" + } }, "router": { + "version": "1.0.0", "manifest": { "app": { + "id": "router", + "name": "Mesh Router", + "version": "1.0.0", + "description": "Mesh routing and local network management. Provides device discovery, routing, and network topology visualization.", "container": { "image": "archipelago/router:1.0.0", "image_signature": "cosign://...", @@ -3869,96 +3882,96 @@ "storage": "500Mi" } ], - "description": "Mesh routing and local network management. Provides device discovery, routing, and network topology visualization.", + "resources": { + "cpu_limit": 2, + "memory_limit": "512Mi", + "disk_limit": "500Mi" + }, + "security": { + "capabilities": [ + "NET_ADMIN", + "NET_RAW" + ], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "host", + "apparmor_profile": "router" + }, + "ports": [ + { + "host": 8084, + "container": 8080, + "protocol": "tcp" + }, + { + "host": 5353, + "container": 5353, + "protocol": "udp" + }, + { + "host": 1900, + "container": 1900, + "protocol": "udp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/router", + "target": "/app/data", + "options": [ + "rw" + ] + }, + { + "type": "bind", + "source": "/var/run/dbus", + "target": "/var/run/dbus", + "options": [ + "ro" + ] + } + ], "environment": [ "NETWORK_INTERFACE=eth0", "MESH_ENABLED=true", "DEVICE_DISCOVERY=true" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8084", - "interval": "30s", "path": "/health", - "retries": 3, + "interval": "30s", "timeout": "5s", - "type": "http" + "retries": 3 }, - "id": "router", - "name": "Mesh Router", "networking": { - "device_discovery": true, - "local_network_access": true, "mesh_enabled": true, + "local_network_access": true, + "device_discovery": true, "routing_protocols": [ "olsr", "babel" ] - }, - "ports": [ - { - "container": 8080, - "host": 8084, - "protocol": "tcp" - }, - { - "container": 5353, - "host": 5353, - "protocol": "udp" - }, - { - "container": 1900, - "host": 1900, - "protocol": "udp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "500Mi", - "memory_limit": "512Mi" - }, - "security": { - "apparmor_profile": "router", - "capabilities": [ - "NET_ADMIN", - "NET_RAW" - ], - "network_policy": "host", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "1.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/router", - "target": "/app/data", - "type": "bind" - }, - { - "options": [ - "ro" - ], - "source": "/var/run/dbus", - "target": "/var/run/dbus", - "type": "bind" - } - ] + } } - }, - "version": "1.0.0" + } }, "routstr": { - "image": "146.59.87.168:3000/lfg2025/routstr:v0.4.3", - "version": "v0.4.3" + "version": "v0.4.3", + "image": "146.59.87.168:3000/lfg2025/routstr:v0.4.3" }, "searxng": { + "version": "latest", "image": "146.59.87.168:3000/lfg2025/searxng:latest", "manifest": { "app": { + "id": "searxng", + "name": "SearXNG", + "version": "1.0.0", + "description": "Privacy-respecting metasearch engine. Search the web without tracking.", "container": { "image": "146.59.87.168:3000/lfg2025/searxng:latest", "pull_policy": "if-not-present" @@ -3968,60 +3981,60 @@ "storage": "2Gi" } ], - "description": "Privacy-respecting metasearch engine. Search the web without tracking.", + "resources": { + "cpu_limit": 2, + "memory_limit": "1Gi", + "disk_limit": "2Gi" + }, + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "user": 1000, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "searxng" + }, + "ports": [ + { + "host": 8888, + "container": 8080, + "protocol": "tcp" + } + ], + "volumes": [ + { + "type": "bind", + "source": "/var/lib/archipelago/searxng", + "target": "/etc/searxng", + "options": [ + "rw" + ] + } + ], "environment": [ "SEARXNG_HOSTNAME=localhost", "SEARXNG_BIND_ADDRESS=0.0.0.0:8080" ], "health_check": { + "type": "http", "endpoint": "http://localhost:8080", - "interval": "30s", "path": "/", - "retries": 5, + "interval": "30s", "timeout": "30s", - "type": "http" - }, - "id": "searxng", - "name": "SearXNG", - "ports": [ - { - "container": 8080, - "host": 8888, - "protocol": "tcp" - } - ], - "resources": { - "cpu_limit": 2, - "disk_limit": "2Gi", - "memory_limit": "1Gi" - }, - "security": { - "apparmor_profile": "searxng", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default", - "user": 1000 - }, - "version": "1.0.0", - "volumes": [ - { - "options": [ - "rw" - ], - "source": "/var/lib/archipelago/searxng", - "target": "/etc/searxng", - "type": "bind" - } - ] + "retries": 5 + } } - }, - "version": "latest" + } }, "strfry": { + "version": "0.9.0", "manifest": { "app": { + "id": "strfry", + "name": "Strfry Nostr Relay", + "version": "0.9.0", + "description": "Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage.", "container": { "image": "dockurr/strfry:1.0.4", "image_signature": "cosign://...", @@ -4032,128 +4045,97 @@ "storage": "5Gi" } ], - "description": "Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage.", - "files": [ - { - "content": "##\n## Default strfry config\n##\n\n# Directory that contains the strfry LMDB database (restart required)\ndb = \"./strfry-db/\"\n\ndbParams {\n # Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)\n maxreaders = 256\n\n # Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required)\n mapsize = 10995116277760\n\n # Disables read-ahead when accessing the LMDB mapping. Reduces IO activity when DB size is larger than RAM. (restart required)\n noReadAhead = false\n}\n\nevents {\n # Maximum size of normalised JSON, in bytes\n maxEventSize = 65536\n\n # Events newer than this will be rejected\n rejectEventsNewerThanSeconds = 900\n\n # Events older than this will be rejected\n rejectEventsOlderThanSeconds = 94608000\n\n # Ephemeral events older than this will be rejected\n rejectEphemeralEventsOlderThanSeconds = 60\n\n # Ephemeral events will be deleted from the DB when older than this\n ephemeralEventsLifetimeSeconds = 300\n\n # Maximum number of tags allowed\n maxNumTags = 2000\n\n # Maximum size for tag values, in bytes\n maxTagValSize = 1024\n}\n\nrelay {\n # Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required)\n bind = \"0.0.0.0\"\n\n # Port to open for the nostr websocket protocol (restart required)\n port = 7777\n\n # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)\n nofiles = 0\n\n # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)\n realIpHeader = \"\"\n\n info {\n # NIP-11: Name of this server. Short/descriptive (< 30 characters)\n name = \"Archipelago Strfry Relay\"\n\n # NIP-11: Detailed information about relay, free-form\n description = \"Self-hosted strfry Nostr relay on Archipelago.\"\n\n # NIP-11: Administrative nostr pubkey, for contact purposes\n pubkey = \"\"\n\n # NIP-11: Alternative administrative contact (email, website, etc)\n contact = \"\"\n\n # NIP-11: URL pointing to an image to be used as an icon for the relay\n icon = \"\"\n\n # List of supported lists as JSON array, or empty string to use default. Example: \"[1,2]\"\n nips = \"\"\n }\n\n # Maximum accepted incoming websocket frame size (should be larger than max event) (restart required)\n maxWebsocketPayloadSize = 131072\n\n # Maximum number of filters allowed in a REQ\n maxReqFilterSize = 200\n\n # Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required)\n autoPingSeconds = 55\n\n # If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy)\n enableTcpKeepalive = false\n\n # How much uninterrupted CPU time a REQ query should get during its DB scan\n queryTimesliceBudgetMicroseconds = 10000\n\n # Maximum records that can be returned per filter\n maxFilterLimit = 500\n\n # Maximum number of subscriptions (concurrent REQs) a connection can have open at any time\n maxSubsPerConnection = 20\n\n writePolicy {\n # If non-empty, path to an executable script that implements the writePolicy plugin logic\n plugin = \"/app/write-policy.py\"\n }\n\n compression {\n # Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required)\n enabled = true\n\n # Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required)\n slidingWindow = true\n }\n\n logging {\n # Dump all incoming messages\n dumpInAll = false\n\n # Dump all incoming EVENT messages\n dumpInEvents = false\n\n # Dump all incoming REQ/CLOSE messages\n dumpInReqs = false\n\n # Log performance metrics for initial REQ database scans\n dbScanPerf = false\n\n # Log reason for invalid event rejection? Can be disabled to silence excessive logging\n invalidEvents = true\n }\n\n numThreads {\n # Ingester threads: route incoming requests, validate events/sigs (restart required)\n ingester = 3\n\n # reqWorker threads: Handle initial DB scan for events (restart required)\n reqWorker = 3\n\n # reqMonitor threads: Handle filtering of new events (restart required)\n reqMonitor = 3\n\n # negentropy threads: Handle negentropy protocol messages (restart required)\n negentropy = 2\n }\n\n negentropy {\n # Support negentropy protocol messages\n enabled = true\n\n # Maximum records that sync will process before returning an error\n maxSyncEvents = 1000000\n }\n}\n", - "overwrite": true, - "path": "/var/lib/archipelago/strfry-config/strfry.conf" - } - ], - "health_check": { - "endpoint": "http://localhost:8090", - "interval": "30s", - "path": "/health", - "retries": 3, - "timeout": "5s", - "type": "http" + "resources": { + "cpu_limit": 1, + "memory_limit": "512Mi", + "disk_limit": "5Gi" }, - "id": "strfry", - "name": "Strfry Nostr Relay", - "nostr_integration": { - "monetization_enabled": true, - "relay_type": "public" + "security": { + "capabilities": [], + "readonly_root": true, + "no_new_privileges": true, + "seccomp_profile": "default", + "network_policy": "isolated", + "apparmor_profile": "nostr-relay" }, "ports": [ { - "container": 7777, "host": 8090, + "container": 7777, "protocol": "tcp" } ], - "resources": { - "cpu_limit": 1, - "disk_limit": "5Gi", - "memory_limit": "512Mi" - }, - "security": { - "apparmor_profile": "nostr-relay", - "capabilities": [], - "network_policy": "isolated", - "no_new_privileges": true, - "readonly_root": true, - "seccomp_profile": "default" - }, - "version": "0.9.0", "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/strfry", "target": "/app/strfry-db", - "type": "bind" + "options": [ + "rw" + ] }, { - "options": [ - "ro" - ], + "type": "bind", "source": "/var/lib/archipelago/strfry-config/strfry.conf", "target": "/etc/strfry.conf", - "type": "bind" + "options": [ + "ro" + ] } - ] + ], + "files": [ + { + "path": "/var/lib/archipelago/strfry-config/strfry.conf", + "overwrite": true, + "content": "##\n## Default strfry config\n##\n\n# Directory that contains the strfry LMDB database (restart required)\ndb = \"./strfry-db/\"\n\ndbParams {\n # Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)\n maxreaders = 256\n\n # Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required)\n mapsize = 10995116277760\n\n # Disables read-ahead when accessing the LMDB mapping. Reduces IO activity when DB size is larger than RAM. (restart required)\n noReadAhead = false\n}\n\nevents {\n # Maximum size of normalised JSON, in bytes\n maxEventSize = 65536\n\n # Events newer than this will be rejected\n rejectEventsNewerThanSeconds = 900\n\n # Events older than this will be rejected\n rejectEventsOlderThanSeconds = 94608000\n\n # Ephemeral events older than this will be rejected\n rejectEphemeralEventsOlderThanSeconds = 60\n\n # Ephemeral events will be deleted from the DB when older than this\n ephemeralEventsLifetimeSeconds = 300\n\n # Maximum number of tags allowed\n maxNumTags = 2000\n\n # Maximum size for tag values, in bytes\n maxTagValSize = 1024\n}\n\nrelay {\n # Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required)\n bind = \"0.0.0.0\"\n\n # Port to open for the nostr websocket protocol (restart required)\n port = 7777\n\n # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)\n nofiles = 0\n\n # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)\n realIpHeader = \"\"\n\n info {\n # NIP-11: Name of this server. Short/descriptive (< 30 characters)\n name = \"Archipelago Strfry Relay\"\n\n # NIP-11: Detailed information about relay, free-form\n description = \"Self-hosted strfry Nostr relay on Archipelago.\"\n\n # NIP-11: Administrative nostr pubkey, for contact purposes\n pubkey = \"\"\n\n # NIP-11: Alternative administrative contact (email, website, etc)\n contact = \"\"\n\n # NIP-11: URL pointing to an image to be used as an icon for the relay\n icon = \"\"\n\n # List of supported lists as JSON array, or empty string to use default. Example: \"[1,2]\"\n nips = \"\"\n }\n\n # Maximum accepted incoming websocket frame size (should be larger than max event) (restart required)\n maxWebsocketPayloadSize = 131072\n\n # Maximum number of filters allowed in a REQ\n maxReqFilterSize = 200\n\n # Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required)\n autoPingSeconds = 55\n\n # If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy)\n enableTcpKeepalive = false\n\n # How much uninterrupted CPU time a REQ query should get during its DB scan\n queryTimesliceBudgetMicroseconds = 10000\n\n # Maximum records that can be returned per filter\n maxFilterLimit = 500\n\n # Maximum number of subscriptions (concurrent REQs) a connection can have open at any time\n maxSubsPerConnection = 20\n\n writePolicy {\n # If non-empty, path to an executable script that implements the writePolicy plugin logic\n plugin = \"/app/write-policy.py\"\n }\n\n compression {\n # Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required)\n enabled = true\n\n # Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required)\n slidingWindow = true\n }\n\n logging {\n # Dump all incoming messages\n dumpInAll = false\n\n # Dump all incoming EVENT messages\n dumpInEvents = false\n\n # Dump all incoming REQ/CLOSE messages\n dumpInReqs = false\n\n # Log performance metrics for initial REQ database scans\n dbScanPerf = false\n\n # Log reason for invalid event rejection? Can be disabled to silence excessive logging\n invalidEvents = true\n }\n\n numThreads {\n # Ingester threads: route incoming requests, validate events/sigs (restart required)\n ingester = 3\n\n # reqWorker threads: Handle initial DB scan for events (restart required)\n reqWorker = 3\n\n # reqMonitor threads: Handle filtering of new events (restart required)\n reqMonitor = 3\n\n # negentropy threads: Handle negentropy protocol messages (restart required)\n negentropy = 2\n }\n\n negentropy {\n # Support negentropy protocol messages\n enabled = true\n\n # Maximum records that sync will process before returning an error\n maxSyncEvents = 1000000\n }\n}\n" + } + ], + "health_check": { + "type": "http", + "endpoint": "http://127.0.0.1:7777", + "path": "/health", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "nostr_integration": { + "relay_type": "public", + "monetization_enabled": true + } } - }, - "version": "0.9.0" + } }, "tailscale": { - "image": "146.59.87.168:3000/lfg2025/tailscale:stable", - "version": "stable" + "version": "stable", + "image": "146.59.87.168:3000/lfg2025/tailscale:stable" }, "uptime-kuma": { + "version": "1", "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", "manifest": { "app": { + "id": "uptime-kuma", + "name": "Uptime Kuma", + "version": "1.23.0", + "description": "Self-hosted uptime monitoring.", "container": { + "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", + "pull_policy": "if-not-present", + "network": "pasta", "custom_args": [ "--", "node", "server/server.js" - ], - "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", - "network": "pasta", - "pull_policy": "if-not-present" + ] }, "dependencies": [ { "storage": "1Gi" } ], - "description": "Self-hosted uptime monitoring.", - "environment": [ - "TZ=UTC" - ], - "health_check": { - "endpoint": "localhost:3001", - "interval": "30s", - "path": "/", - "retries": 3, - "timeout": "5s", - "type": "http" - }, - "id": "uptime-kuma", - "metadata": { - "author": "Uptime Kuma", - "category": "data", - "icon": "/assets/img/app-icons/uptime-kuma.webp", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://github.com/louislam/uptime-kuma", - "tier": "recommended" - }, - "name": "Uptime Kuma", - "ports": [ - { - "container": 3001, - "host": 3002, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "1Gi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "1Gi" }, "security": { "capabilities": [ @@ -4162,79 +4144,72 @@ "SETUID", "SETGID" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "1.23.0", + "ports": [ + { + "host": 3002, + "container": 3001, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/uptime-kuma", "target": "/app/data", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [ + "TZ=UTC" + ], + "health_check": { + "type": "http", + "endpoint": "localhost:3001", + "path": "/", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "metadata": { + "icon": "/assets/img/app-icons/uptime-kuma.webp", + "category": "data", + "tier": "recommended", + "author": "Uptime Kuma", + "repo": "https://github.com/louislam/uptime-kuma", + "launch": { + "open_in_new_tab": true + } + } } - }, - "version": "1" + } }, "vaultwarden": { + "version": "1.30.0-alpine", "image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine", "manifest": { "app": { + "id": "vaultwarden", + "name": "Vaultwarden", + "version": "1.30.0", + "description": "Self-hosted password vault with zero-knowledge encryption.", "container": { "image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine", - "network": "pasta", - "pull_policy": "if-not-present" + "pull_policy": "if-not-present", + "network": "pasta" }, "dependencies": [ { "storage": "1Gi" } ], - "description": "Self-hosted password vault with zero-knowledge encryption.", - "environment": [], - "health_check": { - "endpoint": "localhost:80", - "interval": "30s", - "retries": 3, - "timeout": "5s", - "type": "tcp" - }, - "id": "vaultwarden", - "interfaces": { - "main": { - "description": "Vaultwarden web vault", - "name": "Web UI", - "path": "/", - "port": 8082, - "protocol": "http", - "type": "ui" - } - }, - "metadata": { - "author": "Vaultwarden", - "category": "data", - "icon": "/assets/img/app-icons/vaultwarden.webp", - "launch": { - "open_in_new_tab": true - }, - "repo": "https://github.com/dani-garcia/vaultwarden", - "tier": "recommended" - }, - "name": "Vaultwarden", - "ports": [ - { - "container": 80, - "host": 8082, - "protocol": "tcp" - } - ], "resources": { - "disk_limit": "1Gi", - "memory_limit": "256Mi" + "memory_limit": "256Mi", + "disk_limit": "1Gi" }, "security": { "capabilities": [ @@ -4243,27 +4218,56 @@ "SETGID", "NET_BIND_SERVICE" ], - "network_policy": "isolated", - "readonly_root": false + "readonly_root": false, + "network_policy": "isolated" }, - "version": "1.30.0", + "ports": [ + { + "host": 8082, + "container": 80, + "protocol": "tcp" + } + ], "volumes": [ { - "options": [ - "rw" - ], + "type": "bind", "source": "/var/lib/archipelago/vaultwarden", "target": "/data", - "type": "bind" + "options": [ + "rw" + ] } - ] + ], + "environment": [], + "health_check": { + "type": "tcp", + "endpoint": "localhost:80", + "interval": "30s", + "timeout": "5s", + "retries": 3 + }, + "interfaces": { + "main": { + "name": "Web UI", + "description": "Vaultwarden web vault", + "type": "ui", + "port": 8082, + "protocol": "http", + "path": "/" + } + }, + "metadata": { + "icon": "/assets/img/app-icons/vaultwarden.webp", + "category": "data", + "tier": "recommended", + "author": "Vaultwarden", + "repo": "https://github.com/dani-garcia/vaultwarden", + "launch": { + "open_in_new_tab": true + } + } } - }, - "version": "1.30.0-alpine" + } } - }, - "schema": 1, - "signature": "bf1c813496074bb06cbd1b096cb3f8f5b72b5d31cfb651907712e54ffe746cc14a2ae6454a2ab266a8c3e8d187f997a7da447e0a4097d3cf0cb32a889bde9502", - "signed_by": "did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur", - "updated": "2026-07-09" + } } diff --git a/scripts/bootstrap-switchover.sh b/scripts/bootstrap-switchover.sh index 0018fb89..4f4543e4 100755 --- a/scripts/bootstrap-switchover.sh +++ b/scripts/bootstrap-switchover.sh @@ -78,7 +78,7 @@ if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^electrumx$'; then -e "DAEMON_URL=http://${RPC_USER}:${RPC_PASS}@bitcoin-knots:8332/" \ -e COIN=Bitcoin -e DB_DIRECTORY=/data \ -e "SERVICES=tcp://:50001,rpc://0.0.0.0:8000" \ - "${ELECTRUMX_IMAGE:-git.tx1138.com/lfg2025/electrumx:v1.18.0}" + "${ELECTRUMX_IMAGE:-146.59.87.168:3000/lfg2025/electrumx:v1.18.0}" fi # Mempool API @@ -98,7 +98,7 @@ if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^mempool-api$'; th -e "DATABASE_ENABLED=true" -e "DATABASE_HOST=archy-mempool-db" \ -e "DATABASE_DATABASE=mempool" -e "DATABASE_USERNAME=mempool" \ -e "DATABASE_PASSWORD=$(cat "$SECRETS_DIR/mempool-db-password" 2>/dev/null || echo mempoolpass)" \ - "${MEMPOOL_API_IMAGE:-git.tx1138.com/lfg2025/mempool-api:v3.2.0}" + "${MEMPOOL_API_IMAGE:-146.59.87.168:3000/lfg2025/mempool-api:v3.2.0}" fi # Stop Tor tunnel if it was active diff --git a/scripts/container-doctor.sh b/scripts/container-doctor.sh index 322c8e94..d7f2ada3 100755 --- a/scripts/container-doctor.sh +++ b/scripts/container-doctor.sh @@ -16,6 +16,9 @@ # 7. Containers stuck with exit code 127 (binary not found) # 8. Stopped core containers (rootless restart policy workaround) # 9. Missing rootless port listeners while Podman still shows published ports +# 10. Nginx Proxy Manager public hosts not mirrored into host nginx +# 11. BTCPay stores producing unpayable Lightning invoices (route hints off) +# 12. Missing catatonit (Podman init binary) — init-enabled deploys fail # # Safe to run multiple times (idempotent). Never blocks deploy (exit 0 always). # @@ -383,10 +386,22 @@ print(' '.join(['\"' + a + '\"' if ' ' in a else a for a in args[2:]])) # ── Fix 8: Rootless netns egress lost ──────────────────────── # Rootless podman uses pasta to give containers internet egress. If pasta's -# tap vanishes (host link flap, mount churn), the rootless-netns keeps inter- -# container traffic working but silently loses outbound. Bitcoin IBD stalls -# at 0 peers; package pulls fail. The only reliable repair is a stop-all/ -# start-all cycle so pasta + aardvark-dns rebuild the netns from scratch. +# tap vanishes (host link flap, mount churn, pasta dying during a boot-time +# restart storm), the rootless-netns keeps inter-container traffic working +# but silently loses outbound. Bitcoin IBD stalls at 0 peers; package pulls +# fail. The repair must rebuild the netns from scratch: merely cycling the +# containers reuses the existing (broken) netns because its holders +# (aardvark-dns, podman's pause process) survive — observed on shorty-s +# 2026-07-10, where the old stop/start-only cycle bounced all 35 containers +# every timer run for ~an hour without ever restoring egress. So: stop the +# containers, kill the netns holders, `podman system migrate`, clear the +# stale netns state, then start everything back up. +# +# Destructive-action latch: cycling the whole fleet is a last resort. After +# NETNS_CYCLE_MAX consecutive failed repairs we stop cycling (and log loudly) +# until a run observes egress healthy again, which resets the counter. +NETNS_CYCLE_STATE="/var/lib/archipelago/doctor-netns-cycle-failures" +NETNS_CYCLE_MAX=3 fix_rootless_netns_egress() { # Needs root for nsenter. When doctor runs as the rootless container owner, # a failed nsenter probe is a permissions artifact, not evidence of broken @@ -410,16 +425,28 @@ fix_rootless_netns_egress() { # Probe egress from inside the rootless-netns. One probe is noisy; # require two consecutive failures 10s apart to rule out transients. if timeout 3 nsenter -t "$aardvark_pid" -n bash -c '/dev/null; then + rm -f "$NETNS_CYCLE_STATE" # healthy again — re-arm the latch return 1 # first probe succeeded fi sleep 10 aardvark_pid=$(pgrep -U "$archi_uid" -f '^/usr/lib/podman/aardvark-dns' 2>/dev/null | head -1) [ -z "$aardvark_pid" ] && return 1 if timeout 3 nsenter -t "$aardvark_pid" -n bash -c '/dev/null; then + rm -f "$NETNS_CYCLE_STATE" return 1 # recovered on its own fi - log "Rootless-netns egress is broken (host online, container netns unreachable) — cycling" + # Latch: don't keep bouncing the fleet when the rebuild demonstrably + # isn't fixing it. + local failures + failures=$(cat "$NETNS_CYCLE_STATE" 2>/dev/null || echo 0) + case "$failures" in *[!0-9]*|"") failures=0;; esac + if [ "$failures" -ge "$NETNS_CYCLE_MAX" ]; then + log "Rootless-netns egress still broken but $failures rebuilds already failed — NOT cycling again (manual intervention needed; rm $NETNS_CYCLE_STATE to re-arm)" + return 1 + fi + + log "Rootless-netns egress is broken (host online, container netns unreachable) — rebuilding netns" local PODMANCMD="sudo -u archipelago XDG_RUNTIME_DIR=/run/user/$archi_uid podman" local running @@ -435,6 +462,19 @@ fix_rootless_netns_egress() { $PODMANCMD stop --all --time 30 >/dev/null 2>&1 sleep 5 + # Tear the broken netns down for real: kill its holders and drop the + # stale state so the first container start rebuilds pasta + aardvark-dns + # from scratch. Without this, podman re-enters the old netns and the + # missing pasta tap never comes back. + log " Rebuilding rootless netns (killing holders, clearing state)..." + pkill -U "$archi_uid" -x aardvark-dns 2>/dev/null + pkill -U "$archi_uid" -x pasta 2>/dev/null + pkill -U "$archi_uid" -x pasta.avx2 2>/dev/null + pkill -U "$archi_uid" -x slirp4netns 2>/dev/null + sleep 2 + $PODMANCMD system migrate >/dev/null 2>&1 + rm -rf "/run/user/$archi_uid/containers/networks" + log " Starting containers back up..." for c in $running; do $PODMANCMD start "$c" >/dev/null 2>&1 & @@ -445,8 +485,11 @@ fix_rootless_netns_egress() { aardvark_pid=$(pgrep -U "$archi_uid" -f '^/usr/lib/podman/aardvark-dns' 2>/dev/null | head -1) if [ -n "$aardvark_pid" ] && timeout 3 nsenter -t "$aardvark_pid" -n bash -c '/dev/null; then log " Rootless-netns egress restored ($count containers cycled)" + rm -f "$NETNS_CYCLE_STATE" else - log " WARN: egress still broken after cycle — may need manual intervention" + failures=$((failures + 1)) + echo "$failures" > "$NETNS_CYCLE_STATE" + log " WARN: egress still broken after rebuild (failure $failures/$NETNS_CYCLE_MAX) — may need manual intervention" fi return 0 } @@ -526,6 +569,51 @@ fix_npm_public_hosts() { return 1 } +# ── Fix 12: BTCPay Lightning route hints ───────────────────── +# A BTCPay store whose LND node has only private (unannounced) channels +# produces BOLT11 invoices that external wallets cannot route to unless the +# store's lightningPrivateRouteHints flag is on — payers see "no way to pay +# this invoice" (observed on shorty-s 2026-07-10 with a Blink payer). Route +# hints are a no-op with public channels and essential with private ones, so +# the doctor enforces the flag on every store. BTCPay reads store blobs from +# Postgres per request; no restart needed. +fix_btcpay_route_hints() { + local state + state=$(podman_rootless inspect archy-btcpay-db --format '{{.State.Status}}' 2>/dev/null || echo "missing") + [ "$state" = "running" ] || return 1 + + local count + count=$(podman_rootless exec archy-btcpay-db psql -U btcpay -d btcpay -t -A -c \ + "SELECT count(*) FROM \"Stores\" WHERE (\"StoreBlob\"->>'lightningPrivateRouteHints') = 'false';" 2>/dev/null) + [ -n "$count" ] && [ "$count" -gt 0 ] 2>/dev/null || return 1 + + if podman_rootless exec archy-btcpay-db psql -U btcpay -d btcpay -q -c \ + "UPDATE \"Stores\" SET \"StoreBlob\" = jsonb_set(\"StoreBlob\", '{lightningPrivateRouteHints}', 'true'::jsonb) WHERE (\"StoreBlob\"->>'lightningPrivateRouteHints') = 'false';" >/dev/null 2>&1; then + log "Enabled Lightning route hints on $count BTCPay store(s) (private-channel invoices were unpayable)" + return 0 + fi + return 1 +} + +# ── Fix 13: Missing catatonit (container init binary) ──────── +# Podman resolves `--init` (and any Portainer/compose deploy with +# "init: true") through catatonit; Debian's podman package only +# Recommends it, so a node installed or upgraded without it fails those +# deploys with a missing-init error (observed on shorty-s 2026-07-10 +# deploying sites via Portainer). install-podman.sh covers fresh ISO +# installs; this heals nodes that predate it. +fix_missing_catatonit() { + command -v catatonit >/dev/null 2>&1 && return 1 + command -v apt-get >/dev/null 2>&1 || return 1 + + if DEBIAN_FRONTEND=noninteractive apt-get install -y catatonit >/dev/null 2>&1; then + log "Installed catatonit (init-enabled container deploys were failing)" + return 0 + fi + log "WARNING: catatonit missing and apt-get install failed — init-enabled deploys will fail" + return 1 +} + # ── Main ───────────────────────────────────────────────────── # If remote host provided, run via SSH @@ -556,6 +644,8 @@ run_fix "netns-egress" fix_rootless_netns_egress run_fix "stopped-core" fix_stopped_core_containers run_fix "rootless-ports" fix_missing_rootless_ports run_fix "npm-public-hosts" fix_npm_public_hosts +run_fix "btcpay-route-hints" fix_btcpay_route_hints +run_fix "catatonit" fix_missing_catatonit echo "" if [ $FIXES_APPLIED -gt 0 ]; then diff --git a/scripts/deploy-bitcoin-knots.sh b/scripts/deploy-bitcoin-knots.sh index df651c1b..96c402d5 100644 --- a/scripts/deploy-bitcoin-knots.sh +++ b/scripts/deploy-bitcoin-knots.sh @@ -74,7 +74,7 @@ mkdir -p "$BUILD_DIR" # Create Dockerfile cat > "$BUILD_DIR/Dockerfile" << 'EOF' -FROM ${NGINX_ALPINE_IMAGE:-git.tx1138.com/lfg2025/nginx:1.29.6-alpine} +FROM ${NGINX_ALPINE_IMAGE:-146.59.87.168:3000/lfg2025/nginx:1.29.6-alpine} # Copy the static UI COPY index.html /usr/share/nginx/html/ diff --git a/scripts/dev-container-test.sh b/scripts/dev-container-test.sh index a1e4f512..7973e0a1 100755 --- a/scripts/dev-container-test.sh +++ b/scripts/dev-container-test.sh @@ -146,7 +146,7 @@ run_smoke_tests() { # Test 3: Install a lightweight container (filebrowser — small, fast, no deps) TESTS=$((TESTS + 1)) - local install_img="git.tx1138.com/lfg2025/filebrowser:v2.27.0" + local install_img="146.59.87.168:3000/lfg2025/filebrowser:v2.27.0" # Check if already installed local fb_state fb_state=$(ssh $SSH_OPTS "$SSH_HOST" "podman inspect filebrowser --format '{{.State.Status}}' 2>/dev/null || echo 'none'") diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index d7446f3e..3d51c005 100755 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -9,7 +9,7 @@ # # Image versions: sourced from /opt/archipelago/image-versions.sh (single source of truth). # All container image references use the $*_IMAGE variables defined there. -# Images pull from the Archipelago app registry (git.tx1138.com/lfg2025/). +# Images pull from the Archipelago app registry (146.59.87.168:3000/lfg2025/). # # --- PLANNED REFACTOR (post-beta) --- # This script is ~995 lines and should be split into a modular library. diff --git a/scripts/image-versions.sh b/scripts/image-versions.sh index 77d8da39..a2f17e22 100644 --- a/scripts/image-versions.sh +++ b/scripts/image-versions.sh @@ -11,7 +11,8 @@ # Archipelago app registries (primary + fallback) ARCHY_REGISTRY="146.59.87.168:3000/lfg2025" -ARCHY_REGISTRY_FALLBACK="git.tx1138.com/lfg2025" +# No fallback registry: the old tx1138 registry host was retired (2026-06-13); empty disables the fallback path. +ARCHY_REGISTRY_FALLBACK="" # Bitcoin stack BITCOIN_KNOTS_IMAGE="$ARCHY_REGISTRY/bitcoin-knots:latest" diff --git a/scripts/reconcile-containers.sh b/scripts/reconcile-containers.sh index 5ac09c76..8d50c486 100755 --- a/scripts/reconcile-containers.sh +++ b/scripts/reconcile-containers.sh @@ -304,7 +304,7 @@ resolve_spec_image() { "${ARCHY_REGISTRY_FALLBACK:-}/${image_path}" \ "80.71.235.15:3000/archipelago/${image_name}:${image_tag}" \ "80.71.235.15:3000/lfg2025/${image_name}:${image_tag}"; do - [ "$candidate" = "/" ] && continue + case "$candidate" in /*) continue;; esac if image_exists "$candidate"; then info "$SPEC_NAME — using local image alias $candidate" SPEC_IMAGE="$candidate" diff --git a/scripts/self-update.sh b/scripts/self-update.sh index 3aa95c4d..c947ed71 100755 --- a/scripts/self-update.sh +++ b/scripts/self-update.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Self-update: pull latest code from git.tx1138.com and apply +# Self-update: pull latest code from the OVH Gitea (146.59.87.168:3000) and apply # Designed to run on installed Archipelago nodes (as archipelago user) # # Usage: @@ -8,7 +8,7 @@ # ./self-update.sh --force # Apply even if already up to date # # The script: -# 1. Pulls latest code from origin (git.tx1138.com) +# 1. Pulls latest code from origin (146.59.87.168:3000) # 2. Builds the Rust backend (release mode) # 3. Builds the Vue frontend (production mode) # 4. Installs the new binary and web UI @@ -69,7 +69,7 @@ done # Ensure repo exists if [ ! -d "$REPO_DIR/.git" ]; then err "Repo not found at $REPO_DIR" - err "Clone it first: git clone https://git.tx1138.com/lfg2025/archy ~/archy" + err "Clone it first: git clone http://146.59.87.168:3000/lfg2025/archy ~/archy" exit 1 fi diff --git a/scripts/validate-app-manifest.sh b/scripts/validate-app-manifest.sh index bc66de5b..9831d735 100755 --- a/scripts/validate-app-manifest.sh +++ b/scripts/validate-app-manifest.sh @@ -86,7 +86,7 @@ else # Check trusted registry TRUSTED=false - for reg in "docker.io" "ghcr.io" "quay.io" "registry.hub.docker.com" "git.tx1138.com"; do + for reg in "docker.io" "ghcr.io" "quay.io" "registry.hub.docker.com" "146.59.87.168:3000"; do if echo "$IMAGE" | grep -q "$reg"; then TRUSTED=true break