fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab - Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x - nginx: resolver + variable proxy_pass for external domains (DNS at boot) - Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU - Container installs: write configs before chown, sudo chown for LUKS volumes - Container installs: build UI sidecars locally (not from registry) for auth injection - Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild - Secrets: chown to archipelago user in first-boot (backend needs read access) - Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace - NostrVPN: enable service in auto-install, always include public relays - NostrVPN: read tunnel IP from nvpn status (not just config file) - VPN invite: v2 base64 no-pad format matching phone app - Companion input: relay always active, kiosk skips relay listener (prevents double input) - dev-start.sh: production build includes AIUI deployment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
88bdba19db
commit
067a3ed106
@@ -265,15 +265,22 @@ impl RpcHandler {
|
||||
run_args.push("--device=/dev/net/tun");
|
||||
}
|
||||
|
||||
// Create data directories
|
||||
self.create_data_dirs(package_id, &volumes).await;
|
||||
// Create data directories (mkdir only — chown happens AFTER config files are written)
|
||||
for volume in &volumes {
|
||||
if let Some(host_path) = volume.split(':').next() {
|
||||
if host_path.starts_with("/var/lib/archipelago/") {
|
||||
if let Err(e) = std::fs::create_dir_all(host_path) {
|
||||
tracing::warn!("Failed to create directory {}: {}", host_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-install: bitcoin.conf with rpcauth
|
||||
// Pre-install: write config files BEFORE chown (dir is still owned by archipelago user)
|
||||
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
|
||||
self.write_bitcoin_conf(&rpc_user, &rpc_pass).await?;
|
||||
}
|
||||
|
||||
// Pre-install: lnd.conf with Bitcoin RPC credentials
|
||||
if package_id == "lnd" {
|
||||
self.write_lnd_conf(&rpc_user, &rpc_pass).await?;
|
||||
}
|
||||
@@ -328,6 +335,9 @@ impl RpcHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// NOW chown data directories to container UID (after all config files are written)
|
||||
self.create_data_dirs(package_id, &volumes).await;
|
||||
|
||||
// Port mappings (skip for host-network containers)
|
||||
if !is_tailscale {
|
||||
for port in &ports {
|
||||
@@ -637,22 +647,31 @@ impl RpcHandler {
|
||||
}
|
||||
|
||||
// Set ownership to the mapped UID for rootless podman.
|
||||
// This needs elevated privileges — use podman unshare to run
|
||||
// chown inside the user namespace where UIDs are mapped.
|
||||
let chown_result = tokio::process::Command::new("podman")
|
||||
.args(["unshare", "chown", "-R", &uid_str, host_path])
|
||||
// Try sudo chown first (works on LUKS), fall back to podman unshare.
|
||||
let host_uid = format!("{}:{}", uid, uid);
|
||||
let sudo_result = tokio::process::Command::new("sudo")
|
||||
.args(["chown", "-R", &host_uid, host_path])
|
||||
.output()
|
||||
.await;
|
||||
match chown_result {
|
||||
Ok(out) if !out.status.success() => {
|
||||
tracing::warn!(
|
||||
"podman unshare chown failed for {}: {}",
|
||||
host_path,
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
let sudo_ok = sudo_result.as_ref().map_or(false, |o| o.status.success());
|
||||
if !sudo_ok {
|
||||
// Fallback: podman unshare (works on non-LUKS ext4)
|
||||
let container_uid = uid - 100000;
|
||||
let container_uid_str = format!("{}:{}", container_uid, container_uid);
|
||||
let chown_result = tokio::process::Command::new("podman")
|
||||
.args(["unshare", "chown", "-R", &container_uid_str, host_path])
|
||||
.output()
|
||||
.await;
|
||||
match chown_result {
|
||||
Ok(out) if !out.status.success() => {
|
||||
tracing::warn!(
|
||||
"chown failed for {} (both sudo and podman unshare)",
|
||||
host_path,
|
||||
);
|
||||
}
|
||||
Err(e) => tracing::warn!("Failed to chown {}: {}", host_path, e),
|
||||
_ => {}
|
||||
}
|
||||
Err(e) => tracing::warn!("Failed to chown {}: {}", host_path, e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -917,11 +936,21 @@ autopilot.active=false\n",
|
||||
for dir in ["/opt/archipelago/docker/bitcoin-ui", "/home/archipelago/archy/docker/bitcoin-ui"] {
|
||||
let conf_path = format!("{}/nginx.conf", dir);
|
||||
if let Ok(content) = tokio::fs::read_to_string(&conf_path).await {
|
||||
if content.contains("__BITCOIN_RPC_AUTH__") {
|
||||
let updated = content.replace("__BITCOIN_RPC_AUTH__", &auth_b64);
|
||||
let _ = tokio::fs::write(&conf_path, updated).await;
|
||||
info!("Injected Bitcoin RPC auth into {}", conf_path);
|
||||
}
|
||||
// Replace placeholder or previously-injected auth (regex: Basic followed by base64 or placeholder)
|
||||
let updated = content
|
||||
.replace("__BITCOIN_RPC_AUTH__", &auth_b64)
|
||||
.lines()
|
||||
.map(|line| {
|
||||
if line.contains("proxy_set_header Authorization") && line.contains("Basic") {
|
||||
format!(" proxy_set_header Authorization \"Basic {}\";", auth_b64)
|
||||
} else {
|
||||
line.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let _ = tokio::fs::write(&conf_path, format!("{}\n", updated)).await;
|
||||
info!("Injected Bitcoin RPC auth into {}", conf_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -949,7 +978,14 @@ autopilot.active=false\n",
|
||||
|
||||
for (name, ui_dir, image_base) in ui_builds {
|
||||
let name = name.to_string();
|
||||
let ui_dir = ui_dir.to_string();
|
||||
// Check multiple paths: /opt (production), project tree (dev)
|
||||
let ui_dir = [
|
||||
ui_dir.to_string(),
|
||||
format!("/home/archipelago/archy/docker/{}", image_base),
|
||||
format!("/home/archipelago/Projects/archy/docker/{}", image_base),
|
||||
].into_iter()
|
||||
.find(|d| std::path::Path::new(d).join("Dockerfile").exists())
|
||||
.unwrap_or_else(|| ui_dir.to_string());
|
||||
let image_base = image_base.to_string();
|
||||
let registry = "80.71.235.15:3000/archipelago";
|
||||
let registry_image = format!("{}/{}:latest", registry, image_base);
|
||||
@@ -961,19 +997,13 @@ autopilot.active=false\n",
|
||||
.output()
|
||||
.await;
|
||||
|
||||
// Try registry image first, fall back to local build
|
||||
// Build locally first (templates may have injected credentials),
|
||||
// fall back to registry only if no local Dockerfile exists.
|
||||
let image = {
|
||||
let pull = tokio::process::Command::new("podman")
|
||||
.args(["pull", ®istry_image])
|
||||
.output()
|
||||
.await;
|
||||
if pull.map_or(false, |o| o.status.success()) {
|
||||
info!("Pulled {} UI from registry", name);
|
||||
registry_image.clone()
|
||||
} else if std::path::Path::new(&ui_dir).exists() {
|
||||
info!("Registry pull failed, building {} from {}", name, ui_dir);
|
||||
if std::path::Path::new(&ui_dir).exists() {
|
||||
info!("Building {} locally from {}", name, ui_dir);
|
||||
let build = tokio::process::Command::new("podman")
|
||||
.args(["build", "-t", &local_image, &ui_dir])
|
||||
.args(["build", "--no-cache", "-t", &local_image, &ui_dir])
|
||||
.output()
|
||||
.await;
|
||||
match build {
|
||||
@@ -989,8 +1019,18 @@ autopilot.active=false\n",
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("No registry image or source for {} — skipping", name);
|
||||
return;
|
||||
// No local Dockerfile — try pulling from registry
|
||||
let pull = tokio::process::Command::new("podman")
|
||||
.args(["pull", ®istry_image])
|
||||
.output()
|
||||
.await;
|
||||
if pull.map_or(false, |o| o.status.success()) {
|
||||
info!("Pulled {} UI from registry", name);
|
||||
registry_image.clone()
|
||||
} else {
|
||||
warn!("No local source or registry image for {} — skipping", name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user