fix: first-boot container creation, remote input relay, ISO packages

Critical first-boot fixes (root cause: ALL 25 containers failed on install):
- Fix image-versions.sh sourcing: multi-path fallback for /opt/archipelago/scripts/
- Fix --add-host host-gateway: resolve actual gateway IP (podman 4.3 compat)
- Fix disk size detection: check /var/lib/archipelago not / (was forcing prune on 428GB disk)
- Fix Bitcoin health check: expand $RPC vars at creation, not inside container
- Add --network-alias to all containers (aardvark-dns reliability)
- Add --network-alias to backend RPC install handler

ISO build:
- Add apache2-utils for htpasswd (Fedimint gateway password hashing)

Remote input:
- Add broadcast relay channel for companion app → browser input forwarding
- Add /ws/remote-relay WebSocket endpoint
- Android: NES controller improvements, server connect flow updates

Container images:
- Fix lnd-ui Dockerfile: listen on 8080, run as root user (rootless compat)
- Fix bitcoin-ui, electrs-ui Dockerfiles: root user for rootless podman

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-02 10:34:58 +01:00
parent 051d3b1375
commit 07808a95c4
13 changed files with 238 additions and 71 deletions

View File

@ -128,16 +128,16 @@ private fun MenuPanel(
OutlinedTextField(
value = addr, onValueChange = { addr = it.trim() },
placeholder = { Text("192.168.1.100", color = NES.MenuMuted, fontSize = 11.sp) },
modifier = Modifier.fillMaxWidth().height(40.dp), singleLine = true,
modifier = Modifier.fillMaxWidth().height(48.dp), singleLine = true,
textStyle = androidx.compose.ui.text.TextStyle(color = NES.MenuText, fontSize = 12.sp),
colors = nesFieldColors(),
shape = RoundedCornerShape(2.dp),
)
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
value = pwd, onValueChange = { pwd = it },
placeholder = { Text("PASSWORD", color = NES.MenuMuted, fontSize = 11.sp) },
modifier = Modifier.weight(1f).height(40.dp), singleLine = true,
modifier = Modifier.weight(1f).height(48.dp), singleLine = true,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Go),
keyboardActions = KeyboardActions(onGo = {
@ -148,7 +148,7 @@ private fun MenuPanel(
shape = RoundedCornerShape(2.dp),
)
Box(
Modifier.size(40.dp).clip(RoundedCornerShape(2.dp)).background(NES.MenuSelected)
Modifier.size(48.dp).clip(RoundedCornerShape(2.dp)).background(NES.MenuSelected)
.clickable {
if (addr.isNotBlank()) { onAddServer(ServerEntry(addr, false, password = pwd)); addr = ""; pwd = ""; showAdd = false }
},

View File

@ -37,6 +37,9 @@ import com.archipelago.app.ui.theme.NES
fun NESPortraitController(
style: ControllerStyle = ControllerStyle.CLASSIC,
onKey: (String) -> Unit,
onMouseMove: (Int, Int) -> Unit = { _, _ -> },
onMouseClick: (Int) -> Unit = { _ -> },
onMouseScroll: (Int) -> Unit = { _ -> },
onMenu: () -> Unit,
) {
val c = paletteFor(style)
@ -80,11 +83,9 @@ fun NESPortraitController(
) {
// Trackpad area (touch surface for mouse)
Trackpad(
onMove = { _, _ -> }, // Not used in gamepad, but keeps the visual
onClick = { onKey("Return") },
onScroll = { dy ->
if (dy > 0) onKey("Down") else onKey("Up")
},
onMove = { dx, dy -> onMouseMove(dx, dy) },
onClick = { onMouseClick(it) },
onScroll = { dy -> onMouseScroll(dy) },
onTwoFingerHold = onMenu,
modifier = Modifier
.fillMaxWidth()

View File

@ -64,11 +64,6 @@ fun RemoteInputScreen(onBack: () -> Unit) {
BackHandler { onBack() }
DisposableEffect(Unit) { onDispose { ws.disconnect() } }
LaunchedEffect(activeServer) { activeServer?.let { ws.connect(it.toUrl(), it.password) } }
LaunchedEffect(connectionState) {
if (connectionState == ConnectionState.ERROR) {
kotlinx.coroutines.delay(3000); activeServer?.let { ws.connect(it.toUrl(), it.password) }
}
}
Box(
Modifier
@ -85,6 +80,9 @@ fun RemoteInputScreen(onBack: () -> Unit) {
isGamepadMode && !isLandscape -> NESPortraitController(
style = controllerStyle,
onKey = { ws.sendKey(it) },
onMouseMove = { dx, dy -> ws.sendMouseMove(dx, dy) },
onMouseClick = { ws.sendClick(it) },
onMouseScroll = { ws.sendScroll(it) },
onMenu = { showModal = true },
)
else -> {

View File

@ -25,6 +25,8 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
@ -59,7 +61,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.archipelago.app.R
@ -94,6 +99,8 @@ fun ServerConnectScreen(
var address by remember { mutableStateOf("") }
var port by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
var useHttps by remember { mutableStateOf(false) }
var isConnecting by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
@ -195,13 +202,7 @@ fun ServerConnectScreen(
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Go,
),
keyboardActions = KeyboardActions(
onGo = {
keyboard?.hide()
connect(ServerEntry(address, useHttps, port))
},
imeAction = ImeAction.Next,
),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.White.copy(alpha = 0.3f),
@ -217,6 +218,10 @@ fun ServerConnectScreen(
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
OutlinedTextField(
value = port,
onValueChange = {
@ -225,16 +230,52 @@ fun ServerConnectScreen(
},
label = { Text(stringResource(R.string.port_label)) },
placeholder = { Text("80") },
modifier = Modifier.width(140.dp),
modifier = Modifier.weight(1f),
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next,
),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.White.copy(alpha = 0.3f),
unfocusedBorderColor = Color.White.copy(alpha = 0.12f),
cursorColor = Color.White,
focusedLabelColor = Color.White.copy(alpha = 0.7f),
unfocusedLabelColor = TextMuted,
focusedTextColor = TextPrimary,
unfocusedTextColor = TextPrimary,
),
shape = RoundedCornerShape(12.dp),
)
OutlinedTextField(
value = password,
onValueChange = {
password = it
errorMessage = null
},
label = { Text("Password") },
modifier = Modifier.weight(2f),
singleLine = true,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = if (passwordVisible) "Hide password" else "Show password",
tint = TextMuted,
modifier = Modifier.size(20.dp),
)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go,
),
keyboardActions = KeyboardActions(
onGo = {
keyboard?.hide()
connect(ServerEntry(address, useHttps, port))
connect(ServerEntry(address, useHttps, port, password))
},
),
colors = OutlinedTextFieldDefaults.colors(
@ -248,6 +289,7 @@ fun ServerConnectScreen(
),
shape = RoundedCornerShape(12.dp),
)
}
Spacer(modifier = Modifier.height(16.dp))
@ -303,7 +345,7 @@ fun ServerConnectScreen(
text = if (isConnecting) stringResource(R.string.connecting) else stringResource(R.string.connect),
onClick = {
keyboard?.hide()
connect(ServerEntry(address, useHttps, port))
connect(ServerEntry(address, useHttps, port, password))
},
modifier = Modifier.fillMaxWidth().height(56.dp),
)
@ -363,7 +405,7 @@ private fun SavedServerItem(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) {
Icon(
imageVector = if (server.useHttps) Icons.Default.Lock else Icons.Default.LockOpen,
contentDescription = null,
@ -372,7 +414,7 @@ private fun SavedServerItem(
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(text = server.address, style = MaterialTheme.typography.bodyMedium, color = TextPrimary)
Text(text = server.address, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (server.port.isNotBlank()) {
Text(text = "Port ${server.port}", style = MaterialTheme.typography.labelMedium, color = TextMuted)
}

2
core/Cargo.lock generated
View File

@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "archipelago"
version = "1.2.0-alpha"
version = "1.3.1"
dependencies = [
"anyhow",
"archipelago-container",

View File

@ -13,6 +13,7 @@ use crate::state::StateManager;
use anyhow::Result;
use hyper::{Method, Request, Response, StatusCode};
use std::sync::Arc;
use tokio::sync::broadcast;
use tracing::debug;
/// Build an HTTP response without unwrap. Falls back to a plain 500 if builder fails.
@ -32,6 +33,8 @@ pub struct ApiHandler {
state_manager: Arc<StateManager>,
metrics_store: Arc<MetricsStore>,
session_store: SessionStore,
/// Broadcast channel for relaying companion app input to remote browsers.
input_relay_tx: broadcast::Sender<String>,
}
impl ApiHandler {
@ -50,6 +53,7 @@ impl ApiHandler {
)
.await?,
);
let (input_relay_tx, _) = broadcast::channel(64);
Ok(Self {
config,
@ -57,6 +61,7 @@ impl ApiHandler {
state_manager,
metrics_store,
session_store,
input_relay_tx,
})
}
@ -147,7 +152,16 @@ impl ApiHandler {
tracing::warn!("401 WebSocket /ws/remote-input — session invalid or missing");
return Ok(Self::unauthorized());
}
return Self::handle_remote_input(req).await;
return Self::handle_remote_input(req, self.input_relay_tx.clone()).await;
}
// Remote relay WebSocket — browser receives companion input events
if method == Method::GET && path == "/ws/remote-relay" {
if !self.is_authenticated(req.headers()).await {
tracing::warn!("401 WebSocket /ws/remote-relay — session invalid or missing");
return Ok(Self::unauthorized());
}
return Self::handle_remote_relay(req, self.input_relay_tx.subscribe()).await;
}
// Convert body to bytes for non-WS routes

View File

@ -5,6 +5,7 @@ use hyper_ws_listener::WsStream;
use serde::Deserialize;
use std::time::Instant;
use tokio::process::Command;
use tokio::sync::broadcast;
use tokio_tungstenite::tungstenite::Message;
use tracing::{debug, info, warn};
@ -121,6 +122,7 @@ async fn handle_input(msg: &str) -> Result<Option<String>> {
impl ApiHandler {
pub(super) async fn handle_remote_input(
req: Request<hyper::Body>,
relay_tx: broadcast::Sender<String>,
) -> Result<Response<hyper::Body>> {
let (response, ws_fut_opt) = hyper_ws_listener::create_ws(req)
.map_err(|e| anyhow::anyhow!("WebSocket upgrade failed: {}", e))?;
@ -183,6 +185,9 @@ impl ApiHandler {
continue; // silently drop
}
// Relay to connected browsers (best-effort, ignore if no receivers)
let _ = relay_tx.send(text.clone());
match handle_input(&text).await {
Ok(Some(reply)) => {
let _ = tx.send(Message::Text(reply)).await;
@ -220,4 +225,88 @@ impl ApiHandler {
Ok(response)
}
/// Browser relay — receives input events from the broadcast channel and forwards to the browser.
pub(super) async fn handle_remote_relay(
req: Request<hyper::Body>,
mut relay_rx: broadcast::Receiver<String>,
) -> Result<Response<hyper::Body>> {
let (response, ws_fut_opt) = hyper_ws_listener::create_ws(req)
.map_err(|e| anyhow::anyhow!("WebSocket upgrade failed: {}", e))?;
if let Some(ws_fut) = ws_fut_opt {
tokio::spawn(async move {
let ws_stream: WsStream = match ws_fut.await {
Ok(Ok(s)) => s,
Ok(Err(e)) => {
debug!("Remote relay WS handshake failed: {}", e);
return;
}
Err(e) => {
debug!("Remote relay WS task join failed: {}", e);
return;
}
};
info!("Remote relay browser connected");
let (mut tx, mut rx) = ws_stream.split();
let _ = tx.send(Message::Text(r#"{"t":"ok"}"#.to_string())).await;
let ping_interval = tokio::time::interval(tokio::time::Duration::from_secs(30));
tokio::pin!(ping_interval);
let mut last_activity = Instant::now();
const INACTIVITY_TIMEOUT: u64 = 300;
loop {
tokio::select! {
_ = ping_interval.tick() => {
if last_activity.elapsed().as_secs() >= INACTIVITY_TIMEOUT {
info!("Remote relay inactive, closing");
let _ = tx.send(Message::Close(None)).await;
break;
}
if tx.send(Message::Ping(vec![])).await.is_err() {
break;
}
}
input = relay_rx.recv() => {
match input {
Ok(text) => {
if tx.send(Message::Text(text)).await.is_err() {
break;
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
debug!("Remote relay lagged, skipped {} messages", n);
}
Err(broadcast::error::RecvError::Closed) => break,
}
}
msg = rx.next() => {
match msg {
Some(Ok(Message::Pong(_))) | Some(Ok(Message::Text(_))) => {
last_activity = Instant::now();
}
Some(Ok(Message::Ping(data))) => {
last_activity = Instant::now();
let _ = tx.send(Message::Pong(data)).await;
}
Some(Ok(Message::Close(_))) | None => break,
Some(Ok(_)) => { last_activity = Instant::now(); }
Some(Err(e)) => {
debug!("Remote relay stream error: {}", e);
break;
}
}
}
}
}
info!("Remote relay browser disconnected");
});
}
Ok(response)
}
}

View File

@ -149,6 +149,8 @@ impl RpcHandler {
];
let is_tailscale = package_id == "tailscale";
// Explicit DNS alias for aardvark-dns (must outlive run_args)
let network_alias_flag = format!("--network-alias={}", container_name);
// Network mode
if is_tailscale {
@ -182,6 +184,7 @@ impl RpcHandler {
.await;
if net_check.map(|s| s.success()).unwrap_or(false) {
run_args.push("--network=archy-net");
run_args.push(&network_alias_flag);
} else {
tracing::error!(
"archy-net network does not exist — {} will use default network. \

View File

@ -2,5 +2,11 @@ FROM 80.71.235.15:3000/archipelago/nginx:1.27.4-alpine
COPY index.html /usr/share/nginx/html/
COPY 50x.html /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Run nginx as root to avoid chown failures in rootless Podman user namespaces
RUN sed -i 's/^user nginx;/user root;/' /etc/nginx/nginx.conf && \
mkdir -p /var/cache/nginx/client_temp /var/cache/nginx/proxy_temp \
/var/cache/nginx/fastcgi_temp /var/cache/nginx/uwsgi_temp \
/var/cache/nginx/scgi_temp
EXPOSE 8334
ENTRYPOINT []
CMD ["nginx", "-g", "daemon off;"]

View File

@ -3,5 +3,11 @@ COPY index.html /usr/share/nginx/html/
COPY 50x.html /usr/share/nginx/html/
COPY qrcode.js /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Run nginx as root to avoid chown failures in rootless Podman user namespaces
RUN sed -i 's/^user nginx;/user root;/' /etc/nginx/nginx.conf && \
mkdir -p /var/cache/nginx/client_temp /var/cache/nginx/proxy_temp \
/var/cache/nginx/fastcgi_temp /var/cache/nginx/uwsgi_temp \
/var/cache/nginx/scgi_temp
EXPOSE 50002
ENTRYPOINT []
CMD ["nginx", "-g", "daemon off;"]

View File

@ -17,6 +17,11 @@ COPY bg-intro.jpg /usr/share/nginx/html/assets/img/
# Copy nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Run nginx as root to avoid chown failures in rootless Podman user namespaces
RUN sed -i 's/^user nginx;/user root;/' /etc/nginx/nginx.conf && \
mkdir -p /var/cache/nginx/client_temp /var/cache/nginx/proxy_temp \
/var/cache/nginx/fastcgi_temp /var/cache/nginx/uwsgi_temp \
/var/cache/nginx/scgi_temp
EXPOSE 8080
ENTRYPOINT []
CMD ["nginx", "-g", "daemon off;"]

View File

@ -301,6 +301,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
plymouth-themes \
zstd \
python3 \
apache2-utils \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

View File

@ -374,7 +374,9 @@ log "=== Tier 1: Databases & Core Infrastructure ==="
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots'; then
log "Creating Bitcoin Knots..."
mkdir -p /var/lib/archipelago/bitcoin
DISK_GB=$(df --output=size -BG / 2>/dev/null | tail -1 | tr -dc '0-9')
# Check the DATA partition size, not root — Bitcoin data goes to /var/lib/archipelago
DISK_GB=$(df --output=size -BG /var/lib/archipelago 2>/dev/null | tail -1 | tr -dc '0-9')
[ -z "$DISK_GB" ] && DISK_GB=$(df --output=size -BG / 2>/dev/null | tail -1 | tr -dc '0-9')
if [ "${DISK_GB:-0}" -lt 1000 ]; then
BTC_EXTRA_ARGS="-prune=550"
BTC_DBCACHE=512
@ -385,8 +387,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch
log " Large disk (${DISK_GB}GB) — enabling txindex"
fi
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped \
--health-cmd="bitcoin-cli -rpcuser=\$BITCOIN_RPC_USER -rpcpassword=\$BITCOIN_RPC_PASS getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit bitcoin-knots) --network archy-net \
--health-cmd="bitcoin-cli -rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS getblockchaininfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit bitcoin-knots) --network archy-net --network-alias bitcoin-knots \
$ADD_HOST_FLAG \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
@ -433,7 +435,7 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-d
mkdir -p /var/lib/archipelago/mysql-mempool
$DOCKER run -d --name archy-mempool-db --restart unless-stopped \
--health-cmd="mariadb -uroot -e 'SELECT 1' || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-mempool-db) --network archy-net \
--memory=$(mem_limit archy-mempool-db) --network archy-net --network-alias archy-mempool-db \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \
@ -455,7 +457,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then
mkdir -p /var/lib/archipelago/electrumx
$DOCKER run -d --name electrumx --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8000/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit electrumx) --network archy-net \
--memory=$(mem_limit electrumx) --network archy-net --network-alias electrumx \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 50001:50001 -v /var/lib/archipelago/electrumx:/data \
@ -472,7 +474,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then
mkdir -p /var/lib/archipelago/mempool
$DOCKER run -d --name mempool-api --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8999/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit mempool-api) --network archy-net \
--memory=$(mem_limit mempool-api) --network archy-net --network-alias mempool-api \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
@ -489,7 +491,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web|
log "Creating mempool frontend..."
$DOCKER run -d --name archy-mempool-web --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8080/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-mempool-web) --network archy-net \
--memory=$(mem_limit archy-mempool-web) --network archy-net --network-alias archy-mempool-web \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 4080:8080 -e FRONTEND_HTTP_PORT=8080 -e BACKEND_MAINNET_HTTP_HOST=mempool-api \
@ -530,7 +532,7 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db
mkdir -p /var/lib/archipelago/postgres-btcpay
$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \
--health-cmd="pg_isready -U postgres || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-btcpay-db) --network archy-net \
--memory=$(mem_limit archy-btcpay-db) --network archy-net --network-alias archy-btcpay-db \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \
@ -553,7 +555,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the
mkdir -p /var/lib/archipelago/nbxplorer
$DOCKER run -d --name archy-nbxplorer --restart unless-stopped \
--health-cmd="curl -sf http://localhost:32838/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-nbxplorer) --network archy-net \
--memory=$(mem_limit archy-nbxplorer) --network archy-net --network-alias archy-nbxplorer \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
@ -571,7 +573,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then
mkdir -p /var/lib/archipelago/btcpay
$DOCKER run -d --name btcpay-server --restart unless-stopped \
--health-cmd="curl -sf http://localhost:49392/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit btcpay-server) --network archy-net \
--memory=$(mem_limit btcpay-server) --network archy-net --network-alias btcpay-server \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \
@ -626,7 +628,7 @@ LNDCONF
fi
$DOCKER run -d --name lnd --restart unless-stopped \
--health-cmd="curl -sf --insecure https://localhost:8080/v1/getinfo || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit lnd) --network archy-net \
--memory=$(mem_limit lnd) --network archy-net --network-alias lnd \
$ADD_HOST_FLAG \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE --cap-add NET_RAW \
--security-opt no-new-privileges:true \
@ -642,7 +644,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then
mkdir -p /var/lib/archipelago/fedimint
$DOCKER run -d --name fedimint --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8174/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit fedimint) --network archy-net \
--memory=$(mem_limit fedimint) --network archy-net --network-alias fedimint \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8173:8173 -p 8174:8174 -p 8175:8175 \
@ -667,7 +669,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
log " LND detected — using lnd mode"
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8175/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit fedimint-gateway) --network archy-net \
--memory=$(mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8176:8176 \
@ -684,7 +686,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
log " No LND found — using ldk (built-in Lightning)"
$DOCKER run -d --name fedimint-gateway --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8175/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit fedimint-gateway) --network archy-net \
--memory=$(mem_limit fedimint-gateway) --network archy-net --network-alias fedimint-gateway \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8176:8176 -p 9737:9737 \