security: remove all infrastructure and internal process material from the repo
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:
archipelago
2026-08-07 10:37:20 -04:00
co-authored by Claude Opus 5
parent cc00884b98
commit 6ba0599639
282 changed files with 119 additions and 58845 deletions
+3 -3
View File
@@ -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() {
+1 -1
View File
@@ -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)"
-31
View File
@@ -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'
-7
View File
@@ -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
+1 -1
View File
@@ -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).
-176
View File
@@ -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
-135
View File
@@ -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
-252
View File
@@ -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
+2 -2
View File
@@ -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"
-176
View File
@@ -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)"
-280
View File
@@ -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)."
-93
View File
@@ -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 ""