security: remove all infrastructure and internal process material from the repo
Demo images / Build & push demo images (push) Failing after 2m13s
Demo images / Build & push demo images (push) Failing after 2m13s
The repo is source code and guidelines only. Nothing about how Archipelago's own fleet is run, or how the team works, stays in it. Untracked (kept on disk, gitignored) — 250 files: - .planning/ (199) and loop/ — internal development process - fleet operations tooling that targets specific nodes: deploy-to-target, deploy-tailscale, deploy-config-defaults, setup-target-dev, setup-aiui-server, setup-https-dev, debug-frontend, node-profile, fleet-fips-pair/unpair, image-recipe/sync-from-live.sh - image-recipe/INTEGRATION-GUIDE.md and docs/multinode-testing-plan.md, both of which are live-server workflow and fleet node inventories - the Phase 10 on-node verification and evidence records, which cite .planning/ as their evidence base KEY-05-ENTROPY-ENFORCEMENT.md was initially moved out with the other Phase 10 docs and then put back: it is cited as normative rationale from ten places in the codebase, including core/clippy.toml, which bans rand::thread_rng and points at it for the reason. That makes it a guideline, not an internal record. Node names removed from source (48 occurrences across comments, manifests and test fixtures): archi-dev-box, archy-x250*, shorty-s, framework-pt, zaza-optiplex, archi-thinkpad. Comments keep the engineering context and the date, which is what carried the meaning; the machine name did not. Three of those were live test values rather than comments and were replaced with valid stand-ins, not prose: two mDNS hostnames and a mesh peer name. An earlier pass substituted "a test node" into a hostname assertion, producing an invalid hostname; caught and fixed as test-node.local. Wipe mechanism: .local-only/manifest.txt inventories every local-only path and .local-only/wipe.sh deletes them on one confirmation, refusing to touch anything git still tracks. Both are themselves untracked, so the public repo does not carry a map of internal filenames. Verified: cargo check -p archipelago --all-features clean; archipelago-container 75/75 tests pass; appOrigin vitest 7/7; audit-secrets 5/5; every relative link in tracked markdown resolves (0 broken). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cc00884b98
commit
6ba0599639
@@ -428,7 +428,7 @@ print(' '.join(['\"' + a + '\"' if ' ' in a else a for a in args[2:]]))
|
||||
# but silently loses outbound. Bitcoin IBD stalls at 0 peers; package pulls
|
||||
# fail. The repair must rebuild the netns from scratch: merely cycling the
|
||||
# containers reuses the existing (broken) netns because its holders
|
||||
# (aardvark-dns, podman's pause process) survive — observed on shorty-s
|
||||
# (aardvark-dns, podman's pause process) survive — observed on a test node
|
||||
# 2026-07-10, where the old stop/start-only cycle bounced all 35 containers
|
||||
# every timer run for ~an hour without ever restoring egress. So: stop the
|
||||
# containers, kill the netns holders, `podman system migrate`, clear the
|
||||
@@ -610,7 +610,7 @@ fix_npm_public_hosts() {
|
||||
# A BTCPay store whose LND node has only private (unannounced) channels
|
||||
# produces BOLT11 invoices that external wallets cannot route to unless the
|
||||
# store's lightningPrivateRouteHints flag is on — payers see "no way to pay
|
||||
# this invoice" (observed on shorty-s 2026-07-10 with a Blink payer). Route
|
||||
# this invoice" (observed on a test node 2026-07-10 with a Blink payer). Route
|
||||
# hints are a no-op with public channels and essential with private ones, so
|
||||
# the doctor enforces the flag on every store. BTCPay reads store blobs from
|
||||
# Postgres per request; no restart needed.
|
||||
@@ -636,7 +636,7 @@ fix_btcpay_route_hints() {
|
||||
# Podman resolves `--init` (and any Portainer/compose deploy with
|
||||
# "init: true") through catatonit; Debian's podman package only
|
||||
# Recommends it, so a node installed or upgraded without it fails those
|
||||
# deploys with a missing-init error (observed on shorty-s 2026-07-10
|
||||
# deploys with a missing-init error (observed on a test node 2026-07-10
|
||||
# deploying sites via Portainer). install-podman.sh covers fresh ISO
|
||||
# installs; this heals nodes that predate it.
|
||||
fix_missing_catatonit() {
|
||||
|
||||
@@ -586,7 +586,7 @@ load_spec_archy-lnd-ui() {
|
||||
# created by first-boot-containers.sh, which is host-networked and never
|
||||
# consults this file; the spec is only read when self-update.sh rebuilds a
|
||||
# UI image, and that only fires when a file under docker/lnd-ui/ changes.
|
||||
# Verified on archi-dev-box: recreating from the old spec left :18083
|
||||
# Verified on a test node: recreating from the old spec left :18083
|
||||
# refusing connections.
|
||||
SPEC_NETWORK="host"
|
||||
SPEC_MEMORY="$(mem_limit archy-lnd-ui)"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
# Check what's actually in the deployed frontend
|
||||
|
||||
TARGET_HOST="${ARCHIPELAGO_TARGET:-archipelago@192.168.1.228}"
|
||||
|
||||
echo "Checking deployed frontend content..."
|
||||
echo ""
|
||||
|
||||
echo "1. Search for 'bundledApps' variable in JS:"
|
||||
ssh "$TARGET_HOST" "grep -o 'bundledApps' /opt/archipelago/web-ui/assets/*.js | wc -l"
|
||||
|
||||
echo ""
|
||||
echo "2. Search for 'Bitcoin Knots' string:"
|
||||
ssh "$TARGET_HOST" "grep -o 'Bitcoin Knots' /opt/archipelago/web-ui/assets/*.js | head -1"
|
||||
|
||||
echo ""
|
||||
echo "3. Search for the v-for loop pattern:"
|
||||
ssh "$TARGET_HOST" "grep -o 'v-for.*bundled' /opt/archipelago/web-ui/assets/*.js | head -1"
|
||||
|
||||
echo ""
|
||||
echo "4. List all JS assets (to see if they updated):"
|
||||
ssh "$TARGET_HOST" "ls -lh /opt/archipelago/web-ui/assets/*.js | head -10"
|
||||
|
||||
echo ""
|
||||
echo "5. Check index.html timestamp:"
|
||||
ssh "$TARGET_HOST" "stat /opt/archipelago/web-ui/index.html | grep Modify"
|
||||
|
||||
echo ""
|
||||
echo "6. Try accessing the API from target:"
|
||||
ssh "$TARGET_HOST" 'curl -s http://localhost:80/ | head -20'
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Default deployment targets — override in deploy-config.sh (gitignored)
|
||||
DEFAULT_PRIMARY="192.168.1.228"
|
||||
DEFAULT_SECONDARY="192.168.1.198"
|
||||
TAILSCALE_ARCH1="100.82.97.63"
|
||||
TAILSCALE_ARCH2="100.122.84.60"
|
||||
TAILSCALE_ARCH3="100.124.105.113"
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
# Creates core containers so My Apps works out of the box after ISO install
|
||||
# Runs after archipelago-load-images.service and archipelago-setup-tor.service
|
||||
#
|
||||
# Based on scripts/deploy-to-target.sh (--live) container logic - do not diverge.
|
||||
# Container logic mirrors the deploy path - do not diverge.
|
||||
# No set -e: each section continues even if one fails (idempotent, best-effort).
|
||||
#
|
||||
# Image versions: sourced from /opt/archipelago/image-versions.sh (single source of truth).
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/bin/bash
|
||||
# LAN fast-path pairing for our 4 dev fleet nodes.
|
||||
#
|
||||
# ── Is this needed for every archipelago install? No. ────────────────
|
||||
# For nodes deployed anywhere in the world, FIPS-to-FIPS routing by
|
||||
# npub works via the anchor peer network (fips.v0l.io ships by default
|
||||
# in /etc/fips/fips.yaml on every install — that anchor bootstraps DHT
|
||||
# routing for any npub the node has ever heard about). The peer's
|
||||
# fips_npub is advertised in our federation invite codes (since v1.4),
|
||||
# so accepting an invite is enough for `dial::peer_base_url(npub)` to
|
||||
# reach the peer through the anchor mesh.
|
||||
#
|
||||
# ── Why this script exists ───────────────────────────────────────────
|
||||
# Our 4 fleet nodes are all on 192.168.1.0/24. Hopping through the
|
||||
# fips.v0l.io anchor for intra-LAN traffic is wasteful when the peers
|
||||
# are on the same wire. This script writes per-node fips.yaml with:
|
||||
# 1. The public anchor (fips.v0l.io) so internet peers still route.
|
||||
# 2. The other 3 fleet nodes as static LAN peers (UDP 2121 / TCP
|
||||
# 8443) so LAN traffic stays on LAN.
|
||||
# 3. `persistent: true` so the npub is stable across restarts —
|
||||
# without this the daemon rolls a new keypair on every restart
|
||||
# and any federation invite we advertised goes stale.
|
||||
#
|
||||
# Idempotent: re-running picks up any newly-added or removed nodes.
|
||||
#
|
||||
# For a production install on an unknown LAN, this script isn't the
|
||||
# mechanism — the ISO install writes the anchor-only fips.yaml and
|
||||
# identity comes from the archipelago seed; peer discovery is purely
|
||||
# through the DHT + federation invites.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/fleet-fips-pair.sh # apply to all nodes
|
||||
# scripts/fleet-fips-pair.sh --verify # just print the peer state
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Fleet roster: "<ip-last-octet> <nic-name> <fips-npub>"
|
||||
NODES=(
|
||||
"116 enp0s25 npub1mxavs6scfgl056k6lm4mk73ddnrhjewg78zlyzfn2lmr0rfyrs5qhcr03g"
|
||||
"198 enp2s0 npub13cy4lml94cj4rdu8runrr945z2muszuvr5tql8mr9m063d7xzpqqu3k8se"
|
||||
"228 enp2s0 npub1a0xxcqce2tsv8ulwastep23jtf3h4wvvry8r8nklnl36jtrdnefqh5qn6h"
|
||||
"253 enx9cbf0d0129f9 npub1dl0m0yfzfw6467c3z6q63s7ggzd77yg97j90ptfrheprxeypt3msj0mq4g"
|
||||
)
|
||||
|
||||
LAN_PREFIX="192.168.1"
|
||||
UDP_PORT=2121
|
||||
TCP_PORT=8443
|
||||
|
||||
if [ "${1:-}" = "--verify" ]; then
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r node _nic _npub <<< "$row"
|
||||
echo "=== .$node ==="
|
||||
ssh_cmd "$LAN_PREFIX.$node" "sudo fipsctl show peers 2>/dev/null | python3 -c 'import sys,json; d=json.load(sys.stdin); print(f\"{len(d[\"peers\"])} authenticated peers\"); [print(\" npub=\", p.get(\"npub\",\"?\"), \"alias=\", p.get(\"alias\",\"?\")) for p in d[\"peers\"]]' || echo ' fipsctl show peers failed'"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMP_ROOT=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||
|
||||
generate_yaml() {
|
||||
# $1 = self node octet, $2 = self nic
|
||||
local self_node="$1"
|
||||
local self_nic="$2"
|
||||
local out="$TMP_ROOT/fips.yaml.$self_node"
|
||||
|
||||
cat > "$out" <<YAML
|
||||
# FIPS Node Configuration — managed by scripts/fleet-fips-pair.sh
|
||||
# DO NOT hand-edit: re-run the script to regenerate.
|
||||
|
||||
node:
|
||||
identity:
|
||||
# Persistent identity so the npub stays stable across restarts.
|
||||
# Without this, every restart rolls a new keypair and federation
|
||||
# peer lists go stale.
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 5354
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:$UDP_PORT"
|
||||
tcp:
|
||||
bind_addr: "0.0.0.0:$TCP_PORT"
|
||||
|
||||
ethernet:
|
||||
interface: "$self_nic"
|
||||
discovery: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
|
||||
peers:
|
||||
# Public anchor — bootstraps DHT routing for any npub heard via
|
||||
# federation invites. Every archipelago install ships this peer.
|
||||
- npub: "npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
|
||||
alias: "fips.v0l.io"
|
||||
addresses:
|
||||
- transport: tcp
|
||||
addr: "fips.v0l.io:8443"
|
||||
- transport: udp
|
||||
addr: "fips.v0l.io:2121"
|
||||
connect_policy: auto_connect
|
||||
|
||||
# Fleet LAN fast-path — other archipelago nodes on this subnet.
|
||||
YAML
|
||||
|
||||
for other_row in "${NODES[@]}"; do
|
||||
read -r o_node _o_nic o_npub <<< "$other_row"
|
||||
[ "$o_node" = "$self_node" ] && continue
|
||||
cat >> "$out" <<YAML
|
||||
- npub: "$o_npub"
|
||||
alias: "archi-$o_node"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "$LAN_PREFIX.$o_node:$UDP_PORT"
|
||||
- transport: tcp
|
||||
addr: "$LAN_PREFIX.$o_node:$TCP_PORT"
|
||||
connect_policy: auto_connect
|
||||
YAML
|
||||
done
|
||||
echo "$out"
|
||||
}
|
||||
|
||||
deploy_to() {
|
||||
local node="$1"
|
||||
local nic="$2"
|
||||
local ip="$LAN_PREFIX.$node"
|
||||
local yaml
|
||||
yaml=$(generate_yaml "$node" "$nic")
|
||||
|
||||
log_info "[.${node}] uploading fips.yaml"
|
||||
scp_cmd "$yaml" "archipelago@${ip}:/tmp/fips.yaml.new"
|
||||
|
||||
log_info "[.${node}] installing + restarting fips.service"
|
||||
ssh_cmd "$ip" '
|
||||
set -e
|
||||
sudo install -o root -g root -m 0600 /tmp/fips.yaml.new /etc/fips/fips.yaml
|
||||
rm -f /tmp/fips.yaml.new
|
||||
sudo systemctl restart fips.service
|
||||
# Give the daemon a beat to come up before we ask about peers
|
||||
for i in $(seq 1 10); do
|
||||
if sudo systemctl is-active fips.service >/dev/null 2>&1; then break; fi
|
||||
sleep 0.5
|
||||
done
|
||||
sudo systemctl is-active fips.service
|
||||
'
|
||||
}
|
||||
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r node nic _npub <<< "$row"
|
||||
deploy_to "$node" "$nic"
|
||||
done
|
||||
|
||||
echo
|
||||
log_info "Waiting 10s for peer handshakes to settle…"
|
||||
sleep 10
|
||||
|
||||
echo
|
||||
log_info "Post-pair peer state:"
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r node _nic _npub <<< "$row"
|
||||
count=$(ssh_cmd "$LAN_PREFIX.$node" "sudo fipsctl show peers 2>/dev/null | grep -c '\"npub\"' || echo 0")
|
||||
log_info " .$node: $count authenticated peers"
|
||||
done
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Strip the LAN fast-path peers from all 4 fleet nodes' fips.yaml,
|
||||
# leaving only the public anchor (fips.v0l.io). Restart fips.service
|
||||
# on each node.
|
||||
#
|
||||
# Purpose: verify that the general-case deployment (nodes anywhere in
|
||||
# the world, no LAN between them) actually works — i.e. that two
|
||||
# paired archipelago peers can reach each other purely through the
|
||||
# FIPS DHT bootstrapped from the anchor.
|
||||
#
|
||||
# After running this, test with:
|
||||
# scripts/fleet-fips-pair.sh --verify (peer state per node)
|
||||
# for ip in 116 198 228 253; do
|
||||
# ssh archipelago@192.168.1.$ip "dig @127.0.0.1 -p 5354 +short \
|
||||
# <other-node-npub>.fips AAAA"
|
||||
# done
|
||||
#
|
||||
# To restore the LAN fast-path: re-run scripts/fleet-fips-pair.sh.
|
||||
#
|
||||
# Usage: scripts/fleet-fips-unpair.sh
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Roster — only need NIC names to preserve them in the yaml.
|
||||
NODES=(
|
||||
"116 enp0s25"
|
||||
"198 enp2s0"
|
||||
"228 enp2s0"
|
||||
"253 enx9cbf0d0129f9"
|
||||
)
|
||||
|
||||
TMP_ROOT=$(mktemp -d)
|
||||
trap 'rm -rf "$TMP_ROOT"' EXIT
|
||||
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r node nic <<< "$row"
|
||||
out="$TMP_ROOT/fips.yaml.$node"
|
||||
cat > "$out" <<YAML
|
||||
# FIPS Node Configuration — anchor-only (managed by fleet-fips-unpair.sh)
|
||||
# This is the shape a general archipelago install ships with: fleet
|
||||
# nodes are NOT pre-paired; discovery happens via the anchor DHT.
|
||||
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
port: 5354
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
tcp:
|
||||
bind_addr: "0.0.0.0:8443"
|
||||
|
||||
ethernet:
|
||||
interface: "$nic"
|
||||
discovery: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
|
||||
peers:
|
||||
- npub: "npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
|
||||
alias: "fips.v0l.io"
|
||||
addresses:
|
||||
- transport: tcp
|
||||
addr: "fips.v0l.io:8443"
|
||||
- transport: udp
|
||||
addr: "fips.v0l.io:2121"
|
||||
connect_policy: auto_connect
|
||||
YAML
|
||||
|
||||
ip="192.168.1.$node"
|
||||
log_info "[.${node}] uploading anchor-only fips.yaml"
|
||||
scp_cmd "$out" "archipelago@${ip}:/tmp/fips.yaml.new"
|
||||
log_info "[.${node}] installing + restarting fips.service"
|
||||
ssh_cmd "$ip" '
|
||||
set -e
|
||||
sudo install -o root -g root -m 0600 /tmp/fips.yaml.new /etc/fips/fips.yaml
|
||||
rm -f /tmp/fips.yaml.new
|
||||
sudo systemctl restart fips.service
|
||||
for i in $(seq 1 10); do
|
||||
if sudo systemctl is-active fips.service >/dev/null 2>&1; then break; fi
|
||||
sleep 0.5
|
||||
done
|
||||
sudo systemctl is-active fips.service
|
||||
'
|
||||
done
|
||||
|
||||
echo
|
||||
log_info "Waiting 20s for anchor handshake + DHT propagation…"
|
||||
sleep 20
|
||||
|
||||
echo
|
||||
log_info "Post-unpair state (should show only fips.v0l.io as an authenticated peer):"
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r node _nic <<< "$row"
|
||||
ip="192.168.1.$node"
|
||||
count=$(ssh_cmd "$ip" "sudo fipsctl show peers 2>/dev/null | grep -c '\"npub\"' || echo 0")
|
||||
log_info " .$node: $count authenticated peers"
|
||||
done
|
||||
|
||||
echo
|
||||
log_info "DHT resolution test — each node resolves the other 3 by npub:"
|
||||
declare -A NPUBS=(
|
||||
[116]="npub1mxavs6scfgl056k6lm4mk73ddnrhjewg78zlyzfn2lmr0rfyrs5qhcr03g"
|
||||
[198]="npub13cy4lml94cj4rdu8runrr945z2muszuvr5tql8mr9m063d7xzpqqu3k8se"
|
||||
[228]="npub1a0xxcqce2tsv8ulwastep23jtf3h4wvvry8r8nklnl36jtrdnefqh5qn6h"
|
||||
[253]="npub1dl0m0yfzfw6467c3z6q63s7ggzd77yg97j90ptfrheprxeypt3msj0mq4g"
|
||||
)
|
||||
for row in "${NODES[@]}"; do
|
||||
read -r self_node _ <<< "$row"
|
||||
ip="192.168.1.$self_node"
|
||||
echo ".${self_node}:"
|
||||
for other in 116 198 228 253; do
|
||||
[ "$other" = "$self_node" ] && continue
|
||||
r=$(ssh_cmd "$ip" "dig @127.0.0.1 -p 5354 +short +time=3 +tries=1 ${NPUBS[$other]}.fips AAAA" 2>&1)
|
||||
if [ -z "$r" ]; then
|
||||
echo " .${other} → unresolved (DHT route not found)"
|
||||
else
|
||||
echo " .${other} → $r"
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -1,252 +0,0 @@
|
||||
#!/bin/bash
|
||||
# node-profile.sh — CPU/memory/container profiling across all Archipelago nodes
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/node-profile.sh # All reachable nodes
|
||||
# ./scripts/node-profile.sh 192.168.1.228 # Single node
|
||||
# ./scripts/node-profile.sh --watch # Repeat every 30s
|
||||
#
|
||||
# Requires: SSH key at ~/.ssh/archipelago-deploy (or ARCHIPELAGO_SSH_KEY)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
source "$SCRIPT_DIR/deploy-config-defaults.sh"
|
||||
[ -f "$SCRIPT_DIR/deploy-config.sh" ] && source "$SCRIPT_DIR/deploy-config.sh"
|
||||
|
||||
ALL_NODES=(
|
||||
"$DEFAULT_PRIMARY"
|
||||
"$DEFAULT_SECONDARY"
|
||||
"$TAILSCALE_ARCH1"
|
||||
"$TAILSCALE_ARCH2"
|
||||
"$TAILSCALE_ARCH3"
|
||||
)
|
||||
|
||||
NODE_LABELS=(
|
||||
"primary (.228)"
|
||||
"secondary (.198)"
|
||||
"tailscale-1"
|
||||
"tailscale-2"
|
||||
"tailscale-3"
|
||||
)
|
||||
|
||||
WATCH_MODE=false
|
||||
WATCH_INTERVAL=30
|
||||
TARGET_NODES=()
|
||||
|
||||
# ── Parse args ─────────────────────────────────────────────────────────
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--watch)
|
||||
WATCH_MODE=true
|
||||
shift
|
||||
;;
|
||||
--interval)
|
||||
WATCH_INTERVAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
TARGET_NODES+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If specific nodes given, use those; otherwise use all
|
||||
if [ ${#TARGET_NODES[@]} -eq 0 ]; then
|
||||
TARGET_NODES=("${ALL_NODES[@]}")
|
||||
fi
|
||||
|
||||
# ── Remote profiling command ───────────────────────────────────────────
|
||||
|
||||
PROFILE_CMD='
|
||||
hostname_val=$(hostname 2>/dev/null || echo "unknown")
|
||||
uptime_val=$(uptime -p 2>/dev/null || uptime | sed "s/.*up/up/;s/,.*//")
|
||||
|
||||
# CPU info
|
||||
cpu_cores=$(nproc 2>/dev/null || echo "?")
|
||||
load_avg=$(cat /proc/loadavg 2>/dev/null | awk "{print \$1, \$2, \$3}")
|
||||
|
||||
# Memory
|
||||
mem_info=$(free -h 2>/dev/null | awk "/^Mem:/{printf \"%s / %s (%s free)\", \$3, \$2, \$4}")
|
||||
swap_info=$(free -h 2>/dev/null | awk "/^Swap:/{if(\$2 != \"0B\" && \$2 != \"0\") printf \"%s / %s\", \$3, \$2; else print \"none\"}")
|
||||
|
||||
# Disk
|
||||
disk_info=$(df -h / 2>/dev/null | awk "NR==2{printf \"%s / %s (%s)\", \$3, \$2, \$5}")
|
||||
|
||||
# CPU temperature (if available)
|
||||
temp="n/a"
|
||||
if [ -f /sys/class/thermal/thermal_zone0/temp ]; then
|
||||
raw=$(cat /sys/class/thermal/thermal_zone0/temp)
|
||||
temp="$((raw / 1000))°C"
|
||||
fi
|
||||
|
||||
echo "HEADER|${hostname_val}|${uptime_val}|${cpu_cores} cores|load ${load_avg}|${temp}"
|
||||
echo "MEM|${mem_info}"
|
||||
echo "SWAP|${swap_info}"
|
||||
echo "DISK|${disk_info}"
|
||||
|
||||
# Top 10 processes by CPU
|
||||
echo "PROCS_START"
|
||||
ps aux --sort=-%cpu 2>/dev/null | head -11 | awk "NR>1{printf \"%-6s %-5s %-5s %s\n\", \$2, \$3, \$4, \$11}" 2>/dev/null
|
||||
echo "PROCS_END"
|
||||
|
||||
# Container status
|
||||
echo "CONTAINERS_START"
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
podman ps -a --format "{{.Names}}|{{.Status}}|{{.Size}}" 2>/dev/null || \
|
||||
podman ps -a --format "{{.Names}}|{{.Status}}" 2>/dev/null || \
|
||||
echo "podman error"
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
docker ps -a --format "{{.Names}}|{{.Status}}" 2>/dev/null || echo "docker error"
|
||||
else
|
||||
echo "no container runtime"
|
||||
fi
|
||||
echo "CONTAINERS_END"
|
||||
'
|
||||
|
||||
# ── Formatting ─────────────────────────────────────────────────────────
|
||||
|
||||
BOLD="\033[1m"
|
||||
DIM="\033[2m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[0;33m"
|
||||
RED="\033[0;31m"
|
||||
CYAN="\033[0;36m"
|
||||
RESET="\033[0m"
|
||||
|
||||
SEP="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
print_node_report() {
|
||||
local ip="$1"
|
||||
local label="$2"
|
||||
local output="$3"
|
||||
|
||||
echo -e "\n${BOLD}${CYAN}${SEP}${RESET}"
|
||||
echo -e "${BOLD}${CYAN} ${label} ${DIM}(${ip})${RESET}"
|
||||
echo -e "${BOLD}${CYAN}${SEP}${RESET}"
|
||||
|
||||
# Parse HEADER line
|
||||
local header
|
||||
header=$(echo "$output" | grep "^HEADER|" | head -1)
|
||||
if [ -n "$header" ]; then
|
||||
IFS='|' read -r _ hostname uptime cores load temp <<< "$header"
|
||||
echo -e " ${BOLD}Host:${RESET} ${hostname} ${DIM}${uptime}${RESET}"
|
||||
echo -e " ${BOLD}CPU:${RESET} ${cores} ${load} ${temp}"
|
||||
fi
|
||||
|
||||
# Memory
|
||||
local mem
|
||||
mem=$(echo "$output" | grep "^MEM|" | cut -d'|' -f2)
|
||||
[ -n "$mem" ] && echo -e " ${BOLD}Mem:${RESET} ${mem}"
|
||||
|
||||
local swap
|
||||
swap=$(echo "$output" | grep "^SWAP|" | cut -d'|' -f2)
|
||||
[ -n "$swap" ] && echo -e " ${BOLD}Swap:${RESET} ${swap}"
|
||||
|
||||
local disk
|
||||
disk=$(echo "$output" | grep "^DISK|" | cut -d'|' -f2)
|
||||
[ -n "$disk" ] && echo -e " ${BOLD}Disk:${RESET} ${disk}"
|
||||
|
||||
# Top processes
|
||||
echo ""
|
||||
echo -e " ${BOLD}Top processes by CPU:${RESET}"
|
||||
echo -e " ${DIM}PID CPU% MEM% Command${RESET}"
|
||||
local procs
|
||||
procs=$(echo "$output" | sed -n '/^PROCS_START$/,/^PROCS_END$/p' | grep -v "^PROCS_")
|
||||
if [ -n "$procs" ]; then
|
||||
while IFS= read -r line; do
|
||||
local cpu_pct
|
||||
cpu_pct=$(echo "$line" | awk '{print $2}' | tr -d '.')
|
||||
if [ "${cpu_pct:-0}" -gt 500 ] 2>/dev/null; then
|
||||
echo -e " ${RED}${line}${RESET}"
|
||||
elif [ "${cpu_pct:-0}" -gt 100 ] 2>/dev/null; then
|
||||
echo -e " ${YELLOW}${line}${RESET}"
|
||||
else
|
||||
echo -e " ${line}"
|
||||
fi
|
||||
done <<< "$procs"
|
||||
else
|
||||
echo -e " ${DIM}(no process data)${RESET}"
|
||||
fi
|
||||
|
||||
# Containers
|
||||
echo ""
|
||||
echo -e " ${BOLD}Containers:${RESET}"
|
||||
local containers
|
||||
containers=$(echo "$output" | sed -n '/^CONTAINERS_START$/,/^CONTAINERS_END$/p' | grep -v "^CONTAINERS_")
|
||||
if [ -n "$containers" ] && [ "$containers" != "no container runtime" ] && [ "$containers" != "podman error" ]; then
|
||||
while IFS='|' read -r name status size; do
|
||||
local icon
|
||||
if echo "$status" | grep -qi "up"; then
|
||||
icon="${GREEN}●${RESET}"
|
||||
else
|
||||
icon="${RED}○${RESET}"
|
||||
fi
|
||||
echo -e " ${icon} ${BOLD}${name}${RESET} ${DIM}${status}${RESET}"
|
||||
done <<< "$containers"
|
||||
else
|
||||
echo -e " ${DIM}${containers:-none}${RESET}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Main profiling loop ───────────────────────────────────────────────
|
||||
|
||||
profile_all() {
|
||||
echo -e "\n${BOLD}Archipelago Node Profile${RESET} ${DIM}$(date '+%Y-%m-%d %H:%M:%S')${RESET}"
|
||||
|
||||
local tmpdir
|
||||
tmpdir=$(mktemp -d)
|
||||
|
||||
# Probe all nodes in parallel
|
||||
local pids=()
|
||||
for i in "${!TARGET_NODES[@]}"; do
|
||||
local ip="${TARGET_NODES[$i]}"
|
||||
local label="${NODE_LABELS[$i]:-$ip}"
|
||||
(
|
||||
result=$(ssh_cmd "$ip" "$PROFILE_CMD" 2>/dev/null) && \
|
||||
echo "$result" > "$tmpdir/$i.out" || \
|
||||
echo "UNREACHABLE" > "$tmpdir/$i.out"
|
||||
) &
|
||||
pids+=($!)
|
||||
done
|
||||
|
||||
# Wait for all probes
|
||||
for pid in "${pids[@]}"; do
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Print reports
|
||||
local reachable=0 unreachable=0
|
||||
for i in "${!TARGET_NODES[@]}"; do
|
||||
local ip="${TARGET_NODES[$i]}"
|
||||
local label="${NODE_LABELS[$i]:-$ip}"
|
||||
local outfile="$tmpdir/$i.out"
|
||||
|
||||
if [ -f "$outfile" ] && [ "$(cat "$outfile")" != "UNREACHABLE" ]; then
|
||||
print_node_report "$ip" "$label" "$(cat "$outfile")"
|
||||
reachable=$((reachable + 1))
|
||||
else
|
||||
echo -e "\n${DIM}${SEP}${RESET}"
|
||||
echo -e "${RED} ${label} (${ip}) — unreachable${RESET}"
|
||||
echo -e "${DIM}${SEP}${RESET}"
|
||||
unreachable=$((unreachable + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\n${DIM}${reachable} reachable, ${unreachable} unreachable${RESET}\n"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
|
||||
if $WATCH_MODE; then
|
||||
while true; do
|
||||
clear
|
||||
profile_all
|
||||
echo -e "${DIM}Refreshing every ${WATCH_INTERVAL}s — Ctrl+C to stop${RESET}"
|
||||
sleep "$WATCH_INTERVAL"
|
||||
done
|
||||
else
|
||||
profile_all
|
||||
fi
|
||||
@@ -217,7 +217,7 @@ ok "Backend installed"
|
||||
# Non-fatal: archipelago falls back to its dev venv path if the packaged
|
||||
# binaries aren't present, so a missing/failed build here degrades mesh
|
||||
# Reticulum support rather than breaking the update. This mirrors
|
||||
# deploy-to-target.sh's existing manual-deploy step, which until now was the
|
||||
# the existing manual-deploy step, which until now was the
|
||||
# only path that ever installed these — a node that only ever received OTA
|
||||
# self-updates had neither binary.
|
||||
if [ -f "$REPO_DIR/reticulum-daemon/build.sh" ]; then
|
||||
@@ -329,7 +329,7 @@ UI_REBUILD_LIST=""
|
||||
# /opt/archipelago/docker/<ui>, and nothing was ever updating that directory.
|
||||
# So source edits to those two trees reached nodes through no path at all:
|
||||
# their nginx kept listening on 0.0.0.0 and served the Guardian and FIPS
|
||||
# screens unauthenticated on every interface (found by scanning archi-dev-box
|
||||
# screens unauthenticated on every interface (found by scanning a test node
|
||||
# from outside, 2026-08-05 — the in-node audit could not see them).
|
||||
for ui in bitcoin-ui lnd-ui electrs-ui fips-ui fedimint-ui; do
|
||||
src="$REPO_DIR/docker/$ui"
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Setup AIUI + Claude API proxy + FileBrowser on any Archipelago server
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/setup-aiui-server.sh <host>
|
||||
# ./scripts/setup-aiui-server.sh archipelago@192.168.1.198
|
||||
# ./scripts/setup-aiui-server.sh archipelago@192.168.1.228
|
||||
#
|
||||
# What it does:
|
||||
# 1. Deploys AIUI files (from local build)
|
||||
# 2. Configures nginx Claude API proxy (direct to Anthropic with API key)
|
||||
# 3. Fixes FileBrowser container (removes read-only root if needed)
|
||||
# 4. Reloads nginx
|
||||
#
|
||||
# Prerequisites:
|
||||
# - AIUI must be built locally first: cd AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build
|
||||
# - SSH key access to target server
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY"
|
||||
|
||||
# Anthropic API key used by the AIUI Claude chat proxy. Keep this in the
|
||||
# caller's environment or scripts/deploy-config.sh; never commit live keys.
|
||||
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}"
|
||||
|
||||
TARGET_HOST="$1"
|
||||
if [ -z "$TARGET_HOST" ]; then
|
||||
echo "Usage: $0 <user@host>"
|
||||
echo " e.g. $0 archipelago@192.168.1.198"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$ANTHROPIC_API_KEY" ]; then
|
||||
echo "ERROR: ANTHROPIC_API_KEY must be set in the environment."
|
||||
echo "Example: ANTHROPIC_API_KEY=<key> $0 $TARGET_HOST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist"
|
||||
if [ ! -f "$AIUI_DIST/index.html" ]; then
|
||||
echo "ERROR: AIUI build not found at $AIUI_DIST"
|
||||
echo "Build it first: cd ../AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
timestamp() { echo "[$(date +%H:%M:%S)]"; }
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Archipelago AIUI + Claude API Setup ║"
|
||||
echo "║ Target: $TARGET_HOST"
|
||||
echo "╚════════════════════════════════════════════════════════════╝"
|
||||
|
||||
# --- Step 1: Deploy AIUI files ---
|
||||
echo ""
|
||||
echo "$(timestamp) 📦 Deploying AIUI files..."
|
||||
|
||||
# Check if rsync is available on remote
|
||||
if ssh $SSH_OPTS "$TARGET_HOST" "which rsync" &>/dev/null; then
|
||||
rsync -avz --delete -e "ssh $SSH_OPTS" "$AIUI_DIST/" "$TARGET_HOST:/opt/archipelago/web-ui/aiui/" 2>&1 | tail -3
|
||||
else
|
||||
echo " rsync not available, using tar+scp..."
|
||||
TMPTAR=$(mktemp /tmp/aiui-dist-XXXXX.tar.gz)
|
||||
(cd "$AIUI_DIST" && tar czf "$TMPTAR" .)
|
||||
scp $SSH_OPTS "$TMPTAR" "$TARGET_HOST:/tmp/aiui-dist.tar.gz"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo mkdir -p /opt/archipelago/web-ui/aiui && cd /opt/archipelago/web-ui/aiui && sudo tar xzf /tmp/aiui-dist.tar.gz --overwrite"
|
||||
rm -f "$TMPTAR"
|
||||
fi
|
||||
echo " AIUI deployed."
|
||||
|
||||
# --- Step 2: Configure nginx Claude API proxy ---
|
||||
echo ""
|
||||
echo "$(timestamp) 🔧 Configuring nginx Claude API proxy..."
|
||||
|
||||
# Create a Python script to patch nginx config
|
||||
cat << 'PYSCRIPT' > /tmp/patch-nginx-claude.py
|
||||
import sys
|
||||
import re
|
||||
|
||||
API_KEY = sys.argv[1]
|
||||
|
||||
with open("/etc/nginx/sites-available/archipelago") as f:
|
||||
content = f.read()
|
||||
|
||||
# The new Claude API proxy block
|
||||
new_block = '''location /aiui/api/claude/ {
|
||||
if ($cookie_session = "") {
|
||||
return 401 '{"error":"Unauthorized"}';
|
||||
}
|
||||
proxy_pass https://api.anthropic.com/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host api.anthropic.com;
|
||||
proxy_set_header x-api-key "''' + API_KEY + '''";
|
||||
proxy_set_header anthropic-version "2023-06-01";
|
||||
proxy_set_header anthropic-dangerous-direct-browser-access "true";
|
||||
proxy_ssl_server_name on;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_connect_timeout 120s;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 120s;
|
||||
}'''
|
||||
|
||||
# Replace existing Claude API proxy blocks (handles both old proxy and direct patterns)
|
||||
pattern = r'location /aiui/api/claude/ \{[^}]*(?:\{[^}]*\}[^}]*)*\}'
|
||||
content = re.sub(pattern, new_block, content)
|
||||
|
||||
with open("/etc/nginx/sites-available/archipelago", "w") as f:
|
||||
f.write(content)
|
||||
|
||||
# Verify
|
||||
count = content.count("api.anthropic.com")
|
||||
print(f" Patched {count // 2} Claude API proxy blocks (HTTP + HTTPS)")
|
||||
PYSCRIPT
|
||||
|
||||
scp $SSH_OPTS /tmp/patch-nginx-claude.py "$TARGET_HOST:/tmp/patch-nginx-claude.py"
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo python3 /tmp/patch-nginx-claude.py '$ANTHROPIC_API_KEY'"
|
||||
|
||||
# Test and reload nginx
|
||||
echo " Testing nginx config..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "sudo nginx -t 2>&1 && sudo systemctl reload nginx && echo ' Nginx reloaded OK'" || {
|
||||
echo " ERROR: nginx config test failed!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Step 3: Fix FileBrowser container ---
|
||||
echo ""
|
||||
echo "$(timestamp) 📁 Checking FileBrowser..."
|
||||
|
||||
FB_STATUS=$(ssh $SSH_OPTS "$TARGET_HOST" "podman inspect filebrowser 2>/dev/null | grep -oP '\"ReadonlyRootfs\":\s*\K\w+'" 2>/dev/null || echo "not_found")
|
||||
|
||||
if [ "$FB_STATUS" = "true" ]; then
|
||||
echo " FileBrowser has read-only root — recreating..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
podman stop filebrowser 2>/dev/null
|
||||
podman rm filebrowser 2>/dev/null
|
||||
sudo mkdir -p /var/lib/archipelago/filebrowser
|
||||
podman run -d --name filebrowser --restart=always \
|
||||
-p 8083:80 \
|
||||
-v /var/lib/archipelago/filebrowser:/srv \
|
||||
filebrowser/filebrowser:v2.27.0
|
||||
" 2>&1 | tail -2
|
||||
echo " FileBrowser recreated."
|
||||
elif [ "$FB_STATUS" = "not_found" ]; then
|
||||
echo " FileBrowser not found — creating..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
sudo mkdir -p /var/lib/archipelago/filebrowser
|
||||
podman run -d --name filebrowser --restart=always \
|
||||
-p 8083:80 \
|
||||
-v /var/lib/archipelago/filebrowser:/srv \
|
||||
filebrowser/filebrowser:v2.27.0
|
||||
" 2>&1 | tail -2
|
||||
echo " FileBrowser created."
|
||||
else
|
||||
echo " FileBrowser OK (ReadonlyRootfs: $FB_STATUS)"
|
||||
fi
|
||||
|
||||
# --- Step 4: Verify ---
|
||||
echo ""
|
||||
echo "$(timestamp) ✅ Verification..."
|
||||
ssh $SSH_OPTS "$TARGET_HOST" "
|
||||
echo \" AIUI index: \$(ls -la /opt/archipelago/web-ui/aiui/index.html 2>/dev/null | awk '{print \$6,\$7,\$8}')\"
|
||||
echo \" FileBrowser: \$(podman ps --format '{{.Names}} {{.Status}}' | grep filebrowser)\"
|
||||
echo \" Nginx: \$(systemctl is-active nginx)\"
|
||||
echo \" Backend: \$(systemctl is-active archipelago)\"
|
||||
echo \" Claude API test: \$(curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost/aiui/api/claude/v1/messages -H 'Content-Type: application/json' -H 'Cookie: session=test' -d '{\"model\":\"claude-sonnet-4-20250514\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}')\"
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "$(timestamp) Done! Server configured."
|
||||
echo " Access: http://$(echo $TARGET_HOST | cut -d@ -f2)"
|
||||
@@ -1,280 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Set up HTTPS on Archipelago dev server for PWA installability.
|
||||
# Browsers require HTTPS (or localhost) to install PWAs.
|
||||
# Generates a self-signed certificate and configures nginx.
|
||||
#
|
||||
# Run on the target server: sudo ./setup-https-dev.sh
|
||||
# Or via deploy: the deploy script runs this automatically.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SSL_DIR="/etc/archipelago/ssl"
|
||||
NGINX_CFG="/etc/nginx/sites-available/archipelago"
|
||||
CERT="$SSL_DIR/archipelago.crt"
|
||||
KEY="$SSL_DIR/archipelago.key"
|
||||
|
||||
# Create SSL directory
|
||||
mkdir -p "$SSL_DIR"
|
||||
chmod 755 "$SSL_DIR"
|
||||
|
||||
# Generate self-signed cert if missing (valid 365 days)
|
||||
# SAN includes common dev IPs so cert works when accessing via IP
|
||||
# Build dynamic SAN with all node IPs (LAN + Tailscale + loopback)
|
||||
SAN_IPS="DNS:archipelago.local,DNS:localhost,IP:127.0.0.1"
|
||||
# Add all IPv4 addresses on this machine (LAN, Tailscale, etc.)
|
||||
for ip in $(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.' | grep -v '^127\.'); do
|
||||
SAN_IPS="$SAN_IPS,IP:$ip"
|
||||
done
|
||||
# Always include common LAN IPs as fallback
|
||||
for ip in 192.168.1.228 192.168.1.198 10.0.0.1; do
|
||||
echo "$SAN_IPS" | grep -q "$ip" || SAN_IPS="$SAN_IPS,IP:$ip"
|
||||
done
|
||||
|
||||
# Regenerate cert if missing OR if current cert doesn't include this node's primary IP
|
||||
REGEN=false
|
||||
if [ ! -f "$CERT" ] || [ ! -f "$KEY" ]; then
|
||||
REGEN=true
|
||||
else
|
||||
# Check if cert has this node's primary IP
|
||||
MY_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
if [ -n "$MY_IP" ] && ! openssl x509 -in "$CERT" -noout -text 2>/dev/null | grep -q "$MY_IP"; then
|
||||
echo " Certificate missing this node's IP ($MY_IP) — regenerating..."
|
||||
REGEN=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$REGEN" = true ]; then
|
||||
echo "Generating self-signed certificate for PWA (HTTPS)..."
|
||||
echo " SAN: $SAN_IPS"
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout "$KEY" \
|
||||
-out "$CERT" \
|
||||
-subj "/CN=archipelago.local/O=Archipelago/C=US" \
|
||||
-addext "subjectAltName=$SAN_IPS"
|
||||
chmod 644 "$CERT"
|
||||
chmod 600 "$KEY"
|
||||
echo " Certificate created at $CERT"
|
||||
fi
|
||||
|
||||
# PWA snippet for manifest + service worker headers (required for Android install)
|
||||
NGINX_SNIPPETS="/etc/nginx/snippets"
|
||||
PWA_SNIPPET="$NGINX_SNIPPETS/archipelago-pwa.conf"
|
||||
mkdir -p "$NGINX_SNIPPETS"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [ -f "$SCRIPT_DIR/nginx-pwa-snippet.conf" ]; then
|
||||
cp "$SCRIPT_DIR/nginx-pwa-snippet.conf" "$PWA_SNIPPET"
|
||||
echo " PWA nginx snippet installed at $PWA_SNIPPET"
|
||||
fi
|
||||
|
||||
# Add PWA snippet include to existing HTTPS block if missing
|
||||
if grep -q "listen 443 ssl" "$NGINX_CFG" 2>/dev/null && [ -f "$PWA_SNIPPET" ]; then
|
||||
if ! grep -q "archipelago-pwa" "$NGINX_CFG" 2>/dev/null; then
|
||||
echo " Adding PWA snippet include to HTTPS block..."
|
||||
# Insert include after "index index.html;" within the HTTPS server block (listen 443 to next })
|
||||
sed -i '/listen 443 ssl/,/^}$/{
|
||||
/index index.html;/a\
|
||||
include snippets/archipelago-pwa.conf;
|
||||
}' "$NGINX_CFG" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install app proxies snippet (mempool, fedimint, lnd, etc.) - fixes apps not opening over HTTPS (mixed content)
|
||||
APPS_SNIPPET="$NGINX_SNIPPETS/archipelago-https-app-proxies.conf"
|
||||
if [ -f "$SCRIPT_DIR/nginx-https-app-proxies.conf" ]; then
|
||||
cp "$SCRIPT_DIR/nginx-https-app-proxies.conf" "$APPS_SNIPPET"
|
||||
echo " HTTPS app proxies snippet installed at $APPS_SNIPPET"
|
||||
# Add include to HTTPS block if missing
|
||||
if grep -q "listen 443 ssl" "$NGINX_CFG" 2>/dev/null && ! grep -q "archipelago-https-app-proxies" "$NGINX_CFG" 2>/dev/null; then
|
||||
echo " Adding app proxies include to HTTPS block..."
|
||||
sed -i '/listen 443 ssl/,/^}$/{
|
||||
/location \/ws {/i\
|
||||
include snippets/archipelago-https-app-proxies.conf;
|
||||
}' "$NGINX_CFG" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if HTTPS is already configured
|
||||
if grep -q "listen 443 ssl" "$NGINX_CFG" 2>/dev/null; then
|
||||
echo "HTTPS already configured in nginx."
|
||||
nginx -t 2>/dev/null && systemctl reload nginx
|
||||
MY_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
echo ""
|
||||
echo "PWA: Use https://${MY_IP:-192.168.1.228} (not http) - accept cert once, then Install app."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Add HTTPS server block (duplicate of HTTP block with SSL)
|
||||
# PWA requires HTTPS for install on Android
|
||||
HTTPS_BLOCK='
|
||||
# HTTPS - required for PWA install (Add to Home Screen) from dev servers
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate '"$CERT"';
|
||||
ssl_certificate_key '"$KEY"';
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
|
||||
root /opt/archipelago/web-ui;
|
||||
index index.html;
|
||||
include snippets/archipelago-pwa.conf;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /archipelago/ {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location /rpc/ {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_connect_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
}
|
||||
|
||||
location /app/nextcloud/ {
|
||||
proxy_pass http://127.0.0.1:8085/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/vaultwarden/ {
|
||||
proxy_pass http://127.0.0.1:8082/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/immich/ {
|
||||
proxy_pass http://127.0.0.1:2283/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/penpot/ {
|
||||
proxy_pass http://127.0.0.1:9001/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/btcpay/ {
|
||||
proxy_pass http://127.0.0.1:23000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/homeassistant/ {
|
||||
proxy_pass http://127.0.0.1:8123/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s;
|
||||
}
|
||||
location /app/mempool/ {
|
||||
proxy_pass http://127.0.0.1:4080/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/fedimint/ {
|
||||
proxy_pass http://127.0.0.1:8175/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/lnd/ {
|
||||
proxy_pass http://127.0.0.1:18083/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/bitcoin-ui/ {
|
||||
proxy_pass http://127.0.0.1:8334/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://127.0.0.1:5678;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 86400s;
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
# Append HTTPS block to nginx config
|
||||
echo "$HTTPS_BLOCK" >> "$NGINX_CFG"
|
||||
echo "Added HTTPS (port 443) to nginx config."
|
||||
|
||||
# Test and reload
|
||||
nginx -t && systemctl reload nginx
|
||||
echo ""
|
||||
MY_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
echo "HTTPS enabled. PWA install: https://${MY_IP:-192.168.1.228} (accept the certificate warning once, then Install app)."
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Setup development environment on Archipelago target machine
|
||||
#
|
||||
# Run this ON the HP ProDesk via SSH:
|
||||
# curl -sSL https://raw.githubusercontent.com/.../setup-target-dev.sh | bash
|
||||
# Or copy and run locally:
|
||||
# scp scripts/setup-target-dev.sh archipelago@192.168.1.228:~/
|
||||
# ssh archipelago@192.168.1.228 'bash ~/setup-target-dev.sh'
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Setting up Archipelago Development Environment ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Update packages
|
||||
echo "📦 Updating packages..."
|
||||
sudo apt update
|
||||
|
||||
# Install Node.js (for Vue.js frontend)
|
||||
echo ""
|
||||
echo "📦 Installing Node.js..."
|
||||
if ! command -v node &> /dev/null; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
else
|
||||
echo " Node.js already installed: $(node --version)"
|
||||
fi
|
||||
|
||||
# Install Rust (for backend)
|
||||
echo ""
|
||||
echo "📦 Installing Rust..."
|
||||
if ! command -v cargo &> /dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source ~/.cargo/env
|
||||
else
|
||||
echo " Rust already installed: $(rustc --version)"
|
||||
fi
|
||||
|
||||
# Install build tools
|
||||
echo ""
|
||||
echo "📦 Installing build tools..."
|
||||
sudo apt install -y \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
git
|
||||
|
||||
# Create development directory
|
||||
echo ""
|
||||
echo "📁 Creating development directory..."
|
||||
mkdir -p ~/archy
|
||||
|
||||
# Fix XDG_RUNTIME_DIR for rootless Podman (add to bashrc)
|
||||
if ! grep -q "XDG_RUNTIME_DIR" ~/.bashrc; then
|
||||
echo ""
|
||||
echo "🔧 Fixing Podman rootless setup..."
|
||||
cat >> ~/.bashrc << 'EOF'
|
||||
|
||||
# Fix for rootless Podman
|
||||
if [ -z "$XDG_RUNTIME_DIR" ]; then
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u)
|
||||
if [ ! -d "$XDG_RUNTIME_DIR" ]; then
|
||||
sudo mkdir -p "$XDG_RUNTIME_DIR"
|
||||
sudo chown $(whoami):$(whoami) "$XDG_RUNTIME_DIR"
|
||||
sudo chmod 700 "$XDG_RUNTIME_DIR"
|
||||
fi
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Enable user lingering for Podman
|
||||
sudo loginctl enable-linger archipelago 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ✅ Development environment ready! ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "Installed:"
|
||||
echo " • Node.js: $(node --version 2>/dev/null || echo 'not found')"
|
||||
echo " • npm: $(npm --version 2>/dev/null || echo 'not found')"
|
||||
echo " • Rust: $(rustc --version 2>/dev/null || echo 'not found')"
|
||||
echo " • Cargo: $(cargo --version 2>/dev/null || echo 'not found')"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Log out and back in (or run: source ~/.bashrc)"
|
||||
echo " 2. From your Mac, run: ./scripts/deploy-to-target.sh"
|
||||
echo " 3. To start Vue.js dev server: cd ~/archy/neode-ui && npm run dev -- --host 0.0.0.0"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user