fix(install): ship app manifests where the backend loads them + self-heal existing nodes

Fresh ISO installs had ZERO disk manifests: auto-install.sh copied
apps/ to /etc/archipelago/apps but the backend/orchestrator loads
/opt/archipelago/apps — so any app not overlaid by the signed catalog
(netbird: 'manifests not available on this node') could never install.

- installer now also populates /mnt/target/opt/archipelago/apps
- bootstrap gains run_apps_dir_repair(): when /opt/archipelago/apps is
  empty and the installer copy exists, populate it at startup — heals
  already-deployed ISO nodes via OTA without reinstalling
- bootstrap's bitcoin.conf repair now also ensures rpcbind=0.0.0.0
  (same fix as install.rs; duplicate rpcbind with a manifest command
  line is harmless — bitcoind binds the first and logs the rest)

Verified live on the framework node: /opt populated (56 dirs), backend
restart loaded '50 app manifest(s) (disk + registry catalog)', all
containers kept running (they live in user.slice, not the service
cgroup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-14 07:44:26 -04:00
parent f2a94548ab
commit c014c4b3ee
2 changed files with 50 additions and 0 deletions

View File

@ -130,6 +130,11 @@ pub async fn ensure_doctor_installed() {
Ok(false) => debug!("Bitcoin RPC bind settings already usable"),
Err(e) => warn!("Bitcoin RPC repair failed (non-fatal): {:#}", e),
}
match run_apps_dir_repair().await {
Ok(true) => info!("Populated /opt/archipelago/apps from installer copy at /etc/archipelago/apps"),
Ok(false) => debug!("/opt/archipelago/apps already populated (or no installer copy)"),
Err(e) => warn!("Apps dir repair failed (non-fatal): {:#}", e),
}
match run_journald_dropin().await {
Ok(true) => info!("Installed journald log-volume policy drop-in"),
Ok(false) => debug!("journald log-volume policy already in place"),
@ -402,6 +407,39 @@ fn path_dot(path: &Path) -> String {
p.to_string_lossy().to_string()
}
/// ISO installs before the auto-install.sh path fix copied the app manifests
/// to /etc/archipelago/apps while the backend loads them from
/// /opt/archipelago/apps — so fresh nodes had ZERO disk manifests and only
/// catalog-covered apps could install (netbird "manifests not available",
/// framework node 2026-07-14). Self-heal: when /opt has no manifests and the
/// installer copy exists, populate /opt from /etc. Never overwrites existing
/// /opt manifests (OTA runtime-assets sync owns those afterwards).
async fn run_apps_dir_repair() -> Result<bool> {
let script = r#"
set -eu
src=/etc/archipelago/apps
dst=/opt/archipelago/apps
[ -d "$src" ] || exit 0
# Only heal when the destination has no manifests at all.
if [ -d "$dst" ] && [ -n "$(ls -A "$dst" 2>/dev/null)" ]; then exit 0; fi
ls "$src"/*/manifest.yml >/dev/null 2>&1 || exit 0
mkdir -p "$dst"
cp -r "$src"/. "$dst"/
exit 2
"#;
let status = host_sudo(&["sh", "-lc", script])
.await
.context("populate /opt/archipelago/apps from installer copy")?;
match status.code() {
Some(0) => Ok(false),
Some(2) => Ok(true),
_ => {
warn!("Apps dir repair helper exited with {}", status);
Ok(false)
}
}
}
async fn run_bitcoin_rpc_repair() -> Result<bool> {
// Older installs can have a container-owned bitcoin.conf with only rpcauth
// and printtoconsole. Repair it at startup so OTA fixes existing nodes
@ -421,6 +459,12 @@ ensure_line() {
fi
}
ensure_line server=1
# rpcbind=0.0.0.0 is required inside the container: with rpcallowip set but
# no rpcbind, bitcoind binds RPC to the container's loopback only and every
# dial over the container network (LND, bitcoin-ui) is refused the fresh-
# install "LND took 5 attempts" / bitcoin-rpc 502 failure (host publish stays
# 127.0.0.1-only, so exposure is unchanged).
ensure_line rpcbind=0.0.0.0
ensure_line rpcallowip=0.0.0.0/0
ensure_line listen=1
# Log-volume fix: printtoconsole=1 duplicated every log line (incl. per-block

View File

@ -2482,6 +2482,12 @@ fi
if [ -d "$BOOT_MEDIA/archipelago/apps" ]; then
cp -r "$BOOT_MEDIA/archipelago/apps" /mnt/target/etc/archipelago/
# The backend loads disk manifests from /opt/archipelago/apps — copying
# only to /etc/archipelago/apps left fresh installs with ZERO manifests,
# so any app not covered by the signed catalog (e.g. netbird) could not
# install at all. Ship them where the orchestrator actually looks.
mkdir -p /mnt/target/opt/archipelago/apps
cp -r "$BOOT_MEDIA/archipelago/apps/." /mnt/target/opt/archipelago/apps/
fi
# Copy pre-bundled container images