feat(pine): wire the Pine 3-container stack into the orchestrator
Register "pine" as a manifest-driven stack (like netbird) across every lifecycle site so install/stop/start/restart/uninstall/crash-recovery treat the launcher + two Wyoming engines as one app: - stacks.rs: pine_stack_app_ids() + install_pine_stack() (orchestrator-first, adopt existing, else bail — no hardcoded fallback) - install.rs: dispatch package_id == "pine" to the stack installer - app_ops.rs: canonical stack-member table + owning_package STACKS - dependencies.rs: startup_order + needs_archy_net - config.rs: all_container_names - crash_recovery.rs: auto-start members + a StackRecoverySpec (anchor pine-whisper) so a genuinely-installed stack self-heals but orphan debris does not crash-loop - docker_packages.rs: exclude the engines from the apps list Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1730d02003
commit
8791ef60f5
@@ -496,6 +496,12 @@ pub(super) fn all_container_names(package_id: &str) -> Vec<String> {
|
|||||||
"netbird-dashboard".into(),
|
"netbird-dashboard".into(),
|
||||||
"netbird-server".into(),
|
"netbird-server".into(),
|
||||||
],
|
],
|
||||||
|
// Pine voice-assistant stack: launcher + the two Wyoming engines.
|
||||||
|
"pine" => vec![
|
||||||
|
"pine".into(),
|
||||||
|
"pine-whisper".into(),
|
||||||
|
"pine-piper".into(),
|
||||||
|
],
|
||||||
"nostr-vpn" => vec![
|
"nostr-vpn" => vec![
|
||||||
"nostr-vpn".into(),
|
"nostr-vpn".into(),
|
||||||
"archy-nostr-vpn".into(),
|
"archy-nostr-vpn".into(),
|
||||||
|
|||||||
@@ -595,6 +595,9 @@ pub(super) fn needs_archy_net(package_id: &str) -> bool {
|
|||||||
| "nbxplorer"
|
| "nbxplorer"
|
||||||
| "fedimint"
|
| "fedimint"
|
||||||
| "fedimint-gateway"
|
| "fedimint-gateway"
|
||||||
|
| "pine"
|
||||||
|
| "pine-whisper"
|
||||||
|
| "pine-piper"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,6 +629,7 @@ pub(super) fn startup_order(package_id: &str) -> &'static [&'static str] {
|
|||||||
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
||||||
}
|
}
|
||||||
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
|
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
|
||||||
|
"pine" => &["pine-whisper", "pine-piper", "pine"],
|
||||||
"penpot" | "penpot-frontend" => &[
|
"penpot" | "penpot-frontend" => &[
|
||||||
"penpot-postgres",
|
"penpot-postgres",
|
||||||
"penpot-valkey",
|
"penpot-valkey",
|
||||||
|
|||||||
@@ -294,6 +294,9 @@ impl RpcHandler {
|
|||||||
if package_id == "netbird" {
|
if package_id == "netbird" {
|
||||||
return self.install_netbird_stack().await;
|
return self.install_netbird_stack().await;
|
||||||
}
|
}
|
||||||
|
if package_id == "pine" {
|
||||||
|
return self.install_pine_stack().await;
|
||||||
|
}
|
||||||
// Dependency checks. Prefer the scanner's cached package state so a
|
// Dependency checks. Prefer the scanner's cached package state so a
|
||||||
// congested Podman API does not turn an already-running dependency into
|
// congested Podman API does not turn an already-running dependency into
|
||||||
// a false install failure. Fall back to a bounded direct Podman probe
|
// a false install failure. Fall back to a bounded direct Podman probe
|
||||||
|
|||||||
@@ -744,6 +744,15 @@ fn netbird_stack_app_ids() -> &'static [&'static str] {
|
|||||||
&["netbird-server", "netbird-dashboard", "netbird"]
|
&["netbird-server", "netbird-dashboard", "netbird"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pine_stack_app_ids() -> &'static [&'static str] {
|
||||||
|
// Dependency/startup order: the two Wyoming engines (STT + TTS) first — they
|
||||||
|
// own their downloaded model/voice under /data and publish 10300/10200 —
|
||||||
|
// then the user-facing launcher ("pine", the nginx that serves the setup /
|
||||||
|
// status page + is the Open target). Mirrors the pine startup_order in
|
||||||
|
// dependencies.rs + the stack member table in app_ops.rs.
|
||||||
|
&["pine-whisper", "pine-piper", "pine"]
|
||||||
|
}
|
||||||
|
|
||||||
fn indeedhub_stack_app_ids() -> &'static [&'static str] {
|
fn indeedhub_stack_app_ids() -> &'static [&'static str] {
|
||||||
// Dependency order: backends + their generated secrets first, then the api
|
// Dependency order: backends + their generated secrets first, then the api
|
||||||
// (owns indeedhub-jwt; reads the db/minio secrets the backends materialised),
|
// (owns indeedhub-jwt; reads the db/minio secrets the backends materialised),
|
||||||
@@ -1907,6 +1916,37 @@ impl RpcHandler {
|
|||||||
"netbird manifests not available on this node — the signed catalog must provide apps/netbird-*/manifest.yml (legacy hardcoded installer removed in #20 ph4)"
|
"netbird manifests not available on this node — the signed catalog must provide apps/netbird-*/manifest.yml (legacy hardcoded installer removed in #20 ph4)"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Install the Pine voice-assistant stack (Whisper STT + Piper TTS + the
|
||||||
|
/// setup/status launcher). Manifest-driven only, like netbird: render the
|
||||||
|
/// 3-member stack from apps/pine-*/manifest.yml via the orchestrator
|
||||||
|
/// (archy-net + network_aliases, published Wyoming ports 10300/10200 so
|
||||||
|
/// Home Assistant reaches the engines via host.containers.internal, the
|
||||||
|
/// launcher's setup page written via `files:`). The manifests use the exact
|
||||||
|
/// live container names, so on an existing node this ADOPTS the running
|
||||||
|
/// stack rather than recreating it (downloaded models/voices preserved).
|
||||||
|
///
|
||||||
|
/// There is no in-Rust hardcoded fallback: the signed catalog always ships
|
||||||
|
/// apps/pine-*/manifest.yml. If the orchestrator doesn't know these app_ids
|
||||||
|
/// and no running stack exists to adopt, install errors rather than
|
||||||
|
/// silently diverging from the manifest contract.
|
||||||
|
pub(super) async fn install_pine_stack(&self) -> Result<serde_json::Value> {
|
||||||
|
if let Some(orchestrated) =
|
||||||
|
install_stack_via_orchestrator(self, "pine", pine_stack_app_ids()).await?
|
||||||
|
{
|
||||||
|
return Ok(orchestrated);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(adopted) =
|
||||||
|
adopt_stack_if_exists("pine", "pine", &["pine-whisper", "pine-piper", "pine"]).await?
|
||||||
|
{
|
||||||
|
return Ok(adopted);
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!(
|
||||||
|
"pine manifests not available on this node — the signed catalog must provide apps/pine-*/manifest.yml"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ pub fn stack_member_app_ids(package_id: &str) -> &'static [&'static str] {
|
|||||||
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
&["archy-btcpay-db", "archy-nbxplorer", "btcpay-server"]
|
||||||
}
|
}
|
||||||
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
|
"netbird" => &["netbird-server", "netbird-dashboard", "netbird"],
|
||||||
|
"pine" => &["pine-whisper", "pine-piper", "pine"],
|
||||||
// The legacy umbrella id maps to the split stack (the orchestrator's
|
// The legacy umbrella id maps to the split stack (the orchestrator's
|
||||||
// umbrella alias handles this too; listing it here keeps the RPC
|
// umbrella alias handles this too; listing it here keeps the RPC
|
||||||
// layer's fan-out explicit).
|
// layer's fan-out explicit).
|
||||||
@@ -75,7 +76,14 @@ pub fn address_caching_dependents(package_id: &str) -> &'static [&'static str] {
|
|||||||
/// `app_id` is a member (RPC ops on "mempool" hold the "mempool" lock while
|
/// `app_id` is a member (RPC ops on "mempool" hold the "mempool" lock while
|
||||||
/// they drive archy-mempool-web), otherwise the app itself.
|
/// they drive archy-mempool-web), otherwise the app itself.
|
||||||
fn owning_package(app_id: &str) -> &str {
|
fn owning_package(app_id: &str) -> &str {
|
||||||
const STACKS: &[&str] = &["immich", "indeedhub", "btcpay-server", "netbird", "mempool"];
|
const STACKS: &[&str] = &[
|
||||||
|
"immich",
|
||||||
|
"indeedhub",
|
||||||
|
"btcpay-server",
|
||||||
|
"netbird",
|
||||||
|
"mempool",
|
||||||
|
"pine",
|
||||||
|
];
|
||||||
for stack in STACKS {
|
for stack in STACKS {
|
||||||
if stack_member_app_ids(stack).contains(&app_id) {
|
if stack_member_app_ids(stack).contains(&app_id) {
|
||||||
return stack;
|
return stack;
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ impl DockerPackageScanner {
|
|||||||
"indeedhub-build_ffmpeg-worker_1",
|
"indeedhub-build_ffmpeg-worker_1",
|
||||||
"netbird-server",
|
"netbird-server",
|
||||||
"netbird-dashboard",
|
"netbird-dashboard",
|
||||||
|
"pine-whisper",
|
||||||
|
"pine-piper",
|
||||||
"buildx_buildkit_default",
|
"buildx_buildkit_default",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -766,6 +766,9 @@ fn should_auto_start_stopped_container(name: &str, include_stack_members: bool)
|
|||||||
| "netbird-server"
|
| "netbird-server"
|
||||||
| "netbird-dashboard"
|
| "netbird-dashboard"
|
||||||
| "netbird"
|
| "netbird"
|
||||||
|
| "pine-whisper"
|
||||||
|
| "pine-piper"
|
||||||
|
| "pine"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,6 +830,20 @@ fn stack_recovery_specs() -> &'static [StackRecoverySpec] {
|
|||||||
containers: &["netbird-server", "netbird-dashboard", "netbird"],
|
containers: &["netbird-server", "netbird-dashboard", "netbird"],
|
||||||
anchor: "netbird-server",
|
anchor: "netbird-server",
|
||||||
},
|
},
|
||||||
|
StackRecoverySpec {
|
||||||
|
name: "pine",
|
||||||
|
network: "archy-net",
|
||||||
|
aliases: &[
|
||||||
|
("pine-whisper", "pine-whisper"),
|
||||||
|
("pine-piper", "pine-piper"),
|
||||||
|
("pine", "pine"),
|
||||||
|
],
|
||||||
|
containers: &["pine-whisper", "pine-piper", "pine"],
|
||||||
|
// The launcher only depends on the two engines; whisper is the
|
||||||
|
// heaviest/first member, so treat it as the stack anchor (its
|
||||||
|
// presence means the stack was really installed, not orphan debris).
|
||||||
|
anchor: "pine-whisper",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user