security: parameterize node addresses; drop dead APP_URLS config
Demo images / Build & push demo images (push) Failing after 2m16s

Keeps the dev and test tooling an outside contributor would want, and takes
our node addresses out of it.

Scripts that silently defaulted to one of our nodes now require an explicit
host and exit 2 without one: smoke-test.sh, trust-archipelago-cert.sh,
dev-container-test.sh (which also derives its RPC and health URLs from the
SSH target instead of a second hardcoded copy), and image-recipe/dev-branding.sh.
A default that points at a machine the user does not own is worse than no
default: it fails confusingly, or reaches a stranger's device.

Usage examples, mock data and test fixtures move to the RFC 5737
documentation range (192.0.2.0/24). CGNAT test values stay inside
100.64.0.0/10 so the range-check semantics they exercise still hold, and
192.168.1.0/.1/.254 are left alone — those are gateway logic and UI
placeholders, not our addresses.

Playwright and the perf spec defaulted their baseURL to one of our nodes;
they now default to localhost:8100, the local dev server.

Removed neode-ui APP_URLS entirely. It is dead code — exported, never
imported — and it pinned fedimint's *prod* launch URL to 192.168.1.228:8175.
Had anything consumed it, every user's node would have tried to reach an
address that on their LAN is either nothing or someone else's machine.
Deleting beats sanitizing dead config.

Verified: frontend 868/868 vitest across 108 files; archipelago-container
75/75; mesh tests 9/9; audit-secrets 5/5. Zero node addresses and zero node
names remain in tracked files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 10:53:25 -04:00
co-authored by Claude Opus 5
parent 6ba0599639
commit b2c7592840
49 changed files with 154 additions and 174 deletions
+2 -2
View File
@@ -258,8 +258,8 @@ mod tests {
"npub": "npub1abc", "npub": "npub1abc",
"alias": "My Archipelago", "alias": "My Archipelago",
"addresses": [ "addresses": [
{"transport": "udp", "addr": "192.168.1.228:2121", "priority": 10}, {"transport": "udp", "addr": "192.0.2.10:2121", "priority": 10},
{"transport": "tcp", "addr": "192.168.1.228:8443", "priority": 20} {"transport": "tcp", "addr": "192.0.2.10:8443", "priority": 20}
] ]
}]"#, }]"#,
) )
+1 -1
View File
@@ -219,7 +219,7 @@ impl RpcHandler {
anyhow::bail!("npub must be bech32 (npub1...)"); anyhow::bail!("npub must be bech32 (npub1...)");
} }
if !anchor.address.contains(':') { if !anchor.address.contains(':') {
anyhow::bail!("address must be host:port (e.g. 192.168.1.116:8668)"); anyhow::bail!("address must be host:port (e.g. 192.0.2.12:8668)");
} }
let list = fips::anchors::add(&self.config.data_dir, anchor.clone()).await?; let list = fips::anchors::add(&self.config.data_dir, anchor.clone()).await?;
// Push just the newly-added anchor into the running daemon so // Push just the newly-added anchor into the running daemon so
+2 -2
View File
@@ -317,10 +317,10 @@ mod client_ip_tests {
#[test] #[test]
fn loopback_connection_trusts_forwarded_header() { fn loopback_connection_trusts_forwarded_header() {
// nginx on loopback forwards the real client IP — use it. // nginx on loopback forwards the real client IP — use it.
let parts = parts_with(Some("127.0.0.1:44412"), Some("192.168.1.50")); let parts = parts_with(Some("127.0.0.1:44412"), Some("192.0.2.50"));
assert_eq!( assert_eq!(
extract_client_ip(&parts), extract_client_ip(&parts),
"192.168.1.50".parse::<IpAddr>().unwrap() "192.0.2.50".parse::<IpAddr>().unwrap()
); );
} }
@@ -725,7 +725,7 @@ async fn seed_assist_pipeline(storage: &std::path::Path, claude_entity: Option<&
/// HA's zeroconf discovery stores a satellite as a fixed LAN IP. DHCP /// HA's zeroconf discovery stores a satellite as a fixed LAN IP. DHCP
/// renumbering — or the whole node moving to a different network — strands /// renumbering — or the whole node moving to a different network — strands
/// the entry and the speaker silently drops (a test node 2026-07-23: entry /// the entry and the speaker silently drops (a test node 2026-07-23: entry
/// pinned to 192.168.1.241 while the LAN had become 192.168.63.0/24). HA /// pinned to 192.0.2.16 while the LAN had become 192.168.63.0/24). HA
/// never re-resolves on its own. This keeper probes each satellite entry and, /// never re-resolves on its own. This keeper probes each satellite entry and,
/// when one stops answering, sweeps the node's local /24s for the same /// when one stops answering, sweeps the node's local /24s for the same
/// Wyoming port and rewrites the entry to the address that answers. /// Wyoming port and rewrites the entry to the address that answers.
+2 -2
View File
@@ -411,7 +411,7 @@ mod tests {
bind_host = "127.0.0.1" bind_host = "127.0.0.1"
bind_port = 9999 bind_port = 9999
log_level = "debug" log_level = "debug"
host_ip = "192.168.1.100" host_ip = "192.0.2.100"
dev_mode = true dev_mode = true
container_runtime = "Podman" container_runtime = "Podman"
port_offset = 20000 port_offset = 20000
@@ -425,7 +425,7 @@ mod tests {
assert_eq!(config.bind_host, "127.0.0.1"); assert_eq!(config.bind_host, "127.0.0.1");
assert_eq!(config.bind_port, 9999); assert_eq!(config.bind_port, 9999);
assert_eq!(config.log_level, "debug"); assert_eq!(config.log_level, "debug");
assert_eq!(config.host_ip, "192.168.1.100"); assert_eq!(config.host_ip, "192.0.2.100");
assert!(config.dev_mode); assert!(config.dev_mode);
assert_eq!(config.port_offset, 20000); assert_eq!(config.port_offset, 20000);
assert!(!config.nostr_discovery_enabled); assert!(!config.nostr_discovery_enabled);
+1 -1
View File
@@ -123,7 +123,7 @@ pub fn default_public_anchors() -> Vec<SeedAnchor> {
pub struct SeedAnchor { pub struct SeedAnchor {
/// Bech32 `npub1...` of the anchor's FIPS identity. /// Bech32 `npub1...` of the anchor's FIPS identity.
pub npub: String, pub npub: String,
/// Directly-dialable transport address, e.g. `192.168.1.116:8668`. /// Directly-dialable transport address, e.g. `192.0.2.12:8668`.
pub address: String, pub address: String,
/// Transport to use — almost always `"udp"`. /// Transport to use — almost always `"udp"`.
#[serde(default = "default_transport")] #[serde(default = "default_transport")]
+4 -4
View File
@@ -110,8 +110,8 @@ mod tests {
#[test] #[test]
fn route_src_wins() { fn route_src_wins() {
let route = "default via 192.168.1.254 dev wlp3s0 proto dhcp src 192.168.1.116 metric 600"; let route = "default via 192.168.1.254 dev wlp3s0 proto dhcp src 192.0.2.12 metric 600";
assert_eq!(parse_route_src(route).as_deref(), Some("192.168.1.116")); assert_eq!(parse_route_src(route).as_deref(), Some("192.0.2.12"));
} }
#[test] #[test]
@@ -123,8 +123,8 @@ mod tests {
#[test] #[test]
fn addr_inet_strips_prefix() { fn addr_inet_strips_prefix() {
let out = "3: wlp3s0 inet 192.168.1.65/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp3s0\\ valid_lft 85328sec preferred_lft 85328sec"; let out = "3: wlp3s0 inet 192.0.2.17/24 brd 192.0.2.255 scope global dynamic noprefixroute wlp3s0\\ valid_lft 85328sec preferred_lft 85328sec";
assert_eq!(parse_addr_inet(out).as_deref(), Some("192.168.1.65")); assert_eq!(parse_addr_inet(out).as_deref(), Some("192.0.2.17"));
} }
#[test] #[test]
+2 -2
View File
@@ -522,7 +522,7 @@ mod tests {
trust_level: None, trust_level: None,
source: None, source: None,
mesh_contact_id: Some(42), mesh_contact_id: Some(42),
lan_address: Some("192.168.1.100:5678".to_string()), lan_address: Some("192.0.2.100:5678".to_string()),
fips_npub: None, fips_npub: None,
onion_address: Some("abc123.onion".to_string()), onion_address: Some("abc123.onion".to_string()),
last_mesh: None, last_mesh: None,
@@ -536,7 +536,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
peer.address_for(TransportKind::Lan), peer.address_for(TransportKind::Lan),
Some("192.168.1.100:5678".to_string()) Some("192.0.2.100:5678".to_string())
); );
assert_eq!( assert_eq!(
peer.address_for(TransportKind::Tor), peer.address_for(TransportKind::Tor),
+3 -3
View File
@@ -1379,7 +1379,7 @@ impl HostFacts {
#[cfg(test)] #[cfg(test)]
pub fn sample() -> Self { pub fn sample() -> Self {
Self { Self {
host_ip: "192.168.1.116".to_string(), host_ip: "192.0.2.12".to_string(),
host_mdns: "test-node.local".to_string(), host_mdns: "test-node.local".to_string(),
disk_gb: 2000, disk_gb: 2000,
bitcoin_host: "bitcoin-knots".to_string(), bitcoin_host: "bitcoin-knots".to_string(),
@@ -2305,7 +2305,7 @@ app:
secret_env_hash: None, secret_env_hash: None,
}; };
let facts = HostFacts { let facts = HostFacts {
host_ip: "192.168.1.116".to_string(), host_ip: "192.0.2.12".to_string(),
host_mdns: "test-node.local".to_string(), host_mdns: "test-node.local".to_string(),
disk_gb: 2000, disk_gb: 2000,
bitcoin_host: "bitcoin-core".to_string(), bitcoin_host: "bitcoin-core".to_string(),
@@ -2313,7 +2313,7 @@ app:
let out = c.resolve_derived_env(&facts); let out = c.resolve_derived_env(&facts);
assert_eq!(out[0], "FM_API_URL=ws://test-node.local:8174"); assert_eq!(out[0], "FM_API_URL=ws://test-node.local:8174");
assert_eq!(out[1], "INFO=192.168.1.116-2000"); assert_eq!(out[1], "INFO=192.0.2.12-2000");
assert_eq!(out[2], "CORE_RPC_HOST=bitcoin-core"); assert_eq!(out[2], "CORE_RPC_HOST=bitcoin-core");
} }
+1 -1
View File
@@ -999,7 +999,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<input id="relayHttpsInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="https://rpc.example.com/"> <input id="relayHttpsInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="https://rpc.example.com/">
<input id="relayHttpInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="http://192.168.1.2/"> <input id="relayHttpInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="http://192.0.2.2/">
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3"> <div class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3">
<input id="relayTorInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="http://exampleonion.onion/"> <input id="relayTorInput" class="w-full px-3 py-2 rounded-lg bg-black/40 border border-white/10 text-sm text-white placeholder-white/35" placeholder="http://exampleonion.onion/">
+2 -2
View File
@@ -153,7 +153,7 @@
<div style="margin-top:14px"> <div style="margin-top:14px">
<div class="grid"> <div class="grid">
<div class="full"><label>npub</label><input id="aNpub" placeholder="npub1…" autocomplete="off" spellcheck="false" /></div> <div class="full"><label>npub</label><input id="aNpub" placeholder="npub1…" autocomplete="off" spellcheck="false" /></div>
<div><label>Address (host:port)</label><input id="aAddr" placeholder="192.168.1.116:8668" autocomplete="off" spellcheck="false" /></div> <div><label>Address (host:port)</label><input id="aAddr" placeholder="192.0.2.12:8668" autocomplete="off" spellcheck="false" /></div>
<div><label>Transport</label><select id="aTransport"><option value="udp">udp</option><option value="tcp">tcp</option></select></div> <div><label>Transport</label><select id="aTransport"><option value="udp">udp</option><option value="tcp">tcp</option></select></div>
<div class="full"><label>Label (optional)</label><input id="aLabel" placeholder="Home anchor" autocomplete="off" /></div> <div class="full"><label>Label (optional)</label><input id="aLabel" placeholder="Home anchor" autocomplete="off" /></div>
</div> </div>
@@ -396,7 +396,7 @@
const transport = document.getElementById('aTransport').value; const transport = document.getElementById('aTransport').value;
const label = document.getElementById('aLabel').value.trim(); const label = document.getElementById('aLabel').value.trim();
if (!npub.startsWith('npub1')) { notice('anchorNotice', 'error', 'npub must start with npub1…'); return; } if (!npub.startsWith('npub1')) { notice('anchorNotice', 'error', 'npub must start with npub1…'); return; }
if (!address.includes(':')) { notice('anchorNotice', 'error', 'Address must be host:port (e.g. 192.168.1.116:8668).'); return; } if (!address.includes(':')) { notice('anchorNotice', 'error', 'Address must be host:port (e.g. 192.0.2.12:8668).'); return; }
busy(btn, true, 'Adding…'); busy(btn, true, 'Adding…');
notice('anchorNotice', '', ''); notice('anchorNotice', '', '');
try { try {
@@ -243,7 +243,7 @@ jobs:
# Build download base URL (FileBrowser serves from /Builds/) # Build download base URL (FileBrowser serves from /Builds/)
HOST=$(hostname -I 2>/dev/null | awk '{print $1}') HOST=$(hostname -I 2>/dev/null | awk '{print $1}')
BASE_URL="http://${HOST:-192.168.1.228}:8083/Builds/releases/v${VERSION}" BASE_URL="http://${HOST:-192.0.2.10}:8083/Builds/releases/v${VERSION}"
# Generate manifest JSON # Generate manifest JSON
python3 -c " python3 -c "
+4 -4
View File
@@ -18,7 +18,7 @@ The script will automatically:
```bash ```bash
# From your Mac (captures from remote dev server): # From your Mac (captures from remote dev server):
cd image-recipe cd image-recipe
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh DEV_SERVER=archipelago@192.0.2.10 sudo bash build-auto-installer-iso.sh
# From the dev server itself: # From the dev server itself:
cd ~/archy/image-recipe cd ~/archy/image-recipe
@@ -40,7 +40,7 @@ BUILD_FROM_SOURCE=1 sudo bash build-auto-installer-iso.sh
```bash ```bash
# Instead of building on the server, build from your Mac: # Instead of building on the server, build from your Mac:
cd ~/Projects/archy/image-recipe cd ~/Projects/archy/image-recipe
DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh DEV_SERVER=archipelago@192.0.2.10 sudo bash build-auto-installer-iso.sh
``` ```
### Issue: Podman registry not configured ### Issue: Podman registry not configured
@@ -49,7 +49,7 @@ DEV_SERVER=archipelago@192.168.1.228 sudo bash build-auto-installer-iso.sh
**Fix**: **Fix**:
```bash ```bash
ssh archipelago@192.168.1.228 ssh archipelago@192.0.2.10
sudo tee -a /etc/containers/registries.conf <<EOF sudo tee -a /etc/containers/registries.conf <<EOF
[registries.search] [registries.search]
registries = ['docker.io'] registries = ['docker.io']
@@ -65,7 +65,7 @@ cd ~/Projects/archy/image-recipe
## What Gets Captured ## What Gets Captured
From your dev server (192.168.1.228): From your dev server (192.0.2.10):
- ✅ Backend binary: `/usr/local/bin/archipelago` (6.2M) - ✅ Backend binary: `/usr/local/bin/archipelago` (6.2M)
- ✅ Frontend: `/opt/archipelago/web-ui` (~64M) - ✅ Frontend: `/opt/archipelago/web-ui` (~64M)
- ✅ Nginx config: `/etc/nginx/sites-available/default` - ✅ Nginx config: `/etc/nginx/sites-available/default`
@@ -9,7 +9,7 @@
# Set DEV_SERVER to point to your development server. # Set DEV_SERVER to point to your development server.
# #
# Usage: # Usage:
# DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh # DEV_SERVER=archipelago@192.0.2.10 ./build-auto-installer-iso.sh
# OR just: ./build-auto-installer-iso.sh (uses default server) # OR just: ./build-auto-installer-iso.sh (uses default server)
# #
# To build from source instead: # To build from source instead:
@@ -49,7 +49,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
[ -f "$SCRIPT_DIR/../../scripts/image-versions.sh" ] && . "$SCRIPT_DIR/../../scripts/image-versions.sh" [ -f "$SCRIPT_DIR/../../scripts/image-versions.sh" ] && . "$SCRIPT_DIR/../../scripts/image-versions.sh"
# Configuration # Configuration
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}" DEV_SERVER="${DEV_SERVER:-archipelago@192.0.2.10}"
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}" BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
UNBUNDLED="${UNBUNDLED:-0}" UNBUNDLED="${UNBUNDLED:-0}"
ARCH="${ARCH:-x86_64}" ARCH="${ARCH:-x86_64}"
@@ -2486,7 +2486,7 @@ if [ -f /var/lib/archipelago/tor-hostnames/bitcoin ]; then
BOOTSTRAP_ONION=$(cat /var/lib/archipelago/tor-hostnames/bitcoin 2>/dev/null) BOOTSTRAP_ONION=$(cat /var/lib/archipelago/tor-hostnames/bitcoin 2>/dev/null)
fi fi
if [ -n "$BOOTSTRAP_RPC_PASS" ]; then if [ -n "$BOOTSTRAP_RPC_PASS" ]; then
DEV_IP="${DEV_SERVER:-192.168.1.228}" DEV_IP="${DEV_SERVER:-192.0.2.10}"
cat > "$ARCH_DIR/bootstrap.conf" <<BSTRAP cat > "$ARCH_DIR/bootstrap.conf" <<BSTRAP
# Bootstrap Bitcoin node — used during Initial Block Download # Bootstrap Bitcoin node — used during Initial Block Download
# Services connect here until the local node is fully synced # Services connect here until the local node is fully synced
@@ -16,14 +16,14 @@
# #
# Usage: # Usage:
# sudo ./build-unbundled-iso.sh # sudo ./build-unbundled-iso.sh
# DEV_SERVER=archipelago@192.168.1.228 sudo ./build-unbundled-iso.sh # DEV_SERVER=archipelago@192.0.2.10 sudo ./build-unbundled-iso.sh
# BUILD_FROM_SOURCE=1 sudo ./build-unbundled-iso.sh # BUILD_FROM_SOURCE=1 sudo ./build-unbundled-iso.sh
# #
set -e set -e
# Configuration # Configuration
DEV_SERVER="${DEV_SERVER:-archipelago@192.168.1.228}" DEV_SERVER="${DEV_SERVER:-archipelago@192.0.2.10}"
BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}" BUILD_FROM_SOURCE="${BUILD_FROM_SOURCE:-0}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+1 -1
View File
@@ -16,7 +16,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WORK="/tmp/archipelago-dev-branding" WORK="/tmp/archipelago-dev-branding"
PATCHED="$SCRIPT_DIR/results/archipelago-dev-patched.iso" PATCHED="$SCRIPT_DIR/results/archipelago-dev-patched.iso"
CACHED_ISO="$SCRIPT_DIR/results/archipelago-dev-base.iso" CACHED_ISO="$SCRIPT_DIR/results/archipelago-dev-base.iso"
DEV_SERVER="archipelago@192.168.1.228" DEV_SERVER="${DEV_SERVER:?DEV_SERVER must be set, e.g. archipelago@<node-host>}"
SSH_KEY="$HOME/.ssh/archipelago-deploy" SSH_KEY="$HOME/.ssh/archipelago-deploy"
echo "" echo ""
+1 -1
View File
@@ -89,7 +89,7 @@ test('surface-perf: measure every D-09 surface and write the baseline artifact',
} }
const header = { const header = {
baseUrl: baseURL ?? process.env.ARCHY_BASE_URL ?? 'http://192.168.1.228', baseUrl: baseURL ?? process.env.ARCHY_BASE_URL ?? 'http://localhost:8100',
takenAt: new Date().toISOString(), takenAt: new Date().toISOString(),
commit: currentCommit(), commit: currentCommit(),
runs: RUNS, runs: RUNS,
+9 -9
View File
@@ -2877,8 +2877,8 @@ app.post('/rpc/v1', (req, res) => {
return res.json({ return res.json({
result: { result: {
interfaces: [ interfaces: [
{ name: 'eth0', type: 'ethernet', state: 'up', mac: 'a8:a1:59:3c:f2:10', ipv4: ['192.168.1.228/24'] }, { name: 'eth0', type: 'ethernet', state: 'up', mac: 'a8:a1:59:3c:f2:10', ipv4: ['192.0.2.10/24'] },
{ name: 'wlan0', type: 'wifi', state: 'up', mac: 'dc:a6:32:12:ab:cd', ipv4: ['192.168.1.230/24'] }, { name: 'wlan0', type: 'wifi', state: 'up', mac: 'dc:a6:32:12:ab:cd', ipv4: ['192.0.2.14/24'] },
{ name: 'lo', type: 'loopback', state: 'up', mac: '00:00:00:00:00:00', ipv4: ['127.0.0.1/8'] }, { name: 'lo', type: 'loopback', state: 'up', mac: '00:00:00:00:00:00', ipv4: ['127.0.0.1/8'] },
{ name: 'podman0', type: 'bridge', state: 'up', mac: '2e:f4:8a:11:22:33', ipv4: ['10.89.0.1/16'] }, { name: 'podman0', type: 'bridge', state: 'up', mac: '2e:f4:8a:11:22:33', ipv4: ['10.89.0.1/16'] },
{ name: 'tailscale0', type: 'tunnel', state: 'up', mac: '', ipv4: ['100.82.97.63/32'] }, { name: 'tailscale0', type: 'tunnel', state: 'up', mac: '', ipv4: ['100.82.97.63/32'] },
@@ -3075,10 +3075,10 @@ app.post('/rpc/v1', (req, res) => {
return res.json({ return res.json({
result: { result: {
nodes: [ nodes: [
{ did: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2ReMkBe4bR6XBIDNq9', onion: 'disc1abc2def3ghi4jkl5mno6pqr7stu8vwx9yz.onion', pubkey: 'disc1pub', node_address: '192.168.1.50' }, { did: 'did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2ReMkBe4bR6XBIDNq9', onion: 'disc1abc2def3ghi4jkl5mno6pqr7stu8vwx9yz.onion', pubkey: 'disc1pub', node_address: '192.0.2.50' },
{ did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', onion: 'disc2xyz9wvu8tsr7qpo6nml5kji4hgf3edc2ba.onion', pubkey: 'disc2pub', node_address: '192.168.1.51' }, { did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH', onion: 'disc2xyz9wvu8tsr7qpo6nml5kji4hgf3edc2ba.onion', pubkey: 'disc2pub', node_address: '192.0.2.51' },
{ did: 'did:key:z6MkfV2sQpXm4d8YtR1nWc7uHb3eKj9gLa5xPzD6oTiN8rEw', onion: 'disc3mn04pq15rs26tu37vw48xy59za60bc71de.onion', pubkey: 'disc3pub', node_address: '192.168.1.72' }, { did: 'did:key:z6MkfV2sQpXm4d8YtR1nWc7uHb3eKj9gLa5xPzD6oTiN8rEw', onion: 'disc3mn04pq15rs26tu37vw48xy59za60bc71de.onion', pubkey: 'disc3pub', node_address: '192.0.2.72' },
{ did: 'did:key:z6MkrJ8pWx2yNc5vT9qLb4eHu7dKf1gMa3sPzE6oXiQ8nRvw', onion: 'disc4fg82hi93jk04lm15no26pq37rs48tu59vw.onion', pubkey: 'disc4pub', node_address: '100.72.19.44' }, { did: 'did:key:z6MkrJ8pWx2yNc5vT9qLb4eHu7dKf1gMa3sPzE6oXiQ8nRvw', onion: 'disc4fg82hi93jk04lm15no26pq37rs48tu59vw.onion', pubkey: 'disc4pub', node_address: '100.64.0.6' },
{ did: 'did:key:z6MkhT4wQn8xPc2vL6sRb9eYu3dJf7gKa1mNzD5oWiE8tXvq', onion: 'disc5xy60za71bc82de93fg04hi15jk26lm37no.onion', pubkey: 'disc5pub', node_address: '100.101.7.23' }, { did: 'did:key:z6MkhT4wQn8xPc2vL6sRb9eYu3dJf7gKa1mNzD5oWiE8tXvq', onion: 'disc5xy60za71bc82de93fg04hi15jk26lm37no.onion', pubkey: 'disc5pub', node_address: '100.101.7.23' },
], ],
}, },
@@ -3604,7 +3604,7 @@ app.post('/rpc/v1', (req, res) => {
name: 'archy-198', name: 'archy-198',
trust_level: 'trusted', trust_level: 'trusted',
mesh_contact_id: 1, mesh_contact_id: 1,
lan_address: '192.168.1.198:5678', lan_address: '192.0.2.11:5678',
onion_address: 'peer1abc2def3ghi4jkl5mno6pqr7stu8vwx9yz.onion', onion_address: 'peer1abc2def3ghi4jkl5mno6pqr7stu8vwx9yz.onion',
preferred_transport: 'lan', preferred_transport: 'lan',
available_transports: ['mesh', 'lan', 'tor'], available_transports: ['mesh', 'lan', 'tor'],
@@ -3640,7 +3640,7 @@ app.post('/rpc/v1', (req, res) => {
name: 'office-node', name: 'office-node',
trust_level: 'trusted', trust_level: 'trusted',
mesh_contact_id: null, mesh_contact_id: null,
lan_address: '192.168.1.42:5678', lan_address: '192.0.2.42:5678',
onion_address: 'peer4mno6pqr7stu8vwx9yzabc2def3ghi4jkl5.onion', onion_address: 'peer4mno6pqr7stu8vwx9yzabc2def3ghi4jkl5.onion',
preferred_transport: 'lan', preferred_transport: 'lan',
available_transports: ['lan', 'tor'], available_transports: ['lan', 'tor'],
@@ -4438,7 +4438,7 @@ app.post('/rpc/v1', (req, res) => {
ssid: 'CasaDelSol-5G', ssid: 'CasaDelSol-5G',
assoc_ssid: 'CasaDelSol-5G', assoc_ssid: 'CasaDelSol-5G',
encryption: 'psk2', encryption: 'psk2',
ip: '192.168.1.187', ip: '192.0.2.15',
internet: true, internet: true,
radio0_disabled: false, radio0_disabled: false,
sta_iface: 'wifinet1', sta_iface: 'wifinet1',
+1 -1
View File
@@ -8,7 +8,7 @@ export default defineConfig({
timeout: 10_000, timeout: 10_000,
}, },
use: { use: {
baseURL: process.env.ARCHY_BASE_URL ?? 'http://192.168.1.228', baseURL: process.env.ARCHY_BASE_URL ?? 'http://localhost:8100',
viewport: { width: 1440, height: 900 }, viewport: { width: 1440, height: 900 },
screenshot: 'only-on-failure', screenshot: 'only-on-failure',
trace: 'off', trace: 'off',
@@ -25,7 +25,7 @@ describe('useAppLauncherStore', () => {
vi.clearAllMocks() vi.clearAllMocks()
// Default to HTTP to avoid proxy rewriting // Default to HTTP to avoid proxy rewriting
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { origin: 'http://192.168.1.228', protocol: 'http:', hostname: '192.168.1.228' }, value: { origin: 'http://192.0.2.10', protocol: 'http:', hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -71,8 +71,8 @@ describe('useAppLauncherStore', () => {
it('open() never falls through to the iframe overlay', () => { it('open() never falls through to the iframe overlay', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:9999', title: 'Unknown app' }) store.open({ url: 'http://192.0.2.10:9999', title: 'Unknown app' })
expect(openInApp).toHaveBeenCalledWith('http://192.168.1.228:9999') expect(openInApp).toHaveBeenCalledWith('http://192.0.2.10:9999')
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
}) })
}) })
@@ -81,7 +81,7 @@ describe('useAppLauncherStore', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
// Port 8083 maps to /app/filebrowser/ — should route to session // Port 8083 maps to /app/filebrowser/ — should route to session
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' }) store.open({ url: 'http://192.0.2.10:8083', title: 'FileBrowser' })
// Default panel mode: sets panelAppId, doesn't open overlay // Default panel mode: sets panelAppId, doesn't open overlay
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
@@ -128,12 +128,12 @@ describe('useAppLauncherStore', () => {
it('routes BTCPay (port 23000) to full-page session', () => { it('routes BTCPay (port 23000) to full-page session', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:23000', title: 'BTCPay' }) store.open({ url: 'http://192.0.2.10:23000', title: 'BTCPay' })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:23000', 'http://192.0.2.10:23000',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -142,12 +142,12 @@ describe('useAppLauncherStore', () => {
it('normalizes old Nginx Proxy Manager port 81 to 8081', () => { it('normalizes old Nginx Proxy Manager port 81 to 8081', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:81', title: 'Nginx Proxy Manager' }) store.open({ url: 'http://192.0.2.10:81', title: 'Nginx Proxy Manager' })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:8081', 'http://192.0.2.10:8081',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -161,7 +161,7 @@ describe('useAppLauncherStore', () => {
}) })
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:8081', title: 'Nginx Proxy Manager' }) store.open({ url: 'http://192.0.2.10:8081', title: 'Nginx Proxy Manager' })
// Tab-only app on mobile-web: open directly in a new browser tab (the // Tab-only app on mobile-web: open directly in a new browser tab (the
// companion would use the in-app WebView). No session, no route push, no // companion would use the in-app WebView). No session, no route push, no
@@ -170,7 +170,7 @@ describe('useAppLauncherStore', () => {
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockPush).not.toHaveBeenCalled() expect(mockPush).not.toHaveBeenCalled()
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:8081', 'http://192.0.2.10:8081',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -179,10 +179,10 @@ describe('useAppLauncherStore', () => {
it('opens Nginx Proxy Manager in new tab using title hint when URL is path-only', () => { it('opens Nginx Proxy Manager in new tab using title hint when URL is path-only', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'https://192.168.1.228/app/nginx-proxy-manager/', title: 'Nginx Proxy Manager' }) store.open({ url: 'https://192.0.2.10/app/nginx-proxy-manager/', title: 'Nginx Proxy Manager' })
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'https://192.168.1.228/app/nginx-proxy-manager/', 'https://192.0.2.10/app/nginx-proxy-manager/',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -192,10 +192,10 @@ describe('useAppLauncherStore', () => {
it('normalizes legacy Nginx Proxy Manager ports to 8081', () => { it('normalizes legacy Nginx Proxy Manager ports to 8081', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:8181', title: 'Nginx Proxy Manager' }) store.open({ url: 'http://192.0.2.10:8181', title: 'Nginx Proxy Manager' })
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:8081', 'http://192.0.2.10:8081',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -204,10 +204,10 @@ describe('useAppLauncherStore', () => {
it('normalizes legacy Uptime Kuma port 3001 to 3002', () => { it('normalizes legacy Uptime Kuma port 3001 to 3002', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:3001', title: 'Uptime Kuma' }) store.open({ url: 'http://192.0.2.10:3001', title: 'Uptime Kuma' })
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:3002', 'http://192.0.2.10:3002',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -218,10 +218,10 @@ describe('useAppLauncherStore', () => {
it('opens Uptime Kuma in new tab using title hint when URL is path-only', () => { it('opens Uptime Kuma in new tab using title hint when URL is path-only', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'https://192.168.1.228/app/uptime-kuma/', title: 'Uptime Kuma' }) store.open({ url: 'https://192.0.2.10/app/uptime-kuma/', title: 'Uptime Kuma' })
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'https://192.168.1.228/app/uptime-kuma/', 'https://192.0.2.10/app/uptime-kuma/',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -231,12 +231,12 @@ describe('useAppLauncherStore', () => {
it('routes Home Assistant (port 8123) to full-page session', () => { it('routes Home Assistant (port 8123) to full-page session', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:8123', title: 'Home Assistant' }) store.open({ url: 'http://192.0.2.10:8123', title: 'Home Assistant' })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:8123', 'http://192.0.2.10:8123',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -245,12 +245,12 @@ describe('useAppLauncherStore', () => {
it('routes Grafana (port 3000) to full-page session', () => { it('routes Grafana (port 3000) to full-page session', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:3000', title: 'Grafana' }) store.open({ url: 'http://192.0.2.10:3000', title: 'Grafana' })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:3000', 'http://192.0.2.10:3000',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -259,12 +259,12 @@ describe('useAppLauncherStore', () => {
it('opens Gitea path URL in new tab', () => { it('opens Gitea path URL in new tab', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228/app/gitea/', title: 'Gitea' }) store.open({ url: 'http://192.0.2.10/app/gitea/', title: 'Gitea' })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228/app/gitea/', 'http://192.0.2.10/app/gitea/',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -273,7 +273,7 @@ describe('useAppLauncherStore', () => {
it('does not map raw port 3001 to gitea session', () => { it('does not map raw port 3001 to gitea session', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:3001', title: 'Unknown 3001' }) store.open({ url: 'http://192.0.2.10:3001', title: 'Unknown 3001' })
expect(store.panelAppId).toBe(null) expect(store.panelAppId).toBe(null)
expect(store.isOpen).toBe(true) expect(store.isOpen).toBe(true)
@@ -283,11 +283,11 @@ describe('useAppLauncherStore', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
// Use an unresolvable URL so it doesn't route to session // Use an unresolvable URL so it doesn't route to session
store.open({ url: 'http://192.168.1.228:9999', title: 'Unknown', openInNewTab: true }) store.open({ url: 'http://192.0.2.10:9999', title: 'Unknown', openInNewTab: true })
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.228:9999', 'http://192.0.2.10:9999',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -327,13 +327,13 @@ describe('useAppLauncherStore', () => {
it('routes HTTPS same-host apps via session view', () => { it('routes HTTPS same-host apps via session view', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' }, value: { origin: 'https://192.0.2.10', protocol: 'https:', hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
const store = useAppLauncherStore() const store = useAppLauncherStore()
store.open({ url: 'http://192.168.1.228:8083', title: 'FileBrowser' }) store.open({ url: 'http://192.0.2.10:8083', title: 'FileBrowser' })
// Known port — routes to session (panel mode by default) // Known port — routes to session (panel mode by default)
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
@@ -344,17 +344,17 @@ describe('useAppLauncherStore', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
// Unresolvable URL — falls through to iframe overlay // Unresolvable URL — falls through to iframe overlay
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom App' }) store.open({ url: 'http://192.0.2.10:9999', title: 'Custom App' })
expect(store.isOpen).toBe(true) expect(store.isOpen).toBe(true)
expect(store.url).toBe('http://192.168.1.228:9999') expect(store.url).toBe('http://192.0.2.10:9999')
expect(store.title).toBe('Custom App') expect(store.title).toBe('Custom App')
expect(mockWindowOpen).not.toHaveBeenCalled() expect(mockWindowOpen).not.toHaveBeenCalled()
}) })
it('opens unknown different-host URL in iframe overlay', () => { it('opens unknown different-host URL in iframe overlay', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { origin: 'https://192.168.1.228', protocol: 'https:', hostname: '192.168.1.228' }, value: { origin: 'https://192.0.2.10', protocol: 'https:', hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -370,7 +370,7 @@ describe('useAppLauncherStore', () => {
it('close resets state', () => { it('close resets state', () => {
const store = useAppLauncherStore() const store = useAppLauncherStore()
// Use unknown URL to trigger iframe overlay // Use unknown URL to trigger iframe overlay
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' }) store.open({ url: 'http://192.0.2.10:9999', title: 'Custom' })
store.close() store.close()
@@ -385,7 +385,7 @@ describe('useAppLauncherStore', () => {
const mockButton = { focus: vi.fn() } as unknown as HTMLElement const mockButton = { focus: vi.fn() } as unknown as HTMLElement
Object.defineProperty(document, 'activeElement', { value: mockButton, configurable: true }) Object.defineProperty(document, 'activeElement', { value: mockButton, configurable: true })
store.open({ url: 'http://192.168.1.228:9999', title: 'Custom' }) store.open({ url: 'http://192.0.2.10:9999', title: 'Custom' })
store.close() store.close()
expect(store.isOpen).toBe(false) expect(store.isOpen).toBe(false)
+1 -1
View File
@@ -144,7 +144,7 @@ export const dummyApps: Record<string, PackageDataEntry> = {
'interface-addresses': { 'interface-addresses': {
main: { main: {
'tor-address': 'lorabell.onion', 'tor-address': 'lorabell.onion',
'lan-address': 'http://192.168.1.166' 'lan-address': 'http://192.0.2.13'
} }
}, },
status: ServiceStatus.Running status: ServiceStatus.Running
+1 -1
View File
@@ -129,7 +129,7 @@
<p class="text-sm text-white/60 mb-5"> <p class="text-sm text-white/60 mb-5">
The URL should be of the form <span class="font-mono text-white/80">host[:port]/namespace</span> The URL should be of the form <span class="font-mono text-white/80">host[:port]/namespace</span>
for example <span class="font-mono text-white/80">ghcr.io/myorg</span> or for example <span class="font-mono text-white/80">ghcr.io/myorg</span> or
<span class="font-mono text-white/80">192.168.1.50:3000/apps</span>. Registries are <span class="font-mono text-white/80">192.0.2.10:3000/apps</span>. Registries are
added to the end of the list; use "Make primary" to reorder. added to the end of the list; use "Make primary" to reorder.
</p> </p>
<form class="space-y-3" @submit.prevent="submitRegistry"> <form class="space-y-3" @submit.prevent="submitRegistry">
+1 -1
View File
@@ -368,7 +368,7 @@ const backendPort = 5678
function getLaunchUrl(app: BundledApp): string { function getLaunchUrl(app: BundledApp): string {
// Prefer lan_address from backend (for apps with custom UIs) // Prefer lan_address from backend (for apps with custom UIs)
if (app.lan_address) { if (app.lan_address) {
// Replace localhost so Launch works when browsing from another machine (e.g. 192.168.1.228) // Replace localhost so Launch works when browsing from another machine (e.g. a LAN address)
let url = app.lan_address.replace(/localhost/i, currentHost.value) let url = app.lan_address.replace(/localhost/i, currentHost.value)
// LND UI (and other app UIs) need backend URL for live data (logs, getinfo proxy) // LND UI (and other app UIs) need backend URL for live data (logs, getinfo proxy)
if (app.id === 'lnd') { if (app.id === 'lnd') {
@@ -56,7 +56,7 @@ describe('AppSession mobile new-tab apps', () => {
configurable: true, configurable: true,
}) })
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -103,7 +103,7 @@ describe('Server network refresh states', () => {
vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => {
if (request.method === 'network.list-interfaces') { if (request.method === 'network.list-interfaces') {
return Promise.resolve({ return Promise.resolve({
interfaces: [{ name: 'eth0', type: 'ethernet', state: 'up', mac: '00:11:22:33:44:55', ipv4: ['192.168.1.10'] }], interfaces: [{ name: 'eth0', type: 'ethernet', state: 'up', mac: '00:11:22:33:44:55', ipv4: ['192.0.2.10'] }],
}) })
} }
if (request.method === 'network.diagnostics') { if (request.method === 'network.diagnostics') {
@@ -131,7 +131,7 @@ describe('Server network refresh states', () => {
await flushPromises() await flushPromises()
expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('eth0')
expect(wrapper.text()).toContain('192.168.1.10') expect(wrapper.text()).toContain('192.0.2.10')
const pendingInterfaces = deferred<{ interfaces: [] }>() const pendingInterfaces = deferred<{ interfaces: [] }>()
vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => { vi.mocked(rpcClient.call).mockImplementation((request: { method: string }) => {
@@ -143,7 +143,7 @@ describe('Server network refresh states', () => {
await wrapper.vm.$nextTick() await wrapper.vm.$nextTick()
expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('eth0')
expect(wrapper.text()).toContain('192.168.1.10') expect(wrapper.text()).toContain('192.0.2.10')
expect(wrapper.text()).toContain('Refreshing interfaces...') expect(wrapper.text()).toContain('Refreshing interfaces...')
pendingInterfaces.reject(new Error('offline')) pendingInterfaces.reject(new Error('offline'))
@@ -151,7 +151,7 @@ describe('Server network refresh states', () => {
await flushPromises() await flushPromises()
expect(wrapper.text()).toContain('eth0') expect(wrapper.text()).toContain('eth0')
expect(wrapper.text()).toContain('192.168.1.10') expect(wrapper.text()).toContain('192.0.2.10')
}) })
it('keeps Tor services visible while refresh is pending or fails', async () => { it('keeps Tor services visible while refresh is pending or fails', async () => {
@@ -52,39 +52,6 @@ export function resolvePackageKey(routeId: string): string {
/** Apps that depend on Bitcoin being synced */ /** Apps that depend on Bitcoin being synced */
export const BITCOIN_DEPENDENT_APPS = ['lnd', 'electrumx', 'electrs', 'mempool-electrs', 'btcpay-server', 'btcpayserver'] export const BITCOIN_DEPENDENT_APPS = ['lnd', 'electrumx', 'electrs', 'mempool-electrs', 'btcpay-server', 'btcpayserver']
/** App launch URLs for dev and prod environments */
export const APP_URLS: Record<string, { dev: string; prod: string }> = {
'lorabell': { dev: 'http://192.168.1.166', prod: 'http://192.168.1.166' },
'atob': { dev: 'http://localhost:8102', prod: 'https://app.atobitcoin.io' },
'k484': { dev: 'http://localhost:8103', prod: 'http://localhost:8103' },
'bitcoin': { dev: 'http://localhost:8332', prod: 'http://localhost:8332' },
'btcpay-server': { dev: 'http://localhost:23000', prod: 'http://localhost:23000' },
'homeassistant': { dev: 'http://localhost:8123', prod: 'http://localhost:8123' },
'grafana': { dev: 'http://localhost:3000', prod: 'http://localhost:3000' },
'endurain': { dev: 'http://localhost:8080', prod: 'http://localhost:8080' },
'fedimint': { dev: 'http://localhost:8175', prod: 'http://192.168.1.228:8175' },
'fedimint-gateway': { dev: 'http://localhost:8176', prod: 'http://192.168.1.228:8176' },
'morphos-server': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
'lightning-stack': { dev: 'http://localhost:9735', prod: 'http://localhost:9735' },
'mempool': { dev: 'http://localhost:4080', prod: 'http://localhost:4080' },
'ollama': { dev: 'http://localhost:11434', prod: 'http://localhost:11434' },
'searxng': { dev: 'http://localhost:8888', prod: 'http://localhost:8888' },
'nextcloud': { dev: 'http://localhost:8085', prod: 'http://localhost:8085' },
'vaultwarden': { dev: 'http://localhost:8082', prod: 'http://localhost:8082' },
'jellyfin': { dev: 'http://localhost:8096', prod: 'http://localhost:8096' },
'photoprism': { dev: 'http://localhost:2342', prod: 'http://localhost:2342' },
'immich': { dev: 'http://localhost:2283', prod: 'http://localhost:2283' },
'filebrowser': { dev: 'http://localhost:8083', prod: 'http://localhost:8083' },
'nginx-proxy-manager': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
'gitea': { dev: 'http://localhost:3001', prod: 'http://localhost:3001' },
'portainer': { dev: 'http://localhost:9000', prod: 'http://localhost:9000' },
'uptime-kuma': { dev: 'http://localhost:3002', prod: 'http://localhost:3002' },
'tailscale': { dev: 'http://localhost:8240', prod: 'http://localhost:8240' },
'lnd': { dev: 'http://localhost:18083', prod: 'http://localhost:18083' },
'bitcoin-knots': { dev: 'http://localhost:8334', prod: 'http://localhost:8334' },
'botfights': { dev: 'http://localhost:9100', prod: 'http://localhost:9100' },
}
/** V3 onion addresses are 56+ chars + .onion. Placeholders like "btcpay.onion" are not real. */ /** V3 onion addresses are 56+ chars + .onion. Placeholders like "btcpay.onion" are not real. */
export function isRealOnionAddress(addr: string | undefined): boolean { export function isRealOnionAddress(addr: string | undefined): boolean {
return !!(addr && addr.endsWith('.onion') && addr.length >= 60 && addr.length <= 70) return !!(addr && addr.endsWith('.onion') && addr.length >= 60 && addr.length <= 70)
@@ -27,8 +27,8 @@ describe('appOrigin', () => {
}) })
it('keeps the hostname the user actually typed, not a fixed name', () => { it('keeps the hostname the user actually typed, not a fixed name', () => {
setLocation('https:', '100.69.68.39') setLocation('https:', '100.64.0.5')
expect(appOrigin(3000)).toBe('https://100.69.68.39:3000') expect(appOrigin(3000)).toBe('https://100.64.0.5:3000')
}) })
}) })
@@ -16,19 +16,19 @@ describe('appSessionConfig', () => {
it('resolves direct app ports against the current browser host', () => { it('resolves direct app ports against the current browser host', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
expect(resolveAppUrl('mempool')).toBe('http://192.168.1.228:4080') expect(resolveAppUrl('mempool')).toBe('http://192.0.2.10:4080')
expect(resolveAppUrl('indeedhub')).toBe('http://192.168.1.228:7778') expect(resolveAppUrl('indeedhub')).toBe('http://192.0.2.10:7778')
expect(resolveAppUrl('botfights')).toBe('http://192.168.1.228:9100') expect(resolveAppUrl('botfights')).toBe('http://192.0.2.10:9100')
}) })
it('uses manifest-generated launch ports for apps outside the manual override list', () => { it('uses manifest-generated launch ports for apps outside the manual override list', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -36,12 +36,12 @@ describe('appSessionConfig', () => {
// did-wallet's manifest publishes host port 8088 (apps/did-wallet/ // did-wallet's manifest publishes host port 8088 (apps/did-wallet/
// manifest.yml) — assert against the manifest-generated value, which is // manifest.yml) — assert against the manifest-generated value, which is
// exactly what this test exists to protect. // exactly what this test exists to protect.
expect(resolveAppUrl('did-wallet')).toBe('http://192.168.1.228:8088') expect(resolveAppUrl('did-wallet')).toBe('http://192.0.2.10:8088')
}) })
it('does not treat service-only tcp ports as web launch surfaces', () => { it('does not treat service-only tcp ports as web launch surfaces', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -51,21 +51,21 @@ describe('appSessionConfig', () => {
it('keeps NetBird on the unified dashboard proxy port', () => { it('keeps NetBird on the unified dashboard proxy port', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
expect(resolveAppUrl('netbird', undefined, 'http://localhost:8086')).toBe('http://192.168.1.228:8087') expect(resolveAppUrl('netbird', undefined, 'http://localhost:8086')).toBe('http://192.0.2.10:8087')
}) })
it('uses backend runtime URLs for apps with dynamic launch surfaces', () => { it('uses backend runtime URLs for apps with dynamic launch surfaces', () => {
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.228' }, value: { hostname: '192.0.2.10' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
expect(resolveAppUrl('filebrowser', undefined, 'http://localhost:18083')).toBe('http://192.168.1.228:18083') expect(resolveAppUrl('filebrowser', undefined, 'http://localhost:18083')).toBe('http://192.0.2.10:18083')
}) })
}) })
@@ -52,7 +52,7 @@ describe('AppIconGrid', () => {
configurable: true, configurable: true,
}) })
Object.defineProperty(window, 'location', { Object.defineProperty(window, 'location', {
value: { hostname: '192.168.1.198' }, value: { hostname: '192.0.2.11' },
writable: true, writable: true,
configurable: true, configurable: true,
}) })
@@ -112,7 +112,7 @@ describe('AppIconGrid', () => {
await flushPromises() await flushPromises()
expect(mockWindowOpen).toHaveBeenCalledWith( expect(mockWindowOpen).toHaveBeenCalledWith(
'http://192.168.1.198:3001', 'http://192.0.2.11:3001',
'_blank', '_blank',
'noopener,noreferrer', 'noopener,noreferrer',
) )
@@ -91,19 +91,19 @@ describe('fleet data helpers', () => {
node_id: 'abcdef123456', node_id: 'abcdef123456',
node_name: 'Kitchen Node', node_name: 'Kitchen Node',
hostname: 'kitchen-node', hostname: 'kitchen-node',
server_url: 'https://192.168.1.20', server_url: 'https://192.0.2.20',
}) })
const hostOnly = normalizeFleetNode({ const hostOnly = normalizeFleetNode({
node_id: '123456abcdef', node_id: '123456abcdef',
hostname: 'workshop-node', hostname: 'workshop-node',
server_url: 'https://192.168.1.21', server_url: 'https://192.0.2.21',
}) })
const idOnly = normalizeFleetNode({ node_id: 'feedfacecafebeef' }) const idOnly = normalizeFleetNode({ node_id: 'feedfacecafebeef' })
expect(fleetNodeDisplayName(named)).toBe('Kitchen Node') expect(fleetNodeDisplayName(named)).toBe('Kitchen Node')
expect(fleetNodeSubtitle(named)).toBe('kitchen-node') expect(fleetNodeSubtitle(named)).toBe('kitchen-node')
expect(fleetNodeDisplayName(hostOnly)).toBe('workshop-node') expect(fleetNodeDisplayName(hostOnly)).toBe('workshop-node')
expect(fleetNodeSubtitle(hostOnly)).toBe('https://192.168.1.21') expect(fleetNodeSubtitle(hostOnly)).toBe('https://192.0.2.21')
expect(fleetNodeDisplayName(idOnly)).toBe('feedface') expect(fleetNodeDisplayName(idOnly)).toBe('feedface')
expect(fleetNodeSubtitle(idOnly)).toBe('feedfacecafebeef') expect(fleetNodeSubtitle(idOnly)).toBe('feedfacecafebeef')
}) })
+1 -1
View File
@@ -117,7 +117,7 @@ export default defineConfig({
} }
}, },
server: { server: {
host: true, // listen on 0.0.0.0 so the dev UI is reachable over the LAN (e.g. http://192.168.1.116:8100) host: true, // listen on 0.0.0.0 so the dev UI is reachable over the LAN (e.g. http://192.0.2.12:8100)
port: 8100, port: 8100,
proxy: { proxy: {
'/rpc/v1': { '/rpc/v1': {
+1 -1
View File
@@ -13,7 +13,7 @@ Checks:
Usage: Usage:
scripts/app-catalog-image-smoke-test.py \ scripts/app-catalog-image-smoke-test.py \
--target archipelago@192.168.1.198 \ --target archipelago@192.0.2.11 \
--ssh-key /home/archipelago/.ssh/id_ed25519 --ssh-key /home/archipelago/.ssh/id_ed25519
""" """
+1 -1
View File
@@ -7,7 +7,7 @@
# the common "container is running but UI disappeared" failure mode. # the common "container is running but UI disappeared" failure mode.
# #
# Usage: # Usage:
# scripts/app-surface-smoke-test.sh --target archipelago@192.168.1.228 --ssh-key /path/key # scripts/app-surface-smoke-test.sh --target archipelago@192.0.2.10 --ssh-key /path/key
set -euo pipefail set -euo pipefail
+3 -3
View File
@@ -10,8 +10,8 @@
# installed nodes, but it will briefly interrupt Bitcoin/ElectrumX service. # installed nodes, but it will briefly interrupt Bitcoin/ElectrumX service.
# #
# Usage: # Usage:
# scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.168.1.228 # scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.0.2.10
# scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.168.1.116 --cycles 5 # scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.0.2.12 --cycles 5
set -euo pipefail set -euo pipefail
@@ -50,7 +50,7 @@ while [ "$#" -gt 0 ]; do
done done
if [ -z "$TARGET" ]; then if [ -z "$TARGET" ]; then
echo "--target is required, for example archipelago@192.168.1.228" >&2 echo "--target is required, for example archipelago@192.0.2.10" >&2
exit 2 exit 2
fi fi
+1 -1
View File
@@ -128,7 +128,7 @@ if $DRY_RUN; then
echo "" echo ""
echo "After this script, you would:" echo "After this script, you would:"
echo " - Push: git push && git push --tags" echo " - Push: git push && git push --tags"
echo " - Build ISOs on server: ssh archipelago@192.168.1.228" echo " - Build ISOs on server: ssh archipelago@192.0.2.10"
exit 0 exit 0
fi fi
+8 -3
View File
@@ -16,10 +16,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
SSH_HOST="${ARCHIPELAGO_SSH_HOST:-archipelago@192.168.1.228}" SSH_HOST="${ARCHIPELAGO_SSH_HOST:-}"
if [ -z "$SSH_HOST" ]; then
echo "ARCHIPELAGO_SSH_HOST must be set, e.g. archipelago@<node-host>" >&2
exit 2
fi
HOST_ONLY="${SSH_HOST#*@}"
SSH_OPTS="-o StrictHostKeyChecking=no -o ServerAliveInterval=15 -i $SSH_KEY" SSH_OPTS="-o StrictHostKeyChecking=no -o ServerAliveInterval=15 -i $SSH_KEY"
REMOTE_DIR="/home/archipelago/archy" REMOTE_DIR="/home/archipelago/archy"
RPC_URL="http://192.168.1.228/rpc/v1" RPC_URL="http://${HOST_ONLY}/rpc/v1"
COOKIE="" COOKIE=""
ONCE=false ONCE=false
[ "$1" = "--once" ] && ONCE=true [ "$1" = "--once" ] && ONCE=true
@@ -66,7 +71,7 @@ login() {
wait_for_health() { wait_for_health() {
local timeout=${1:-30} local timeout=${1:-30}
for i in $(seq 1 "$timeout"); do for i in $(seq 1 "$timeout"); do
if curl -sf "http://192.168.1.228/health" >/dev/null 2>&1; then if curl -sf "http://${HOST_ONLY}/health" >/dev/null 2>&1; then
return 0 return 0
fi fi
sleep 1 sleep 1
+1 -1
View File
@@ -82,7 +82,7 @@ case $choice in
echo " a) Preview GRUB background only (instant):" echo " a) Preview GRUB background only (instant):"
echo " python3 image-recipe/branding/generate-grub-background.py /tmp/grub-bg.png && open /tmp/grub-bg.png" echo " python3 image-recipe/branding/generate-grub-background.py /tmp/grub-bg.png && open /tmp/grub-bg.png"
echo "" echo ""
echo " b) Download an ISO from FileBrowser (http://192.168.1.228:8083)" echo " b) Download an ISO from FileBrowser (http://192.0.2.10:8083)"
echo " then drop it on your Desktop and re-run this option." echo " then drop it on your Desktop and re-run this option."
echo "" echo ""
echo " Files you can edit:" echo " Files you can edit:"
+2 -2
View File
@@ -576,12 +576,12 @@ screen_complete() {
# URL in orange # URL in orange
goto $row 1 goto $row 1
p " ${ORANGE}http://192.168.1.198${NC}" p " ${ORANGE}http://192.0.2.11${NC}"
row=$((row + 2)) row=$((row + 2))
# Credentials — white, NOT orange (user request) # Credentials — white, NOT orange (user request)
goto $row 1 goto $row 1
p " ${WHITE}SSH ssh archipelago@192.168.1.198${NC}" p " ${WHITE}SSH ssh archipelago@192.0.2.11${NC}"
row=$((row + 1)) row=$((row + 1))
goto $row 1 goto $row 1
p " ${WHITE}Password archipelago${NC}" p " ${WHITE}Password archipelago${NC}"
+5 -5
View File
@@ -28,23 +28,23 @@ will never catch. This harness is the gate.
Against the .228 test node: Against the .228 test node:
scripts/resilience/resilience.sh archipelago@192.168.1.228 scripts/resilience/resilience.sh archipelago@192.0.2.10
Or non-interactive (CI): Or non-interactive (CI):
RESILIENCE_SSH_PASS=… RESILIENCE_UI_PASS=… \ RESILIENCE_SSH_PASS=… RESILIENCE_UI_PASS=… \
scripts/resilience/resilience.sh archipelago@192.168.1.228 scripts/resilience/resilience.sh archipelago@192.0.2.10
Filters: Filters:
# Smoke test (3 apps, no reboot, ~15min) # Smoke test (3 apps, no reboot, ~15min)
scripts/resilience/resilience.sh archipelago@192.168.1.228 smoke scripts/resilience/resilience.sh archipelago@192.0.2.10 smoke
# Single app # Single app
scripts/resilience/resilience.sh archipelago@192.168.1.228 bitcoin-knots scripts/resilience/resilience.sh archipelago@192.0.2.10 bitcoin-knots
# Subset # Subset
scripts/resilience/resilience.sh archipelago@192.168.1.228 bitcoin-knots,lnd scripts/resilience/resilience.sh archipelago@192.0.2.10 bitcoin-knots,lnd
Without a filter, the harness sweeps **every** app in the catalog Without a filter, the harness sweeps **every** app in the catalog
(~24 apps × 7 per-app transitions + 2 batch transitions) and runs the (~24 apps × 7 per-app transitions + 2 batch transitions) and runs the
+1 -1
View File
@@ -3,7 +3,7 @@
# Sourced by resilience.sh — do not invoke directly. # Sourced by resilience.sh — do not invoke directly.
# Required env (set by resilience.sh before sourcing): # Required env (set by resilience.sh before sourcing):
# TARGET — ssh target, e.g. archipelago@192.168.1.228 # TARGET — ssh target, e.g. archipelago@192.0.2.10
# RPC_URL — http://<host>:5678/rpc/v1 # RPC_URL — http://<host>:5678/rpc/v1
# COOKIE_JAR — path for curl cookie store # COOKIE_JAR — path for curl cookie store
# SSH_PASS — sshpass password # SSH_PASS — sshpass password
+1 -1
View File
@@ -8,7 +8,7 @@
# remains in the expected state at every step. # remains in the expected state at every step.
# #
# Usage: # Usage:
# scripts/resilience/resilience.sh archipelago@192.168.1.228 [filter] # scripts/resilience/resilience.sh archipelago@192.0.2.10 [filter]
# #
# `filter` is a comma-separated list of app IDs (or "smoke" for the curated # `filter` is a comma-separated list of app IDs (or "smoke" for the curated
# fast subset). Default: every app in app-catalog/catalog.json. # fast subset). Default: every app in app-catalog/catalog.json.
+5 -1
View File
@@ -5,7 +5,11 @@
set -euo pipefail set -euo pipefail
HOST="${1:-192.168.1.198}" HOST="${1:-${ARCHY_HOST:-}}"
if [ -z "$HOST" ]; then
echo "usage: $0 <node-host> (or set ARCHY_HOST)" >&2
exit 2
fi
PASS=0 PASS=0
FAIL=0 FAIL=0
FAILURES="" FAILURES=""
+7 -3
View File
@@ -1,17 +1,21 @@
#!/bin/bash #!/bin/bash
# #
# Trust the Archipelago server's self-signed certificate on macOS. # Trust the Archipelago server's self-signed certificate on macOS.
# Run this to eliminate "Not secure" when accessing https://192.168.1.228 # Run this to eliminate "Not secure" when accessing https://<node-host>
# #
# Usage: ./scripts/trust-archipelago-cert.sh [host] # Usage: ./scripts/trust-archipelago-cert.sh [host]
# Default host: 192.168.1.228 # Host is required: pass it as $1 or set ARCHY_HOST
# #
# Requires: SSH access to archipelago@host (uses deploy-config.sh password) # Requires: SSH access to archipelago@host (uses deploy-config.sh password)
# #
set -e set -e
HOST="${1:-192.168.1.228}" HOST="${1:-${ARCHY_HOST:-}}"
if [ -z "$HOST" ]; then
echo "usage: $0 <node-host> (or set ARCHY_HOST)" >&2
exit 2
fi
CERT_FILE="/tmp/archipelago-${HOST}.crt" CERT_FILE="/tmp/archipelago-${HOST}.crt"
KEYCHAIN="${HOME}/Library/Keychains/login.keychain-db" KEYCHAIN="${HOME}/Library/Keychains/login.keychain-db"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+2 -2
View File
@@ -3,8 +3,8 @@
# NEVER commit real node passwords. # NEVER commit real node passwords.
# smoke.sh / repro-federation-sync.sh # smoke.sh / repro-federation-sync.sh
A_PW=changeme # node A (default URL http://192.168.1.116) A_PW=changeme # node A (default URL http://192.0.2.12)
B_PW=changeme # node B (default URL https://192.168.1.228) B_PW=changeme # node B (default URL https://192.0.2.10)
#C_URL=https://x.x.x.x # optional third node #C_URL=https://x.x.x.x # optional third node
#C_PW=changeme #C_PW=changeme
+2 -2
View File
@@ -11,8 +11,8 @@
# #
# Usage: # Usage:
# source tests/multinode/lib/multinode.bash # source tests/multinode/lib/multinode.bash
# node_register A https://192.168.1.228 "$A_PW" # node_register A https://192.0.2.10 "$A_PW"
# node_register B http://192.168.1.116 "$B_PW" # node_register B http://192.0.2.12 "$B_PW"
# node_login A; node_login B # node_login A; node_login B
# node_rpc A node.tor-address # node_rpc A node.tor-address
# node_result B federation.list-nodes # node_result B federation.list-nodes
+4 -4
View File
@@ -33,8 +33,8 @@
# #
# Usage: # Usage:
# tests/multinode/meshtastic.sh # tests/multinode/meshtastic.sh
# MA_URL=http://192.168.1.116 MB_URL=https://192.168.1.228 \ # MA_URL=http://192.0.2.12 MB_URL=https://192.0.2.10 \
# MC_URL=https://192.168.1.198 tests/multinode/meshtastic.sh # MC_URL=https://192.0.2.11 tests/multinode/meshtastic.sh
# #
# Requires: curl, jq. Exit code = number of failed assertions (0 = all green). # Requires: curl, jq. Exit code = number of failed assertions (0 = all green).
@@ -44,8 +44,8 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$HERE/lib/multinode.bash" source "$HERE/lib/multinode.bash"
# ── node registration ────────────────────────────────────────────────────── # ── node registration ──────────────────────────────────────────────────────
MA_URL="${MA_URL:-http://192.168.1.116}"; MA_PW="${MA_PW:?MA_PW required — export it or set tests/multinode/.env (see .env.example)}" MA_URL="${MA_URL:-http://192.0.2.12}"; MA_PW="${MA_PW:?MA_PW required — export it or set tests/multinode/.env (see .env.example)}"
MB_URL="${MB_URL:-https://192.168.1.228}"; MB_PW="${MB_PW:?MB_PW required — export it or set tests/multinode/.env (see .env.example)}" MB_URL="${MB_URL:-https://192.0.2.10}"; MB_PW="${MB_PW:?MB_PW required — export it or set tests/multinode/.env (see .env.example)}"
MC_URL="${MC_URL:-}"; MC_PW="${MC_PW:-}" MC_URL="${MC_URL:-}"; MC_PW="${MC_PW:-}"
PROP_WAIT="${PROP_WAIT:-45}" PROP_WAIT="${PROP_WAIT:-45}"
MB_NAME="${MB_NAME:-}" MB_NAME="${MB_NAME:-}"
+2 -2
View File
@@ -18,8 +18,8 @@ set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$HERE/lib/multinode.bash" source "$HERE/lib/multinode.bash"
A_URL="${A_URL:-http://192.168.1.116}"; A_PW="${A_PW:?A_PW required — export it or set tests/multinode/.env (see .env.example)}" A_URL="${A_URL:-http://192.0.2.12}"; A_PW="${A_PW:?A_PW required — export it or set tests/multinode/.env (see .env.example)}"
B_URL="${B_URL:-https://192.168.1.228}"; B_PW="${B_PW:?B_PW required — export it or set tests/multinode/.env (see .env.example)}" B_URL="${B_URL:-https://192.0.2.10}"; B_PW="${B_PW:?B_PW required — export it or set tests/multinode/.env (see .env.example)}"
bar() { printf '\n=== %s ===\n' "$*"; } bar() { printf '\n=== %s ===\n' "$*"; }
+3 -3
View File
@@ -21,13 +21,13 @@
# #
# Usage: # Usage:
# tests/multinode/smoke.sh # tests/multinode/smoke.sh
# A_URL=http://192.168.1.116 B_URL=https://192.168.1.228 tests/multinode/smoke.sh # A_URL=http://192.0.2.12 B_URL=https://192.0.2.10 tests/multinode/smoke.sh
set -uo pipefail set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$HERE/lib/multinode.bash" source "$HERE/lib/multinode.bash"
A_URL="${A_URL:-http://192.168.1.116}"; A_PW="${A_PW:?A_PW required — export it or set tests/multinode/.env (see .env.example)}" A_URL="${A_URL:-http://192.0.2.12}"; A_PW="${A_PW:?A_PW required — export it or set tests/multinode/.env (see .env.example)}"
B_URL="${B_URL:-https://192.168.1.228}"; B_PW="${B_PW:?B_PW required — export it or set tests/multinode/.env (see .env.example)}" B_URL="${B_URL:-https://192.0.2.10}"; B_PW="${B_PW:?B_PW required — export it or set tests/multinode/.env (see .env.example)}"
C_URL="${C_URL:-}"; C_PW="${C_PW:-}" C_URL="${C_URL:-}"; C_PW="${C_PW:-}"
# ── tiny assertion framework ────────────────────────────────────────────── # ── tiny assertion framework ──────────────────────────────────────────────
+1 -1
View File
@@ -5,7 +5,7 @@
# Bug B5: /lnd-connect-info duplicated ACAO on some nodes; /proxy/lnd/v1/* 401 # Bug B5: /lnd-connect-info duplicated ACAO on some nodes; /proxy/lnd/v1/* 401
# carries no ACAO fleet-wide. Browser blocks both. # carries no ACAO fleet-wide. Browser blocks both.
# #
# Usage: ./lnd-cors-test.sh <node-host> (e.g. 192.168.1.116 or 100.102.169.103) # Usage: ./lnd-cors-test.sh <node-host> (e.g. 192.0.2.12 or 100.64.0.7)
# Exit 0 = all assertions pass. # Exit 0 = all assertions pass.
set -uo pipefail set -uo pipefail