2026-06-21 17:55:28 +02:00
|
|
|
#!/usr/bin/env bash
|
2026-06-21 18:13:27 +02:00
|
|
|
# test-network.sh — spin up anchor + 3 peers, connect them, exchange messages + DMs.
|
2026-06-21 17:55:28 +02:00
|
|
|
# Usage: ./test-network.sh
|
2026-06-21 18:13:27 +02:00
|
|
|
# Requires: go, nc, jq, sqlite3
|
2026-06-21 17:55:28 +02:00
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
# ── colours ───────────────────────────────────────────────────────────────────
|
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
|
|
|
CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; BLUE='\033[0;34m'
|
|
|
|
|
BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'
|
|
|
|
|
|
|
|
|
|
ANCHOR_COLOR=$YELLOW
|
|
|
|
|
ALICE_COLOR=$CYAN
|
|
|
|
|
BOB_COLOR=$MAGENTA
|
|
|
|
|
CHARLIE_COLOR=$GREEN
|
|
|
|
|
|
|
|
|
|
# ── config ────────────────────────────────────────────────────────────────────
|
|
|
|
|
ANCHOR_PORT=19339
|
|
|
|
|
ALICE_IPC=19337
|
|
|
|
|
BOB_IPC=19340
|
|
|
|
|
CHARLIE_IPC=19343
|
|
|
|
|
NETWORK_NAME="test-$(date +%s)"
|
|
|
|
|
DATA_ROOT=$(mktemp -d /tmp/waste-test-XXXXXX)
|
|
|
|
|
|
|
|
|
|
# ── cleanup ───────────────────────────────────────────────────────────────────
|
|
|
|
|
PIDS=()
|
|
|
|
|
cleanup() {
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}── cleaning up ──────────────────────────────────────────${RESET}"
|
|
|
|
|
for pid in "${PIDS[@]:-}"; do
|
|
|
|
|
kill "$pid" 2>/dev/null || true
|
|
|
|
|
done
|
|
|
|
|
wait 2>/dev/null || true
|
2026-06-21 18:13:27 +02:00
|
|
|
echo -e "${DIM}data left at: ${DATA_ROOT}${RESET}"
|
|
|
|
|
echo -e "${DIM} inspect: sqlite3 ${DATA_ROOT}/alice/messages.db${RESET}"
|
2026-06-21 17:55:28 +02:00
|
|
|
echo -e "${DIM}done.${RESET}"
|
|
|
|
|
}
|
|
|
|
|
trap cleanup EXIT INT TERM
|
|
|
|
|
|
|
|
|
|
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
|
log() {
|
|
|
|
|
local color="$1"; shift
|
|
|
|
|
local label="$1"; shift
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} $*"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Pretty-print a raw IPC JSON line
|
|
|
|
|
pretty() {
|
|
|
|
|
local color="$1"
|
|
|
|
|
local label="$2"
|
|
|
|
|
local line="$3"
|
|
|
|
|
local type
|
|
|
|
|
type=$(echo "$line" | jq -r '.type // "?"' 2>/dev/null)
|
|
|
|
|
case "$type" in
|
|
|
|
|
state_snapshot)
|
|
|
|
|
local id nick
|
|
|
|
|
id=$(echo "$line" | jq -r '.local_peer.id[:16]' 2>/dev/null | sed 's/.\{4\}/& /g')
|
|
|
|
|
nick=$(echo "$line" | jq -r '.local_peer.alias' 2>/dev/null)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${DIM}state_snapshot${RESET} id=${CYAN}${id}${RESET} alias=${BOLD}${nick}${RESET}"
|
|
|
|
|
;;
|
|
|
|
|
session_ready)
|
|
|
|
|
local pid nick
|
|
|
|
|
pid=$(echo "$line" | jq -r 'if .peer_id then .peer_id[:16] else "?" end' 2>/dev/null | sed 's/.\{4\}/& /g')
|
|
|
|
|
nick=$(echo "$line" | jq -r '.nick // "?"' 2>/dev/null)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${GREEN}✓ session_ready${RESET} peer=${BOLD}${nick}${RESET} (${DIM}${pid}${RESET})"
|
|
|
|
|
;;
|
|
|
|
|
peer_connected)
|
|
|
|
|
local nick
|
|
|
|
|
nick=$(echo "$line" | jq -r '.peer.alias // "?"' 2>/dev/null)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${GREEN}→ peer_connected${RESET} ${BOLD}${nick}${RESET}"
|
|
|
|
|
;;
|
|
|
|
|
peer_disconnected)
|
|
|
|
|
local pid
|
|
|
|
|
pid=$(echo "$line" | jq -r 'if .peer_id then .peer_id[:8] else "?" end' 2>/dev/null)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${RED}← peer_disconnected${RESET} ${DIM}${pid}${RESET}"
|
|
|
|
|
;;
|
|
|
|
|
message_received)
|
2026-06-21 18:13:27 +02:00
|
|
|
local from body room to_field
|
2026-06-21 17:55:28 +02:00
|
|
|
from=$(echo "$line" | jq -r '.message.from[:8] // "?"' 2>/dev/null)
|
|
|
|
|
body=$(echo "$line" | jq -r '.message.body // ""' 2>/dev/null)
|
|
|
|
|
room=$(echo "$line" | jq -r '.message.room // ""' 2>/dev/null)
|
2026-06-21 18:13:27 +02:00
|
|
|
to_field=$(echo "$line" | jq -r '.message.to // ""' 2>/dev/null)
|
|
|
|
|
if [ -n "$to_field" ]; then
|
|
|
|
|
local to_short
|
|
|
|
|
to_short=$(echo "$to_field" | cut -c1-8)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${BLUE}📨 DM${RESET} ${DIM}<${from}…→${to_short}…>${RESET} ${body}"
|
|
|
|
|
else
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${BOLD}💬 #${room}${RESET} ${DIM}<${from}…>${RESET} ${body}"
|
|
|
|
|
fi
|
2026-06-21 17:55:28 +02:00
|
|
|
;;
|
|
|
|
|
error)
|
|
|
|
|
local msg
|
|
|
|
|
msg=$(echo "$line" | jq -r '.error_message // .' 2>/dev/null)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${RED}✗ error:${RESET} ${msg}"
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
echo -e "${color}${BOLD}[${label}]${RESET} ${DIM}${line}${RESET}"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Subscribe to a peer's IPC events in the background, pretty-printing each line.
|
|
|
|
|
subscribe() {
|
|
|
|
|
local color="$1"
|
|
|
|
|
local label="$2"
|
|
|
|
|
local port="$3"
|
|
|
|
|
(
|
|
|
|
|
set +e
|
|
|
|
|
nc 127.0.0.1 "$port" 2>/dev/null | while IFS= read -r line; do
|
|
|
|
|
pretty "$color" "$label" "$line"
|
|
|
|
|
done
|
|
|
|
|
) &
|
|
|
|
|
PIDS+=($!)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Send a single JSON command to a peer's IPC port (fire-and-forget).
|
|
|
|
|
ipc() {
|
|
|
|
|
local port="$1"
|
|
|
|
|
local json="$2"
|
|
|
|
|
echo "$json" | nc -q 0 127.0.0.1 "$port" >/dev/null 2>&1 || true
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
# Query get_state and return a single field via jq from the state_snapshot line.
|
|
|
|
|
# Retries for up to 3 seconds in case the daemon is still initialising.
|
|
|
|
|
# Usage: peer_field <ipc_port> <jq_expr>
|
|
|
|
|
peer_field() {
|
|
|
|
|
local port="$1"
|
|
|
|
|
local expr="$2"
|
|
|
|
|
local result=""
|
|
|
|
|
local n=0
|
|
|
|
|
while [ -z "$result" ] || [ "$result" = "null" ]; do
|
|
|
|
|
result=$(echo '{"type":"get_state"}' \
|
|
|
|
|
| timeout 2 nc 127.0.0.1 "$port" 2>/dev/null \
|
|
|
|
|
| grep '"type":"state_snapshot"' \
|
|
|
|
|
| head -1 \
|
|
|
|
|
| jq -r "$expr" 2>/dev/null || true)
|
|
|
|
|
n=$(( n + 1 ))
|
|
|
|
|
[ "$n" -ge 10 ] && break
|
|
|
|
|
[ -z "$result" ] || [ "$result" = "null" ] && sleep 0.3
|
|
|
|
|
done
|
|
|
|
|
echo "$result"
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-21 17:55:28 +02:00
|
|
|
# Wait until a TCP port accepts connections (up to N seconds).
|
|
|
|
|
wait_port() {
|
|
|
|
|
local port="$1"
|
|
|
|
|
local label="$2"
|
|
|
|
|
local n=0
|
|
|
|
|
while ! nc -z 127.0.0.1 "$port" 2>/dev/null; do
|
|
|
|
|
sleep 0.1
|
|
|
|
|
n=$(( n + 1 ))
|
|
|
|
|
if [ "$n" -gt 80 ]; then
|
|
|
|
|
echo -e "${RED}timeout waiting for ${label} on :${port}${RESET}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ── build ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${BOLD}waste-go network test${RESET}"
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
echo -e "${DIM}network : ${BOLD}${NETWORK_NAME}${RESET}"
|
|
|
|
|
echo -e "${DIM}data : ${DATA_ROOT}${RESET}"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
echo -e "${DIM}building binaries…${RESET}"
|
|
|
|
|
go build -o "$DATA_ROOT/bin/waste-anchor" ./cmd/anchor
|
|
|
|
|
go build -o "$DATA_ROOT/bin/waste-daemon" ./cmd/daemon
|
|
|
|
|
echo -e "${GREEN}✓ built${RESET}"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# ── anchor ────────────────────────────────────────────────────────────────────
|
|
|
|
|
log "$ANCHOR_COLOR" "anchor" "starting on :${ANCHOR_PORT}"
|
|
|
|
|
"$DATA_ROOT/bin/waste-anchor" -bind "127.0.0.1:${ANCHOR_PORT}" \
|
|
|
|
|
2> >(while IFS= read -r l; do echo -e "${ANCHOR_COLOR}${DIM}[anchor] ${l}${RESET}"; done) &
|
|
|
|
|
PIDS+=($!)
|
|
|
|
|
wait_port "$ANCHOR_PORT" "anchor"
|
|
|
|
|
log "$ANCHOR_COLOR" "anchor" "ready"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# ── daemons ───────────────────────────────────────────────────────────────────
|
|
|
|
|
ANCHOR_URL="ws://127.0.0.1:${ANCHOR_PORT}/ws"
|
|
|
|
|
|
|
|
|
|
log "$ALICE_COLOR" "alice" "starting daemon (ipc :${ALICE_IPC})"
|
|
|
|
|
"$DATA_ROOT/bin/waste-daemon" \
|
|
|
|
|
-alias alice \
|
|
|
|
|
-data-dir "$DATA_ROOT/alice" \
|
|
|
|
|
-ipc-port "$ALICE_IPC" \
|
|
|
|
|
-anchor "$ANCHOR_URL" \
|
|
|
|
|
2> >(while IFS= read -r l; do echo -e "${ALICE_COLOR}${DIM}[alice] ${l}${RESET}"; done) &
|
|
|
|
|
PIDS+=($!)
|
|
|
|
|
|
|
|
|
|
log "$BOB_COLOR" "bob" "starting daemon (ipc :${BOB_IPC})"
|
|
|
|
|
"$DATA_ROOT/bin/waste-daemon" \
|
|
|
|
|
-alias bob \
|
|
|
|
|
-data-dir "$DATA_ROOT/bob" \
|
|
|
|
|
-ipc-port "$BOB_IPC" \
|
|
|
|
|
-anchor "$ANCHOR_URL" \
|
|
|
|
|
2> >(while IFS= read -r l; do echo -e "${BOB_COLOR}${DIM}[bob] ${l}${RESET}"; done) &
|
|
|
|
|
PIDS+=($!)
|
|
|
|
|
|
|
|
|
|
log "$CHARLIE_COLOR" "charlie" "starting daemon (ipc :${CHARLIE_IPC})"
|
|
|
|
|
"$DATA_ROOT/bin/waste-daemon" \
|
|
|
|
|
-alias charlie \
|
|
|
|
|
-data-dir "$DATA_ROOT/charlie" \
|
|
|
|
|
-ipc-port "$CHARLIE_IPC" \
|
|
|
|
|
-anchor "$ANCHOR_URL" \
|
|
|
|
|
2> >(while IFS= read -r l; do echo -e "${CHARLIE_COLOR}${DIM}[charlie]${l}${RESET}"; done) &
|
|
|
|
|
PIDS+=($!)
|
|
|
|
|
|
|
|
|
|
wait_port "$ALICE_IPC" "alice"
|
|
|
|
|
wait_port "$BOB_IPC" "bob"
|
|
|
|
|
wait_port "$CHARLIE_IPC" "charlie"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# ── subscribe ─────────────────────────────────────────────────────────────────
|
|
|
|
|
echo -e "${DIM}subscribing to IPC event streams…${RESET}"
|
|
|
|
|
subscribe "$ALICE_COLOR" "alice " "$ALICE_IPC"
|
|
|
|
|
subscribe "$BOB_COLOR" "bob " "$BOB_IPC"
|
|
|
|
|
subscribe "$CHARLIE_COLOR" "charlie" "$CHARLIE_IPC"
|
|
|
|
|
sleep 0.3 # let state_snapshot lines arrive
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
# ── resolve peer IDs ──────────────────────────────────────────────────────────
|
|
|
|
|
# Each daemon knows its own id from the state_snapshot.
|
|
|
|
|
ALICE_ID=$(peer_field "$ALICE_IPC" '.local_peer.id')
|
|
|
|
|
BOB_ID=$(peer_field "$BOB_IPC" '.local_peer.id')
|
|
|
|
|
CHARLIE_ID=$(peer_field "$CHARLIE_IPC" '.local_peer.id')
|
|
|
|
|
|
|
|
|
|
log "$ANCHOR_COLOR" "ids" "alice = ${ALICE_ID:0:16}…"
|
|
|
|
|
log "$ANCHOR_COLOR" "ids" "bob = ${BOB_ID:0:16}…"
|
|
|
|
|
log "$ANCHOR_COLOR" "ids" "charlie = ${CHARLIE_ID:0:16}…"
|
2026-06-21 17:55:28 +02:00
|
|
|
echo ""
|
2026-06-21 18:13:27 +02:00
|
|
|
|
|
|
|
|
# ── join network ──────────────────────────────────────────────────────────────
|
2026-06-21 17:55:28 +02:00
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
echo -e "joining all peers to network: ${BOLD}${NETWORK_NAME}${RESET}"
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
|
|
|
|
|
JOIN=$(printf '{"type":"join_network","network_name":"%s"}' "$NETWORK_NAME")
|
|
|
|
|
ipc "$ALICE_IPC" "$JOIN"
|
|
|
|
|
sleep 0.2
|
|
|
|
|
ipc "$BOB_IPC" "$JOIN"
|
|
|
|
|
sleep 0.2
|
|
|
|
|
ipc "$CHARLIE_IPC" "$JOIN"
|
|
|
|
|
|
|
|
|
|
echo -e "${DIM}waiting for ICE / DataChannel setup (up to 10s)…${RESET}"
|
|
|
|
|
sleep 6
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
# ── group chat ────────────────────────────────────────────────────────────────
|
2026-06-21 17:55:28 +02:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
2026-06-21 18:13:27 +02:00
|
|
|
echo -e "group chat (#general)"
|
2026-06-21 17:55:28 +02:00
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"hey everyone, alice here"}'
|
2026-06-21 17:55:28 +02:00
|
|
|
sleep 0.4
|
2026-06-21 18:13:27 +02:00
|
|
|
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"hi alice! bob checking in"}'
|
2026-06-21 17:55:28 +02:00
|
|
|
sleep 0.4
|
|
|
|
|
ipc "$CHARLIE_IPC" '{"type":"send_message","room":"general","body":"charlie online. the mesh works!"}'
|
|
|
|
|
sleep 0.4
|
2026-06-21 18:13:27 +02:00
|
|
|
ipc "$ALICE_IPC" '{"type":"send_message","room":"general","body":"nice — three-way chat over WebRTC 🎉"}'
|
2026-06-21 17:55:28 +02:00
|
|
|
sleep 0.4
|
2026-06-21 18:13:27 +02:00
|
|
|
ipc "$BOB_IPC" '{"type":"send_message","room":"general","body":"no central server, fully peer-to-peer"}'
|
2026-06-21 17:55:28 +02:00
|
|
|
sleep 0.4
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
# ── direct messages ───────────────────────────────────────────────────────────
|
|
|
|
|
# DMs set the "to" field to the recipient's full hex peer ID.
|
|
|
|
|
# The room field is used as "dm:<recipient_id>" by convention so both sides
|
|
|
|
|
# can query their DM history for the same conversation.
|
2026-06-21 17:55:28 +02:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
2026-06-21 18:13:27 +02:00
|
|
|
echo -e "direct messages"
|
2026-06-21 17:55:28 +02:00
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
2026-06-21 18:13:27 +02:00
|
|
|
|
|
|
|
|
# Alice → Bob (privately)
|
|
|
|
|
ipc "$ALICE_IPC" "$(jq -cn \
|
|
|
|
|
--arg to "$BOB_ID" \
|
|
|
|
|
--arg room "dm:$BOB_ID" \
|
|
|
|
|
--arg body "hey bob, this is just between us" \
|
|
|
|
|
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
|
|
|
|
sleep 0.4
|
|
|
|
|
|
|
|
|
|
# Bob replies to Alice
|
|
|
|
|
ipc "$BOB_IPC" "$(jq -cn \
|
|
|
|
|
--arg to "$ALICE_ID" \
|
|
|
|
|
--arg room "dm:$ALICE_ID" \
|
|
|
|
|
--arg body "got it alice, loud and clear 🤫" \
|
|
|
|
|
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
|
|
|
|
sleep 0.4
|
|
|
|
|
|
|
|
|
|
# Charlie → Alice (privately)
|
|
|
|
|
ipc "$CHARLIE_IPC" "$(jq -cn \
|
|
|
|
|
--arg to "$ALICE_ID" \
|
|
|
|
|
--arg room "dm:$ALICE_ID" \
|
|
|
|
|
--arg body "alice, can you hear me? charlie dm test" \
|
|
|
|
|
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
|
|
|
|
sleep 0.4
|
|
|
|
|
|
|
|
|
|
# Alice replies to Charlie
|
|
|
|
|
ipc "$ALICE_IPC" "$(jq -cn \
|
|
|
|
|
--arg to "$CHARLIE_ID" \
|
|
|
|
|
--arg room "dm:$CHARLIE_ID" \
|
|
|
|
|
--arg body "yes charlie, DMs working perfectly!" \
|
|
|
|
|
'{"type":"send_message","room":$room,"body":$body,"to":$to}')"
|
|
|
|
|
sleep 0.4
|
2026-06-21 17:55:28 +02:00
|
|
|
|
|
|
|
|
# ── leave ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
echo -e "charlie leaves the network"
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
ipc "$CHARLIE_IPC" '{"type":"leave_network"}'
|
|
|
|
|
sleep 1
|
|
|
|
|
|
2026-06-21 18:13:27 +02:00
|
|
|
# ── persistence check ─────────────────────────────────────────────────────────
|
2026-06-21 18:04:42 +02:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
echo -e "verifying persistence (SQLite)"
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
for peer in alice bob charlie; do
|
|
|
|
|
db="$DATA_ROOT/$peer/messages.db"
|
|
|
|
|
if [ -f "$db" ]; then
|
2026-06-21 18:13:27 +02:00
|
|
|
total=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages;" 2>/dev/null || echo "?")
|
|
|
|
|
group=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room='general';" 2>/dev/null || echo "?")
|
|
|
|
|
dms=$(sqlite3 "$db" "SELECT COUNT(*) FROM messages WHERE room LIKE 'dm:%';" 2>/dev/null || echo "?")
|
|
|
|
|
peers_count=$(sqlite3 "$db" "SELECT COUNT(*) FROM peers;" 2>/dev/null || echo "?")
|
|
|
|
|
echo -e " ${BOLD}${peer}${RESET}: ${total} total (${group} group, ${dms} DM), ${peers_count} known peers"
|
2026-06-21 18:04:42 +02:00
|
|
|
else
|
|
|
|
|
echo -e " ${RED}${peer}: no messages.db found${RESET}"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
2026-06-21 17:55:28 +02:00
|
|
|
echo ""
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
echo -e "${GREEN}${BOLD}test complete${RESET}"
|
|
|
|
|
echo -e "${DIM}────────────────────────────────────────────────────────${RESET}"
|
|
|
|
|
sleep 0.5
|