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:
parent
82419c52ab
commit
7393c5f158
@ -185,7 +185,7 @@ impl ApiHandler {
|
|||||||
continue; // silently drop
|
continue; // silently drop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relay to connected browsers (best-effort, ignore if no receivers)
|
// Always relay to browser clients (remote browser sessions)
|
||||||
let _ = relay_tx.send(text.clone());
|
let _ = relay_tx.send(text.clone());
|
||||||
|
|
||||||
match handle_input(&text).await {
|
match handle_input(&text).await {
|
||||||
|
|||||||
@ -265,15 +265,22 @@ impl RpcHandler {
|
|||||||
run_args.push("--device=/dev/net/tun");
|
run_args.push("--device=/dev/net/tun");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create data directories
|
// Create data directories (mkdir only — chown happens AFTER config files are written)
|
||||||
self.create_data_dirs(package_id, &volumes).await;
|
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") {
|
if matches!(package_id, "bitcoin" | "bitcoin-core" | "bitcoin-knots") {
|
||||||
self.write_bitcoin_conf(&rpc_user, &rpc_pass).await?;
|
self.write_bitcoin_conf(&rpc_user, &rpc_pass).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-install: lnd.conf with Bitcoin RPC credentials
|
|
||||||
if package_id == "lnd" {
|
if package_id == "lnd" {
|
||||||
self.write_lnd_conf(&rpc_user, &rpc_pass).await?;
|
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)
|
// Port mappings (skip for host-network containers)
|
||||||
if !is_tailscale {
|
if !is_tailscale {
|
||||||
for port in &ports {
|
for port in &ports {
|
||||||
@ -637,22 +647,31 @@ impl RpcHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set ownership to the mapped UID for rootless podman.
|
// Set ownership to the mapped UID for rootless podman.
|
||||||
// This needs elevated privileges — use podman unshare to run
|
// Try sudo chown first (works on LUKS), fall back to podman unshare.
|
||||||
// chown inside the user namespace where UIDs are mapped.
|
let host_uid = format!("{}:{}", uid, uid);
|
||||||
let chown_result = tokio::process::Command::new("podman")
|
let sudo_result = tokio::process::Command::new("sudo")
|
||||||
.args(["unshare", "chown", "-R", &uid_str, host_path])
|
.args(["chown", "-R", &host_uid, host_path])
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
match chown_result {
|
let sudo_ok = sudo_result.as_ref().map_or(false, |o| o.status.success());
|
||||||
Ok(out) if !out.status.success() => {
|
if !sudo_ok {
|
||||||
tracing::warn!(
|
// Fallback: podman unshare (works on non-LUKS ext4)
|
||||||
"podman unshare chown failed for {}: {}",
|
let container_uid = uid - 100000;
|
||||||
host_path,
|
let container_uid_str = format!("{}:{}", container_uid, container_uid);
|
||||||
String::from_utf8_lossy(&out.stderr)
|
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"] {
|
for dir in ["/opt/archipelago/docker/bitcoin-ui", "/home/archipelago/archy/docker/bitcoin-ui"] {
|
||||||
let conf_path = format!("{}/nginx.conf", dir);
|
let conf_path = format!("{}/nginx.conf", dir);
|
||||||
if let Ok(content) = tokio::fs::read_to_string(&conf_path).await {
|
if let Ok(content) = tokio::fs::read_to_string(&conf_path).await {
|
||||||
if content.contains("__BITCOIN_RPC_AUTH__") {
|
// Replace placeholder or previously-injected auth (regex: Basic followed by base64 or placeholder)
|
||||||
let updated = content.replace("__BITCOIN_RPC_AUTH__", &auth_b64);
|
let updated = content
|
||||||
let _ = tokio::fs::write(&conf_path, updated).await;
|
.replace("__BITCOIN_RPC_AUTH__", &auth_b64)
|
||||||
info!("Injected Bitcoin RPC auth into {}", conf_path);
|
.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 {
|
for (name, ui_dir, image_base) in ui_builds {
|
||||||
let name = name.to_string();
|
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 image_base = image_base.to_string();
|
||||||
let registry = "80.71.235.15:3000/archipelago";
|
let registry = "80.71.235.15:3000/archipelago";
|
||||||
let registry_image = format!("{}/{}:latest", registry, image_base);
|
let registry_image = format!("{}/{}:latest", registry, image_base);
|
||||||
@ -961,19 +997,13 @@ autopilot.active=false\n",
|
|||||||
.output()
|
.output()
|
||||||
.await;
|
.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 image = {
|
||||||
let pull = tokio::process::Command::new("podman")
|
if std::path::Path::new(&ui_dir).exists() {
|
||||||
.args(["pull", ®istry_image])
|
info!("Building {} locally from {}", name, ui_dir);
|
||||||
.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);
|
|
||||||
let build = tokio::process::Command::new("podman")
|
let build = tokio::process::Command::new("podman")
|
||||||
.args(["build", "-t", &local_image, &ui_dir])
|
.args(["build", "--no-cache", "-t", &local_image, &ui_dir])
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
match build {
|
match build {
|
||||||
@ -989,8 +1019,18 @@ autopilot.active=false\n",
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!("No registry image or source for {} — skipping", name);
|
// No local Dockerfile — try pulling from registry
|
||||||
return;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -269,19 +269,33 @@ impl RpcHandler {
|
|||||||
let network_id = vpn::read_nvpn_config_list_entry("networks", "network_id").await
|
let network_id = vpn::read_nvpn_config_list_entry("networks", "network_id").await
|
||||||
.unwrap_or_else(|| "nostr-vpn".to_string());
|
.unwrap_or_else(|| "nostr-vpn".to_string());
|
||||||
|
|
||||||
// Read relays from config
|
// Read relays from config — filter out localhost relays (unreachable from phone)
|
||||||
let relays = vpn::read_nvpn_config_list("nostr", "relays").await;
|
let relays = vpn::read_nvpn_config_list("nostr", "relays").await;
|
||||||
let relay_csv = if relays.is_empty() {
|
let reachable: Vec<String> = relays.iter()
|
||||||
"wss://relay.damus.io,wss://relay.primal.net".to_string()
|
.filter(|r| !r.contains("127.0.0.1") && !r.contains("localhost"))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
let invite_relays = if reachable.is_empty() {
|
||||||
|
vec!["wss://relay.damus.io".to_string(), "wss://relay.primal.net".to_string()]
|
||||||
} else {
|
} else {
|
||||||
relays.join(",")
|
reachable
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build invite URL: nvpn://invite/<network_id>?npub=<npub>&relays=<csv>
|
// Build invite as base64-encoded JSON (nvpn v2 format, no padding)
|
||||||
let invite_url = format!(
|
use base64::Engine;
|
||||||
"nvpn://invite/{}?npub={}&relays={}",
|
let invite_payload = serde_json::json!({
|
||||||
network_id, npub, relay_csv
|
"v": 2,
|
||||||
);
|
"networkName": network_id,
|
||||||
|
"networkId": network_id,
|
||||||
|
"inviterNpub": npub,
|
||||||
|
"inviterNodeName": "archipelago",
|
||||||
|
"admins": [npub],
|
||||||
|
"participants": [npub],
|
||||||
|
"relays": invite_relays,
|
||||||
|
});
|
||||||
|
let invite_b64 = base64::engine::general_purpose::STANDARD_NO_PAD
|
||||||
|
.encode(invite_payload.to_string().as_bytes());
|
||||||
|
let invite_url = format!("nvpn://invite/{}", invite_b64);
|
||||||
|
|
||||||
// Generate QR code
|
// Generate QR code
|
||||||
let qr = qrcode::QrCode::new(invite_url.as_bytes())
|
let qr = qrcode::QrCode::new(invite_url.as_bytes())
|
||||||
@ -295,11 +309,7 @@ impl RpcHandler {
|
|||||||
"qr_svg": svg,
|
"qr_svg": svg,
|
||||||
"npub": npub,
|
"npub": npub,
|
||||||
"network_id": network_id,
|
"network_id": network_id,
|
||||||
"relays": if relays.is_empty() {
|
"relays": invite_relays,
|
||||||
vec!["wss://relay.damus.io".to_string(), "wss://relay.primal.net".to_string()]
|
|
||||||
} else {
|
|
||||||
relays
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -322,8 +322,48 @@ async fn get_nostr_vpn_status() -> Result<VpnStatus> {
|
|||||||
anyhow::bail!("nostr-vpn service not running");
|
anyhow::bail!("nostr-vpn service not running");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick IP check: read from nvpn config (TOML)
|
// Get tunnel IP: try nvpn status first, fall back to config, then interface
|
||||||
let ip = read_nvpn_config_value("node", "tunnel_ip").await;
|
let ip = {
|
||||||
|
// Method 1: nvpn status (most accurate)
|
||||||
|
let status_ip = tokio::process::Command::new("nvpn")
|
||||||
|
.args(["status"])
|
||||||
|
.env("HOME", "/var/lib/archipelago/nostr-vpn")
|
||||||
|
.env("XDG_CONFIG_HOME", "/var/lib/archipelago/nostr-vpn/.config")
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| {
|
||||||
|
let out = String::from_utf8_lossy(&o.stdout).to_string();
|
||||||
|
out.lines()
|
||||||
|
.find(|l| l.starts_with("tunnel_ip:"))
|
||||||
|
.map(|l| l.split(':').nth(1).unwrap_or("").trim().to_string())
|
||||||
|
})
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
|
|
||||||
|
if status_ip.is_some() {
|
||||||
|
status_ip
|
||||||
|
} else {
|
||||||
|
// Method 2: config file
|
||||||
|
let cfg_ip = read_nvpn_config_value("node", "tunnel_ip").await;
|
||||||
|
if cfg_ip.is_some() {
|
||||||
|
cfg_ip
|
||||||
|
} else {
|
||||||
|
// Method 3: interface IP
|
||||||
|
tokio::process::Command::new("ip")
|
||||||
|
.args(["-4", "addr", "show", "nvpn0"])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| {
|
||||||
|
let out = String::from_utf8_lossy(&o.stdout).to_string();
|
||||||
|
out.lines()
|
||||||
|
.find(|l| l.contains("inet "))
|
||||||
|
.and_then(|l| l.split_whitespace().nth(1))
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(VpnStatus {
|
Ok(VpnStatus {
|
||||||
connected: svc_state == "active",
|
connected: svc_state == "active",
|
||||||
@ -424,17 +464,18 @@ pub async fn configure_nostr_vpn(data_dir: &Path) -> Result<()> {
|
|||||||
if should_write {
|
if should_write {
|
||||||
// Gather relay URLs for the config
|
// Gather relay URLs for the config
|
||||||
let (relay_onion, relay_direct) = get_relay_urls().await;
|
let (relay_onion, relay_direct) = get_relay_urls().await;
|
||||||
let mut relays = Vec::new();
|
// Always include public relays so peers can discover each other.
|
||||||
|
// Local/onion relays are added as extras for direct connectivity.
|
||||||
|
let mut relays = vec![
|
||||||
|
"\"wss://relay.damus.io\"".to_string(),
|
||||||
|
"\"wss://relay.primal.net\"".to_string(),
|
||||||
|
];
|
||||||
if let Some(ref onion) = relay_onion {
|
if let Some(ref onion) = relay_onion {
|
||||||
relays.push(format!("\"{}\"", onion));
|
relays.push(format!("\"{}\"", onion));
|
||||||
}
|
}
|
||||||
if let Some(ref direct) = relay_direct {
|
if let Some(ref direct) = relay_direct {
|
||||||
relays.push(format!("\"{}\"", direct));
|
relays.push(format!("\"{}\"", direct));
|
||||||
}
|
}
|
||||||
if relays.is_empty() {
|
|
||||||
relays.push("\"wss://relay.damus.io\"".to_string());
|
|
||||||
relays.push("\"wss://relay.primal.net\"".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_toml = format!(
|
let config_toml = format!(
|
||||||
"[nostr]\npublic_key = \"{npub}\"\nsecret_key = \"{nsec}\"\nrelays = [{relays}]\n\n[[networks]]\nnetwork_id = \"archipelago\"\nparticipants = []\n",
|
"[nostr]\npublic_key = \"{npub}\"\nsecret_key = \"{nsec}\"\nrelays = [{relays}]\n\n[[networks]]\nnetwork_id = \"archipelago\"\nparticipants = []\n",
|
||||||
|
|||||||
@ -274,6 +274,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
podman \
|
podman \
|
||||||
uidmap \
|
uidmap \
|
||||||
slirp4netns \
|
slirp4netns \
|
||||||
|
passt \
|
||||||
|
aardvark-dns \
|
||||||
|
netavark \
|
||||||
|
nftables \
|
||||||
fuse-overlayfs \
|
fuse-overlayfs \
|
||||||
tor \
|
tor \
|
||||||
python3 \
|
python3 \
|
||||||
@ -922,10 +926,20 @@ else
|
|||||||
echo " Capturing backend binary from live server..."
|
echo " Capturing backend binary from live server..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
|
# Try to get backend binary: local release build → local install → remote → container build
|
||||||
BACKEND_CAPTURED=0
|
BACKEND_CAPTURED=0
|
||||||
if [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
|
||||||
# Direct copy from ARCHIPELAGO_BIN env, local install, or remote
|
# Check for local release binary first (works for both BUILD_FROM_SOURCE and normal mode)
|
||||||
|
LOCAL_RELEASE="$(cd "$SCRIPT_DIR/.." && pwd)/core/target/release/archipelago"
|
||||||
|
if [ -f "$LOCAL_RELEASE" ]; then
|
||||||
|
cp "$LOCAL_RELEASE" "$ARCH_DIR/bin/archipelago"
|
||||||
|
chmod +x "$ARCH_DIR/bin/archipelago"
|
||||||
|
echo " ✅ Backend from local release build ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))"
|
||||||
|
BACKEND_CAPTURED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$BACKEND_CAPTURED" = "0" ] && [ "$BUILD_FROM_SOURCE" != "1" ]; then
|
||||||
|
# Direct copy from ARCHIPELAGO_BIN env or local install
|
||||||
BIN="${ARCHIPELAGO_BIN:-/usr/local/bin/archipelago}"
|
BIN="${ARCHIPELAGO_BIN:-/usr/local/bin/archipelago}"
|
||||||
if [ -f "$BIN" ]; then
|
if [ -f "$BIN" ]; then
|
||||||
cp "$BIN" "$ARCH_DIR/bin/archipelago"
|
cp "$BIN" "$ARCH_DIR/bin/archipelago"
|
||||||
@ -2006,12 +2020,65 @@ DATA_UUID=$(blkid -s UUID -o value "$DATA_PART")
|
|||||||
echo "# LUKS2 encrypted data — auto-unlock with key file" > /mnt/target/etc/crypttab
|
echo "# LUKS2 encrypted data — auto-unlock with key file" > /mnt/target/etc/crypttab
|
||||||
echo "archipelago-data UUID=$DATA_UUID /root/.luks-archipelago.key luks,discard" >> /mnt/target/etc/crypttab
|
echo "archipelago-data UUID=$DATA_UUID /root/.luks-archipelago.key luks,discard" >> /mnt/target/etc/crypttab
|
||||||
|
|
||||||
|
# Configure LUKS auto-unlock: three layers to ensure it works
|
||||||
|
# Layer 1: cryptsetup-initramfs config (tells update-initramfs to embed key)
|
||||||
|
mkdir -p /mnt/target/etc/cryptsetup-initramfs
|
||||||
|
cat > /mnt/target/etc/cryptsetup-initramfs/conf <<'CRYPTCONF'
|
||||||
|
KEYFILE_PATTERN="/root/.luks-*.key"
|
||||||
|
UMASK=0077
|
||||||
|
CRYPTCONF
|
||||||
|
|
||||||
|
# Layer 2: initramfs hook to force-copy key file
|
||||||
|
mkdir -p /mnt/target/etc/initramfs-tools/hooks
|
||||||
|
cat > /mnt/target/etc/initramfs-tools/hooks/archipelago-luks <<'LUKSHOOK'
|
||||||
|
#!/bin/sh
|
||||||
|
PREREQ=""
|
||||||
|
prereqs() { echo "$PREREQ"; }
|
||||||
|
case $1 in prereqs) prereqs; exit 0;; esac
|
||||||
|
. /usr/share/initramfs-tools/hook-functions
|
||||||
|
if [ -f /root/.luks-archipelago.key ]; then
|
||||||
|
mkdir -p "${DESTDIR}/root"
|
||||||
|
cp /root/.luks-archipelago.key "${DESTDIR}/root/.luks-archipelago.key"
|
||||||
|
chmod 600 "${DESTDIR}/root/.luks-archipelago.key"
|
||||||
|
fi
|
||||||
|
if [ -f /etc/crypttab ]; then
|
||||||
|
mkdir -p "${DESTDIR}/etc"
|
||||||
|
cp /etc/crypttab "${DESTDIR}/etc/crypttab"
|
||||||
|
fi
|
||||||
|
copy_exec /sbin/cryptsetup
|
||||||
|
LUKSHOOK
|
||||||
|
chmod +x /mnt/target/etc/initramfs-tools/hooks/archipelago-luks
|
||||||
|
|
||||||
|
# Layer 3: systemd service as fallback — unlocks LUKS early if initramfs missed it
|
||||||
|
cat > /mnt/target/etc/systemd/system/archipelago-luks-unlock.service <<'LUKSUNIT'
|
||||||
|
[Unit]
|
||||||
|
Description=Unlock Archipelago LUKS data partition
|
||||||
|
DefaultDependencies=no
|
||||||
|
Before=local-fs-pre.target
|
||||||
|
After=systemd-udevd.service
|
||||||
|
Wants=systemd-udevd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/bin/bash -c '\
|
||||||
|
if [ -e /dev/mapper/archipelago-data ]; then exit 0; fi; \
|
||||||
|
DATA_DEV=$(blkid -t TYPE=crypto_LUKS -o device 2>/dev/null | head -1); \
|
||||||
|
if [ -z "$DATA_DEV" ]; then exit 0; fi; \
|
||||||
|
cryptsetup open --type luks2 --key-file /root/.luks-archipelago.key "$DATA_DEV" archipelago-data'
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=local-fs-pre.target
|
||||||
|
LUKSUNIT
|
||||||
|
chroot /mnt/target systemctl enable archipelago-luks-unlock.service 2>/dev/null || \
|
||||||
|
ln -sf /etc/systemd/system/archipelago-luks-unlock.service /mnt/target/etc/systemd/system/local-fs-pre.target.wants/archipelago-luks-unlock.service
|
||||||
|
|
||||||
# Create fstab
|
# Create fstab
|
||||||
cat > /mnt/target/etc/fstab <<EOF
|
cat > /mnt/target/etc/fstab <<EOF
|
||||||
# Archipelago Bitcoin Node OS
|
# Archipelago Bitcoin Node OS
|
||||||
UUID=$(blkid -s UUID -o value "$ROOT_PART") / ext4 errors=remount-ro 0 1
|
UUID=$(blkid -s UUID -o value "$ROOT_PART") / ext4 errors=remount-ro 0 1
|
||||||
UUID=$(blkid -s UUID -o value "$EFI_PART") /boot/efi vfat umask=0077 0 1
|
UUID=$(blkid -s UUID -o value "$EFI_PART") /boot/efi vfat umask=0077 0 1
|
||||||
/dev/mapper/archipelago-data /var/lib/archipelago ext4 defaults 0 2
|
/dev/mapper/archipelago-data /var/lib/archipelago ext4 defaults,nofail,x-systemd.device-timeout=60 0 2
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Configure hostname
|
# Configure hostname
|
||||||
@ -2055,7 +2122,13 @@ if [ -f /mnt/target/usr/lib/podman/netavark ]; then
|
|||||||
cat > /mnt/target/home/archipelago/.config/containers/containers.conf <<'CONTAINERSCONF'
|
cat > /mnt/target/home/archipelago/.config/containers/containers.conf <<'CONTAINERSCONF'
|
||||||
[network]
|
[network]
|
||||||
network_backend = "netavark"
|
network_backend = "netavark"
|
||||||
|
default_rootless_network_cmd = "pasta"
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
image_copy_tmp_dir = "/var/lib/archipelago/containers/tmp"
|
||||||
CONTAINERSCONF
|
CONTAINERSCONF
|
||||||
|
mkdir -p /mnt/target/var/lib/archipelago/containers/tmp
|
||||||
|
chown -R 1000:1000 /mnt/target/var/lib/archipelago/containers/tmp
|
||||||
chown -R 1000:1000 /mnt/target/home/archipelago/.config/containers
|
chown -R 1000:1000 /mnt/target/home/archipelago/.config/containers
|
||||||
echo " Configured netavark backend (container DNS enabled)"
|
echo " Configured netavark backend (container DNS enabled)"
|
||||||
else
|
else
|
||||||
@ -2623,15 +2696,9 @@ if [ -d "$BOOT_MEDIA/archipelago/plymouth-theme" ]; then
|
|||||||
chroot /mnt/target plymouth-set-default-theme archipelago 2>/dev/null || \
|
chroot /mnt/target plymouth-set-default-theme archipelago 2>/dev/null || \
|
||||||
ln -sf /usr/share/plymouth/themes/archipelago/archipelago.plymouth \
|
ln -sf /usr/share/plymouth/themes/archipelago/archipelago.plymouth \
|
||||||
/mnt/target/etc/alternatives/default.plymouth 2>/dev/null || true
|
/mnt/target/etc/alternatives/default.plymouth 2>/dev/null || true
|
||||||
# Enable splash and ACPI in GRUB
|
# Configure clean boot: splash, suppress kernel noise, hide cursor
|
||||||
if ! grep -q "splash" /mnt/target/etc/default/grub; then
|
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT=".*"/GRUB_CMDLINE_LINUX_DEFAULT="quiet splash loglevel=0 rd.systemd.show_status=false vt.global_cursor_default=0 acpi=force"/' \
|
||||||
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/GRUB_CMDLINE_LINUX_DEFAULT="\1 splash"/' \
|
/mnt/target/etc/default/grub 2>/dev/null || true
|
||||||
/mnt/target/etc/default/grub 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
if ! grep -q "acpi=force" /mnt/target/etc/default/grub; then
|
|
||||||
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/GRUB_CMDLINE_LINUX_DEFAULT="\1 acpi=force"/' \
|
|
||||||
/mnt/target/etc/default/grub 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
echo " Installed Archipelago Plymouth theme on target"
|
echo " Installed Archipelago Plymouth theme on target"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -2816,6 +2883,7 @@ chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null
|
|||||||
chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true
|
chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true
|
||||||
chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true
|
chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true
|
||||||
chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true
|
chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true
|
||||||
|
chroot /mnt/target systemctl enable nostr-vpn.service 2>/dev/null || true
|
||||||
# Enable claude-api-proxy (create symlink manually — chroot systemctl can fail)
|
# Enable claude-api-proxy (create symlink manually — chroot systemctl can fail)
|
||||||
chroot /mnt/target systemctl enable claude-api-proxy.service 2>/dev/null || \
|
chroot /mnt/target systemctl enable claude-api-proxy.service 2>/dev/null || \
|
||||||
ln -sf /etc/systemd/system/claude-api-proxy.service /mnt/target/etc/systemd/system/multi-user.target.wants/claude-api-proxy.service 2>/dev/null || true
|
ln -sf /etc/systemd/system/claude-api-proxy.service /mnt/target/etc/systemd/system/multi-user.target.wants/claude-api-proxy.service 2>/dev/null || true
|
||||||
@ -3079,15 +3147,21 @@ fi
|
|||||||
set timeout=5
|
set timeout=5
|
||||||
set default=0
|
set default=0
|
||||||
|
|
||||||
|
# Serial console for QEMU/headless testing
|
||||||
|
insmod serial
|
||||||
|
serial --unit=0 --speed=115200
|
||||||
|
terminal_input serial console
|
||||||
|
terminal_output serial console
|
||||||
|
|
||||||
# Load font for graphical menu — fallback to text mode on hardware without gfxterm
|
# Load font for graphical menu — fallback to text mode on hardware without gfxterm
|
||||||
if loadfont ($root)/boot/grub/font.pf2; then
|
if loadfont ($root)/boot/grub/font.pf2; then
|
||||||
set gfxmode=auto
|
set gfxmode=auto
|
||||||
set gfxpayload=keep
|
set gfxpayload=keep
|
||||||
insmod gfxterm
|
insmod gfxterm
|
||||||
insmod png
|
insmod png
|
||||||
terminal_output gfxterm
|
terminal_output gfxterm serial
|
||||||
else
|
else
|
||||||
terminal_output console
|
terminal_output console serial
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Archipelago GRUB theme
|
# Archipelago GRUB theme
|
||||||
@ -3103,7 +3177,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
menuentry "Install Archipelago" --hotkey=i {
|
menuentry "Install Archipelago" --hotkey=i {
|
||||||
linux ($root)/live/vmlinuz boot=live components quiet splash loglevel=0 rd.systemd.show_status=false vt.global_cursor_default=0 acpi=force
|
linux ($root)/live/vmlinuz boot=live components quiet splash loglevel=0 rd.systemd.show_status=false vt.global_cursor_default=0 acpi=force console=ttyS0,115200 console=tty0
|
||||||
initrd ($root)/live/initrd.img
|
initrd ($root)/live/initrd.img
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,10 @@ limit_req_zone $binary_remote_addr zone=rpc:10m rate=20r/s;
|
|||||||
limit_req_zone $binary_remote_addr zone=auth:10m rate=3r/s;
|
limit_req_zone $binary_remote_addr zone=auth:10m rate=3r/s;
|
||||||
limit_req_zone $binary_remote_addr zone=peer:10m rate=10r/s;
|
limit_req_zone $binary_remote_addr zone=peer:10m rate=10r/s;
|
||||||
|
|
||||||
|
# Resolve external domains at request time (not startup) to prevent boot failures
|
||||||
|
resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
|
||||||
|
resolver_timeout 5s;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
@ -46,7 +50,9 @@ server {
|
|||||||
|
|
||||||
# AIUI OpenRouter API proxy (API key managed by proxy, no session gate needed)
|
# AIUI OpenRouter API proxy (API key managed by proxy, no session gate needed)
|
||||||
location /aiui/api/openrouter/ {
|
location /aiui/api/openrouter/ {
|
||||||
proxy_pass https://openrouter.ai/api/;
|
set $upstream_1 "https://openrouter.ai/api/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_1;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host openrouter.ai;
|
proxy_set_header Host openrouter.ai;
|
||||||
proxy_ssl_server_name on;
|
proxy_ssl_server_name on;
|
||||||
@ -175,6 +181,20 @@ server {
|
|||||||
error_page 504 = @backend_timeout;
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# LND REST proxy — backend handles auth + CORS
|
||||||
|
location /proxy/lnd/ {
|
||||||
|
proxy_pass http://127.0.0.1:5678;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_read_timeout 10s;
|
||||||
|
proxy_send_timeout 5s;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
# Content sharing — peer access over Tor (no auth)
|
# Content sharing — peer access over Tor (no auth)
|
||||||
location /content {
|
location /content {
|
||||||
limit_req zone=peer burst=20 nodelay;
|
limit_req zone=peer burst=20 nodelay;
|
||||||
@ -662,7 +682,9 @@ server {
|
|||||||
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
||||||
# add_header here prevents inheritance of server-level X-Frame-Options.
|
# add_header here prevents inheritance of server-level X-Frame-Options.
|
||||||
location /ext/botfights/ {
|
location /ext/botfights/ {
|
||||||
proxy_pass https://botfights.net/;
|
set $upstream_2 "https://botfights.net/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_2;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host botfights.net;
|
proxy_set_header Host botfights.net;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -684,7 +706,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/484-kitchen/ {
|
location /ext/484-kitchen/ {
|
||||||
proxy_pass https://484.kitchen/;
|
set $upstream_3 "https://484.kitchen/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_3;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host 484.kitchen;
|
proxy_set_header Host 484.kitchen;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -703,7 +727,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/arch-presentation/ {
|
location /ext/arch-presentation/ {
|
||||||
proxy_pass https://present.l484.com/;
|
set $upstream_4 "https://present.l484.com/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_4;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host present.l484.com;
|
proxy_set_header Host present.l484.com;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -722,7 +748,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/nostrudel/ {
|
location /ext/nostrudel/ {
|
||||||
proxy_pass https://nostrudel.ninja/;
|
set $upstream_5 "https://nostrudel.ninja/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_5;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host nostrudel.ninja;
|
proxy_set_header Host nostrudel.ninja;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -818,7 +846,9 @@ server {
|
|||||||
proxy_send_timeout 120s;
|
proxy_send_timeout 120s;
|
||||||
}
|
}
|
||||||
location /aiui/api/openrouter/ {
|
location /aiui/api/openrouter/ {
|
||||||
proxy_pass https://openrouter.ai/api/;
|
set $upstream_6 "https://openrouter.ai/api/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_6;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host openrouter.ai;
|
proxy_set_header Host openrouter.ai;
|
||||||
proxy_ssl_server_name on;
|
proxy_ssl_server_name on;
|
||||||
@ -886,6 +916,20 @@ server {
|
|||||||
error_page 504 = @backend_timeout;
|
error_page 504 = @backend_timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# LND REST proxy — backend handles auth + CORS
|
||||||
|
location /proxy/lnd/ {
|
||||||
|
proxy_pass http://127.0.0.1:5678;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
proxy_read_timeout 10s;
|
||||||
|
proxy_send_timeout 5s;
|
||||||
|
error_page 502 503 = @backend_unavailable;
|
||||||
|
error_page 504 = @backend_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
# Content sharing — peer access over Tor (no auth)
|
# Content sharing — peer access over Tor (no auth)
|
||||||
location /content {
|
location /content {
|
||||||
limit_req zone=peer burst=20 nodelay;
|
limit_req zone=peer burst=20 nodelay;
|
||||||
@ -1038,7 +1082,9 @@ server {
|
|||||||
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
# External site proxies — strip X-Frame-Options so iframe embedding works.
|
||||||
# add_header here prevents inheritance of server-level X-Frame-Options.
|
# add_header here prevents inheritance of server-level X-Frame-Options.
|
||||||
location /ext/botfights/ {
|
location /ext/botfights/ {
|
||||||
proxy_pass https://botfights.net/;
|
set $upstream_7 "https://botfights.net/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_7;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host botfights.net;
|
proxy_set_header Host botfights.net;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1060,7 +1106,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/484-kitchen/ {
|
location /ext/484-kitchen/ {
|
||||||
proxy_pass https://484.kitchen/;
|
set $upstream_8 "https://484.kitchen/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_8;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host 484.kitchen;
|
proxy_set_header Host 484.kitchen;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1079,7 +1127,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/arch-presentation/ {
|
location /ext/arch-presentation/ {
|
||||||
proxy_pass https://present.l484.com/;
|
set $upstream_9 "https://present.l484.com/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_9;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host present.l484.com;
|
proxy_set_header Host present.l484.com;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1098,7 +1148,9 @@ server {
|
|||||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||||
}
|
}
|
||||||
location /ext/nostrudel/ {
|
location /ext/nostrudel/ {
|
||||||
proxy_pass https://nostrudel.ninja/;
|
set $upstream_10 "https://nostrudel.ninja/";
|
||||||
|
|
||||||
|
proxy_pass $upstream_10;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host nostrudel.ninja;
|
proxy_set_header Host nostrudel.ninja;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1139,7 +1191,9 @@ server {
|
|||||||
listen 8901;
|
listen 8901;
|
||||||
server_name _;
|
server_name _;
|
||||||
location / {
|
location / {
|
||||||
proxy_pass https://botfights.net;
|
set $upstream_11 "https://botfights.net";
|
||||||
|
|
||||||
|
proxy_pass $upstream_11;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host botfights.net;
|
proxy_set_header Host botfights.net;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1164,7 +1218,9 @@ server {
|
|||||||
listen 8902;
|
listen 8902;
|
||||||
server_name _;
|
server_name _;
|
||||||
location / {
|
location / {
|
||||||
proxy_pass https://484.kitchen;
|
set $upstream_12 "https://484.kitchen";
|
||||||
|
|
||||||
|
proxy_pass $upstream_12;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host 484.kitchen;
|
proxy_set_header Host 484.kitchen;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
@ -1185,7 +1241,9 @@ server {
|
|||||||
listen 8903;
|
listen 8903;
|
||||||
server_name _;
|
server_name _;
|
||||||
location / {
|
location / {
|
||||||
proxy_pass https://present.l484.com;
|
set $upstream_13 "https://present.l484.com";
|
||||||
|
|
||||||
|
proxy_pass $upstream_13;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host present.l484.com;
|
proxy_set_header Host present.l484.com;
|
||||||
proxy_set_header Accept-Encoding "";
|
proxy_set_header Accept-Encoding "";
|
||||||
|
|||||||
@ -102,7 +102,12 @@ watch(() => appStore.isAuthenticated, (authenticated) => {
|
|||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
messageToast.startPolling()
|
messageToast.startPolling()
|
||||||
screensaverStore.resetInactivityTimer()
|
screensaverStore.resetInactivityTimer()
|
||||||
startRemoteRelay()
|
// Don't start relay on kiosk — kiosk gets input via xdotool (system-level),
|
||||||
|
// relay would duplicate every keystroke/click as DOM events
|
||||||
|
const isKiosk = window.location.pathname.startsWith('/kiosk')
|
||||||
|
if (!isKiosk) {
|
||||||
|
startRemoteRelay()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
messageToast.stopPolling()
|
messageToast.stopPolling()
|
||||||
toastMessage.value = { show: false, text: '' }
|
toastMessage.value = { show: false, text: '' }
|
||||||
|
|||||||
@ -1350,7 +1350,7 @@ import json, os
|
|||||||
# Protocol services get direct port mapping; web apps map port 80 to their local port
|
# Protocol services get direct port mapping; web apps map port 80 to their local port
|
||||||
PROTOCOL_SERVICES = {"bitcoin", "bitcoin-knots", "electrs", "electrumx", "lnd"}
|
PROTOCOL_SERVICES = {"bitcoin", "bitcoin-knots", "electrs", "electrumx", "lnd"}
|
||||||
|
|
||||||
lines = ["# Auto-generated by Archipelago deploy", "SocksPort 9050", "# ControlPort disabled", ""]
|
lines = ["# Auto-generated by Archipelago deploy", "SocksPort 0.0.0.0:9050", "# ControlPort disabled", ""]
|
||||||
|
|
||||||
# Try reading services config (check both paths for compatibility)
|
# Try reading services config (check both paths for compatibility)
|
||||||
cfg = None
|
cfg = None
|
||||||
|
|||||||
@ -331,6 +331,15 @@ case $choice in
|
|||||||
if npx vue-tsc -b --noEmit 2>&1 | tail -3; then
|
if npx vue-tsc -b --noEmit 2>&1 | tail -3; then
|
||||||
npm run build 2>&1 | tail -3
|
npm run build 2>&1 | tail -3
|
||||||
sudo cp -r "$PROJECT_ROOT/web/dist/neode-ui/"* /opt/archipelago/web-ui/
|
sudo cp -r "$PROJECT_ROOT/web/dist/neode-ui/"* /opt/archipelago/web-ui/
|
||||||
|
# Deploy AIUI (pre-built demo or source build)
|
||||||
|
if [ -d "$PROJECT_ROOT/../AIUI/packages/app/dist" ]; then
|
||||||
|
sudo cp -r "$PROJECT_ROOT/../AIUI/packages/app/dist/"* /opt/archipelago/web-ui/aiui/
|
||||||
|
echo " AIUI deployed from source build"
|
||||||
|
elif [ -d "$PROJECT_ROOT/demo/aiui" ]; then
|
||||||
|
sudo mkdir -p /opt/archipelago/web-ui/aiui/
|
||||||
|
sudo cp -r "$PROJECT_ROOT/demo/aiui/"* /opt/archipelago/web-ui/aiui/
|
||||||
|
echo " AIUI deployed from demo/"
|
||||||
|
fi
|
||||||
echo " Frontend deployed to /opt/archipelago/web-ui/"
|
echo " Frontend deployed to /opt/archipelago/web-ui/"
|
||||||
else
|
else
|
||||||
echo " FAILED: vue-tsc type check"
|
echo " FAILED: vue-tsc type check"
|
||||||
|
|||||||
@ -242,6 +242,8 @@ if [ ! -f "$SECRETS_DIR/bitcoin-rpc-password" ]; then
|
|||||||
openssl rand -hex 16 > "$SECRETS_DIR/bitcoin-rpc-password"
|
openssl rand -hex 16 > "$SECRETS_DIR/bitcoin-rpc-password"
|
||||||
chmod 600 "$SECRETS_DIR/bitcoin-rpc-password"
|
chmod 600 "$SECRETS_DIR/bitcoin-rpc-password"
|
||||||
fi
|
fi
|
||||||
|
# Ensure archipelago user can read secrets (backend runs as archipelago, not root)
|
||||||
|
chown -R 1000:1000 "$SECRETS_DIR"
|
||||||
BITCOIN_RPC_USER="archipelago"
|
BITCOIN_RPC_USER="archipelago"
|
||||||
BITCOIN_RPC_PASS=$(cat "$SECRETS_DIR/bitcoin-rpc-password" 2>/dev/null)
|
BITCOIN_RPC_PASS=$(cat "$SECRETS_DIR/bitcoin-rpc-password" 2>/dev/null)
|
||||||
if [ -z "$BITCOIN_RPC_PASS" ]; then
|
if [ -z "$BITCOIN_RPC_PASS" ]; then
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user