fix: container stack installers, DNS resolution, uninstall cleanup

- Replace aardvark-dns container names with host.containers.internal
  for all cross-app connections (LND→Bitcoin, ElectrumX→Bitcoin,
  Mempool→ElectrumX, Fedimint→Bitcoin, NBXplorer→Bitcoin P2P+RPC)
- Add BTCPay multi-container stack installer (postgres + nbxplorer +
  btcpay-server) with proper secrets, data dir ownership, NOAUTH
- Add Mempool multi-container stack installer (mariadb + mempool-api +
  mempool-frontend) with host.containers.internal for RPC
- Immediately remove apps from state on uninstall (no 3-min ghost delay)
- Include archy-bitcoin-ui in bitcoin uninstall container list
- Fix LND UI port 8081 (was 8080, conflicting with LND gRPC)
- Fix ElectrumX UI: proxy /electrs-status to backend, cache-busting
  headers, graceful fallback when backend returns HTML
- Add Tor hidden services for ElectrumX and LND in torrc template
- Remove unused detect_bitcoin_container_name() (replaced by
  host.containers.internal)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-08 23:29:50 +02:00
parent b30f41f3d7
commit 07dff3e4ca
8 changed files with 397 additions and 37 deletions

View File

@ -6,25 +6,6 @@ use anyhow::{Context, Result};
#[allow(dead_code)] #[allow(dead_code)]
pub(super) const TRUSTED_REGISTRIES: &[&str] = &["docker.io/", "ghcr.io/", "localhost/", "80.71.235.15:3000/"]; pub(super) const TRUSTED_REGISTRIES: &[&str] = &["docker.io/", "ghcr.io/", "localhost/", "80.71.235.15:3000/"];
/// Detect which Bitcoin container is running on archy-net for DNS resolution.
/// Returns the container name to use as the RPC host (e.g., "bitcoin-knots").
pub(super) fn detect_bitcoin_container_name() -> String {
// Synchronous check — called from get_app_config which is sync
let output = std::process::Command::new("podman")
.args(["ps", "--format", "{{.Names}}"])
.output();
if let Ok(out) = output {
let names = String::from_utf8_lossy(&out.stdout);
for candidate in &["bitcoin-knots", "bitcoin-core", "bitcoin"] {
if names.lines().any(|l| l.trim() == *candidate) {
return candidate.to_string();
}
}
}
// Default to bitcoin-knots (most common)
"bitcoin-knots".to_string()
}
/// Validate Docker image against trusted registry allowlist. /// Validate Docker image against trusted registry allowlist.
pub(super) fn is_valid_docker_image(image: &str) -> bool { pub(super) fn is_valid_docker_image(image: &str) -> bool {
if image.is_empty() || image.len() > 256 { if image.is_empty() || image.len() > 256 {
@ -318,7 +299,7 @@ pub(super) fn all_container_names(package_id: &str) -> Vec<String> {
"bitcoin" | "bitcoin-core" | "bitcoin-knots" => vec![ "bitcoin" | "bitcoin-core" | "bitcoin-knots" => vec![
"bitcoin-knots".into(), "bitcoin".into(), "bitcoin-core".into(), "bitcoin-knots".into(), "bitcoin".into(), "bitcoin-core".into(),
"archy-bitcoin-knots".into(), "archy-bitcoin".into(), "archy-bitcoin-knots".into(), "archy-bitcoin".into(),
"bitcoin-ui".into(), "bitcoin-ui".into(), "archy-bitcoin-ui".into(),
], ],
// LND + UI // LND + UI
"lnd" => vec!["lnd".into(), "archy-lnd".into(), "archy-lnd-ui".into()], "lnd" => vec!["lnd".into(), "archy-lnd".into(), "archy-lnd-ui".into()],
@ -426,6 +407,24 @@ fn read_secret(name: &str, default: &str) -> String {
.unwrap_or_else(|_| default.to_string()) .unwrap_or_else(|_| default.to_string())
} }
/// Read a secret or generate and persist a random one if it doesn't exist.
pub(super) async fn read_or_generate_secret(name: &str) -> String {
let path = format!("/var/lib/archipelago/secrets/{}", name);
if let Ok(val) = tokio::fs::read_to_string(&path).await {
let trimmed = val.trim().to_string();
if !trimmed.is_empty() {
return trimmed;
}
}
// Generate a 24-byte random password (hex-encoded = 48 chars)
let mut buf = [0u8; 24];
rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut buf);
let secret = hex::encode(buf);
let _ = tokio::fs::create_dir_all("/var/lib/archipelago/secrets").await;
let _ = tokio::fs::write(&path, &secret).await;
secret
}
/// Read the node-level Nostr secret key (hex) for identity-aware apps. /// Read the node-level Nostr secret key (hex) for identity-aware apps.
/// Returns empty string if not yet generated. /// Returns empty string if not yet generated.
fn read_nostr_secret_hex() -> String { fn read_nostr_secret_hex() -> String {
@ -491,9 +490,9 @@ pub(super) async fn get_app_config(
"--bitcoin.node=bitcoind".to_string(), "--bitcoin.node=bitcoind".to_string(),
format!("--bitcoind.rpcuser={}", rpc_user), format!("--bitcoind.rpcuser={}", rpc_user),
format!("--bitcoind.rpcpass={}", rpc_pass), format!("--bitcoind.rpcpass={}", rpc_pass),
"--bitcoind.rpchost=bitcoin-knots:8332".to_string(), "--bitcoind.rpchost=host.containers.internal:8332".to_string(),
"--bitcoind.zmqpubrawblock=tcp://bitcoin-knots:28332".to_string(), "--bitcoind.zmqpubrawblock=tcp://host.containers.internal:28332".to_string(),
"--bitcoind.zmqpubrawtx=tcp://bitcoin-knots:28333".to_string(), "--bitcoind.zmqpubrawtx=tcp://host.containers.internal:28333".to_string(),
"--rpclisten=0.0.0.0:10009".to_string(), "--rpclisten=0.0.0.0:10009".to_string(),
"--restlisten=0.0.0.0:8080".to_string(), "--restlisten=0.0.0.0:8080".to_string(),
"--listen=0.0.0.0:9735".to_string(), "--listen=0.0.0.0:9735".to_string(),
@ -528,7 +527,7 @@ pub(super) async fn get_app_config(
vec!["/var/lib/archipelago/mempool:/data".to_string()], vec!["/var/lib/archipelago/mempool:/data".to_string()],
vec![ vec![
"MEMPOOL_BACKEND=electrum".to_string(), "MEMPOOL_BACKEND=electrum".to_string(),
"ELECTRUM_HOST=electrumx".to_string(), "ELECTRUM_HOST=host.containers.internal".to_string(),
"ELECTRUM_PORT=50001".to_string(), "ELECTRUM_PORT=50001".to_string(),
"ELECTRUM_TLS_ENABLED=false".to_string(), "ELECTRUM_TLS_ENABLED=false".to_string(),
format!("CORE_RPC_HOST={}", host_ip), format!("CORE_RPC_HOST={}", host_ip),
@ -545,15 +544,13 @@ pub(super) async fn get_app_config(
None, None,
), ),
"electrumx" | "mempool-electrs" | "electrs" => { "electrumx" | "mempool-electrs" | "electrs" => {
// Detect which bitcoin container is running for archy-net DNS resolution
let bitcoin_host = detect_bitcoin_container_name();
( (
vec!["50001:50001".to_string()], vec!["50001:50001".to_string()],
vec!["/var/lib/archipelago/electrumx:/data".to_string()], vec!["/var/lib/archipelago/electrumx:/data".to_string()],
vec![ vec![
format!( format!(
"DAEMON_URL=http://{}:{}@{}:8332/", "DAEMON_URL=http://{}:{}@host.containers.internal:8332/",
rpc_user, rpc_pass, bitcoin_host rpc_user, rpc_pass
), ),
"COIN=Bitcoin".to_string(), "COIN=Bitcoin".to_string(),
"DB_DIRECTORY=/data".to_string(), "DB_DIRECTORY=/data".to_string(),
@ -757,7 +754,7 @@ pub(super) async fn get_app_config(
Some(vec![ Some(vec![
"--data-dir".to_string(), "--data-dir".to_string(),
"/data".to_string(), "/data".to_string(),
format!("--bitcoind-url=http://{}:{}@bitcoin-knots:8332", rpc_user, rpc_pass), format!("--bitcoind-url=http://{}:{}@host.containers.internal:8332", rpc_user, rpc_pass),
]), ]),
), ),
"fedimint-gateway" => ( "fedimint-gateway" => (

View File

@ -68,6 +68,12 @@ impl RpcHandler {
if package_id == "penpot" || package_id == "penpot-frontend" { if package_id == "penpot" || package_id == "penpot-frontend" {
return self.install_penpot_stack().await; return self.install_penpot_stack().await;
} }
if matches!(package_id, "btcpay-server" | "btcpayserver" | "btcpay") {
return self.install_btcpay_stack().await;
}
if matches!(package_id, "mempool" | "mempool-web") {
return self.install_mempool_stack().await;
}
// Dependency checks // Dependency checks
let deps = detect_running_deps().await?; let deps = detect_running_deps().await?;
@ -692,7 +698,7 @@ bitcoin.mainnet=true\n\
bitcoin.node=bitcoind\n\ bitcoin.node=bitcoind\n\
\n\ \n\
[Bitcoind]\n\ [Bitcoind]\n\
bitcoind.rpchost=bitcoin-knots:8332\n\ bitcoind.rpchost=host.containers.internal:8332\n\
bitcoind.rpcuser={user}\n\ bitcoind.rpcuser={user}\n\
bitcoind.rpcpass={pass}\n\ bitcoind.rpcpass={pass}\n\
bitcoind.rpcpolling=true\n\ bitcoind.rpcpolling=true\n\

View File

@ -374,6 +374,25 @@ impl RpcHandler {
removed removed
); );
// Immediately remove from in-memory state so the UI updates without
// waiting for the scanner's absence threshold (3 scans × 60s each).
{
let (mut data, _rev) = self.state_manager.get_snapshot().await;
let before = data.package_data.len();
data.package_data.remove(package_id);
// Also remove any alias keys (e.g. "bitcoin-knots" vs "bitcoin")
let aliases: Vec<String> = data.package_data.keys()
.filter(|k| super::config::all_container_names(package_id)
.iter().any(|c| c.strip_prefix("archy-").unwrap_or(c) == k.as_str()))
.cloned().collect();
for alias in &aliases {
data.package_data.remove(alias);
}
if data.package_data.len() < before {
self.state_manager.update_data(data).await;
}
}
Ok(serde_json::json!({ Ok(serde_json::json!({
"status": "uninstalled", "status": "uninstalled",
"stopped": stopped, "stopped": stopped,

View File

@ -1,4 +1,4 @@
//! Multi-container app stack installers (Immich, Penpot). //! Multi-container app stack installers (Immich, Penpot, BTCPay, Mempool).
//! //!
//! Each stack pulls multiple images, creates a private network, and starts //! Each stack pulls multiple images, creates a private network, and starts
//! containers in dependency order. //! containers in dependency order.
@ -7,6 +7,8 @@ use crate::api::rpc::RpcHandler;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use tracing::info; use tracing::info;
const REGISTRY: &str = "80.71.235.15:3000/archipelago";
/// Pull an image with retry and exponential backoff (3 attempts). /// Pull an image with retry and exponential backoff (3 attempts).
async fn pull_image_with_retry(image: &str) -> Result<()> { async fn pull_image_with_retry(image: &str) -> Result<()> {
const MAX_ATTEMPTS: u32 = 3; const MAX_ATTEMPTS: u32 = 3;
@ -361,4 +363,318 @@ impl RpcHandler {
"message": "Penpot stack installed and started" "message": "Penpot stack installed and started"
})) }))
} }
/// Install BTCPay stack (postgres + nbxplorer + btcpay-server).
pub(super) async fn install_btcpay_stack(&self) -> Result<serde_json::Value> {
use super::install::install_log;
let check = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to list containers")?;
let stdout = String::from_utf8_lossy(&check.stdout);
if stdout.lines().any(|l| l.trim() == "btcpay-server") {
return Err(anyhow::anyhow!(
"BTCPay already installed. Stop and remove it first."
));
}
// Dependency check: Bitcoin must be running
let deps = super::dependencies::detect_running_deps().await?;
super::dependencies::check_install_deps("btcpay-server", &deps)?;
install_log("INSTALL START: btcpay-server (stack: postgres + nbxplorer + btcpay)").await;
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
let host_ip = &self.config.host_ip;
// Read or generate btcpay DB password
let db_pass = super::config::read_or_generate_secret("btcpay-db-password").await;
let images = [
&format!("{}/postgres:15.17", REGISTRY),
&format!("{}/nbxplorer:2.6.0", REGISTRY),
&format!("{}/btcpayserver:1.13.7", REGISTRY),
];
for img in &images {
pull_image_with_retry(img).await?;
}
// Create data dirs (chown to current user so rootless podman can write)
let _ = tokio::process::Command::new("sudo")
.args([
"mkdir", "-p",
"/var/lib/archipelago/postgres-btcpay",
"/var/lib/archipelago/btcpay/Main",
"/var/lib/archipelago/nbxplorer/Main",
])
.output()
.await;
let user = std::env::var("USER").unwrap_or_else(|_| "archipelago".to_string());
for dir in &[
"/var/lib/archipelago/postgres-btcpay",
"/var/lib/archipelago/btcpay",
"/var/lib/archipelago/nbxplorer",
] {
let _ = tokio::process::Command::new("sudo")
.args(["chown", "-R", &format!("{}:{}", user, user), dir])
.output()
.await;
}
// Ensure archy-net exists
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "archy-net"])
.output()
.await;
// 1. PostgreSQL
let _ = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "archy-btcpay-db",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "archy-btcpay-db",
"--memory=512m",
"-v", "/var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data",
"-e", "POSTGRES_DB=btcpay",
"-e", "POSTGRES_USER=btcpay",
"-e", &format!("POSTGRES_PASSWORD={}", db_pass),
&format!("{}/postgres:15.17", REGISTRY),
])
.output()
.await;
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
// Create nbxplorer database (superuser is "btcpay", not "postgres")
let _ = tokio::process::Command::new("podman")
.args([
"exec", "archy-btcpay-db",
"psql", "-U", "btcpay", "-c", "CREATE DATABASE nbxplorer;",
])
.output()
.await;
// 2. NBXplorer
let _ = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "archy-nbxplorer",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "archy-nbxplorer",
"--memory=512m",
"-p", "32838:32838",
"-v", "/var/lib/archipelago/nbxplorer:/data",
"-e", "NBXPLORER_DATADIR=/data",
"-e", "NBXPLORER_NETWORK=mainnet",
"-e", "NBXPLORER_CHAINS=btc",
"-e", "NBXPLORER_BIND=0.0.0.0:32838",
"-e", "NBXPLORER_BTCRPCURL=http://host.containers.internal:8332",
"-e", &format!("NBXPLORER_BTCRPCUSER={}", rpc_user),
"-e", &format!("NBXPLORER_BTCRPCPASSWORD={}", rpc_pass),
"-e", "NBXPLORER_BTCNODEENDPOINT=host.containers.internal:8333",
"-e", "NBXPLORER_NOAUTH=1",
"-e", &format!(
"NBXPLORER_POSTGRES=User ID=btcpay;Password={};Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true",
db_pass
),
&format!("{}/nbxplorer:2.6.0", REGISTRY),
])
.output()
.await;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
// 3. BTCPay Server
let run = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "btcpay-server",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "btcpay-server",
"--memory=1g",
"-p", "23000:49392",
"-v", "/var/lib/archipelago/btcpay:/datadir",
"-e", "ASPNETCORE_URLS=http://0.0.0.0:49392",
"-e", "BTCPAY_PROTOCOL=http",
"-e", &format!("BTCPAY_HOST={}:23000", host_ip),
"-e", "BTCPAY_CHAINS=btc",
"-e", "BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838",
"-e", &format!("BTCPAY_BTCRPCURL=http://host.containers.internal:8332"),
"-e", &format!("BTCPAY_BTCRPCUSER={}", rpc_user),
"-e", &format!("BTCPAY_BTCRPCPASSWORD={}", rpc_pass),
"-e", &format!(
"BTCPAY_POSTGRES=User ID=btcpay;Password={};Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true",
db_pass
),
&format!("{}/btcpayserver:1.13.7", REGISTRY),
])
.output()
.await
.context("Failed to start btcpay-server")?;
if !run.status.success() {
let stderr = String::from_utf8_lossy(&run.stderr);
install_log(&format!("INSTALL FAIL: btcpay-server — {}", stderr)).await;
return Err(anyhow::anyhow!("Failed to start BTCPay Server: {}", stderr));
}
install_log("INSTALL OK: btcpay-server stack").await;
info!("BTCPay stack installed and started");
Ok(serde_json::json!({
"success": true,
"package_id": "btcpay-server",
"message": "BTCPay stack installed and started",
"container_id": String::from_utf8_lossy(&run.stdout).trim().to_string(),
}))
}
/// Install Mempool stack (mariadb + mempool-api + mempool-web).
pub(super) async fn install_mempool_stack(&self) -> Result<serde_json::Value> {
use super::install::install_log;
let check = tokio::process::Command::new("podman")
.args(["ps", "-a", "--format", "{{.Names}}"])
.output()
.await
.context("Failed to list containers")?;
let stdout = String::from_utf8_lossy(&check.stdout);
if stdout.lines().any(|l| l.trim() == "mempool" || l.trim() == "archy-mempool-web") {
return Err(anyhow::anyhow!(
"Mempool already installed. Stop and remove it first."
));
}
// Dependency check: Bitcoin + ElectrumX must be running
let deps = super::dependencies::detect_running_deps().await?;
super::dependencies::check_install_deps("mempool", &deps)?;
install_log("INSTALL START: mempool (stack: mariadb + mempool-api + mempool-web)").await;
let (rpc_user, rpc_pass) = crate::bitcoin_rpc::bitcoin_rpc_credentials().await;
let db_pass = super::config::read_or_generate_secret("mempool-db-password").await;
let root_pass = super::config::read_or_generate_secret("mempool-db-root-password").await;
let images = [
&format!("{}/mariadb:11.4.10", REGISTRY),
&format!("{}/mempool-backend:v3.0.0", REGISTRY),
&format!("{}/mempool-frontend:v3.0.0", REGISTRY),
];
for img in &images {
pull_image_with_retry(img).await?;
}
// Create data dirs (chown to current user so rootless podman can write)
let _ = tokio::process::Command::new("sudo")
.args([
"mkdir", "-p",
"/var/lib/archipelago/mysql-mempool",
"/var/lib/archipelago/mempool",
])
.output()
.await;
let user = std::env::var("USER").unwrap_or_else(|_| "archipelago".to_string());
for dir in &[
"/var/lib/archipelago/mysql-mempool",
"/var/lib/archipelago/mempool",
] {
let _ = tokio::process::Command::new("sudo")
.args(["chown", "-R", &format!("{}:{}", user, user), dir])
.output()
.await;
}
// Ensure archy-net exists
let _ = tokio::process::Command::new("podman")
.args(["network", "create", "archy-net"])
.output()
.await;
// 1. MariaDB
let _ = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "archy-mempool-db",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "archy-mempool-db",
"--memory=512m",
"-v", "/var/lib/archipelago/mysql-mempool:/var/lib/mysql",
"-e", "MYSQL_DATABASE=mempool",
"-e", "MYSQL_USER=mempool",
"-e", &format!("MYSQL_PASSWORD={}", db_pass),
"-e", &format!("MYSQL_ROOT_PASSWORD={}", root_pass),
&format!("{}/mariadb:11.4.10", REGISTRY),
])
.output()
.await;
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
// 2. Mempool API backend
let _ = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "mempool-api",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "mempool-api",
"--memory=512m",
"-p", "8999:8999",
"-v", "/var/lib/archipelago/mempool:/data",
"-e", "MEMPOOL_BACKEND=electrum",
"-e", "ELECTRUM_HOST=host.containers.internal",
"-e", "ELECTRUM_PORT=50001",
"-e", "ELECTRUM_TLS_ENABLED=false",
"-e", "CORE_RPC_HOST=host.containers.internal",
"-e", "CORE_RPC_PORT=8332",
"-e", &format!("CORE_RPC_USERNAME={}", rpc_user),
"-e", &format!("CORE_RPC_PASSWORD={}", rpc_pass),
"-e", "DATABASE_ENABLED=true",
"-e", "DATABASE_HOST=archy-mempool-db",
"-e", "DATABASE_DATABASE=mempool",
"-e", "DATABASE_USERNAME=mempool",
"-e", &format!("DATABASE_PASSWORD={}", db_pass),
&format!("{}/mempool-backend:v3.0.0", REGISTRY),
])
.output()
.await;
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
// 3. Mempool frontend
let run = tokio::process::Command::new("podman")
.args([
"run", "-d",
"--name", "mempool",
"--restart", "unless-stopped",
"--network", "archy-net",
"--network-alias", "mempool",
"--memory=256m",
"-p", "4080:8080",
"-e", "FRONTEND_HTTP_PORT=8080",
"-e", "BACKEND_MAINNET_HTTP_HOST=mempool-api",
&format!("{}/mempool-frontend:v3.0.0", REGISTRY),
])
.output()
.await
.context("Failed to start mempool frontend")?;
if !run.status.success() {
let stderr = String::from_utf8_lossy(&run.stderr);
install_log(&format!("INSTALL FAIL: mempool — {}", stderr)).await;
return Err(anyhow::anyhow!("Failed to start Mempool: {}", stderr));
}
install_log("INSTALL OK: mempool stack").await;
info!("Mempool stack installed and started");
Ok(serde_json::json!({
"success": true,
"package_id": "mempool",
"message": "Mempool stack installed and started",
"container_id": String::from_utf8_lossy(&run.stdout).trim().to_string(),
}))
}
} }

View File

@ -355,11 +355,15 @@
async function updateStatus() { async function updateStatus() {
try { try {
var resp = await fetch('/electrs-status'); var resp = await fetch('/electrs-status', { cache: 'no-store' });
if (!resp.ok) { if (!resp.ok) {
throw new Error('Backend unavailable (HTTP ' + resp.status + ')'); throw new Error('Backend unavailable (HTTP ' + resp.status + ')');
} }
var data = await resp.json(); var text = await resp.text();
if (text.trim().charAt(0) !== '{') {
throw new Error('Waiting for Archipelago backend...');
}
var data = JSON.parse(text);
// Extract Tor onion from status response // Extract Tor onion from status response
if (data.tor_onion && !torOnion) { if (data.tor_onion && !torOnion) {

View File

@ -5,9 +5,17 @@ server {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
# electrs-status is fetched via absolute /electrs-status path, location /electrs-status {
# handled by the host nginx backend at :5678 directly proxy_pass http://127.0.0.1:5678/electrs-status;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_connect_timeout 10s;
proxy_read_timeout 10s;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
location / { location / {
add_header Cache-Control "no-cache";
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
} }

View File

@ -1,5 +1,5 @@
server { server {
listen 8080; listen 8081;
server_name _; server_name _;
root /usr/share/nginx/html; root /usr/share/nginx/html;

View File

@ -12,9 +12,19 @@ DataDirectory /var/lib/archipelago/tor
HiddenServiceDir /var/lib/archipelago/tor/hidden_service_archipelago/ HiddenServiceDir /var/lib/archipelago/tor/hidden_service_archipelago/
HiddenServicePort 80 127.0.0.1:80 HiddenServicePort 80 127.0.0.1:80
# LND UI # Bitcoin P2P (protocol service)
HiddenServiceDir /var/lib/archipelago/tor/hidden_service_bitcoin/
HiddenServicePort 8333 127.0.0.1:8333
# ElectrumX (protocol service — wallet connections)
HiddenServiceDir /var/lib/archipelago/tor/hidden_service_electrumx/
HiddenServicePort 50001 127.0.0.1:50001
# LND (protocol service — Lightning Network)
HiddenServiceDir /var/lib/archipelago/tor/hidden_service_lnd/ HiddenServiceDir /var/lib/archipelago/tor/hidden_service_lnd/
HiddenServicePort 80 127.0.0.1:8081 HiddenServicePort 80 127.0.0.1:8081
HiddenServicePort 9735 127.0.0.1:9735
HiddenServicePort 10009 127.0.0.1:10009
# BTCPay Server # BTCPay Server
HiddenServiceDir /var/lib/archipelago/tor/hidden_service_btcpay/ HiddenServiceDir /var/lib/archipelago/tor/hidden_service_btcpay/