igneum/infra/seed-nodes/create-seed.sh
igneum-labs 72351e8270 Testnet seeds: NET=testnet profile for the seed scripts, seeds-testnet.tsv (3 Hetzner VMs), dns.sh (deSEC), the public RPC allowlist and nginx site, build-job.mjs forwards --node-tests
seed1.testnet nbg1 195.201.35.33, seed2.testnet ash 5.161.232.205, seed3.testnet sin 5.223.52.210 (5 October 2026). Ports 26810/26811/28810/26890
from seed.env; provision-seed.sh BUILD_WHERE=cross takes the PC build job's Linux igneumd from infra/cross/out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 15:46:22 +00:00

75 lines
5.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# Create one seed VM with a fixed public IPv4. Hetzner (hcloud) primary; DigitalOcean (doctl) with PROVIDER=digitalocean.
# ./create-seed.sh creates SEED_NAME (default igneum-seed-1) in SEED_LOCATION
# SEED_NAME=igneum-seed-2 SEED_LOCATION=ash ./create-seed.sh
# Firewall: inbound tcp 26611 (p2p) from anywhere, tcp 22 from SSH_SOURCE, icmp; nothing else. RPC binds to loopback
# in the unit, so no rule is needed for it. The IPv4 is made persistent (Hetzner: primary IP auto-delete off; DO:
# a reserved IP), so a rebuilt server keeps the address that clients have baked in.
# Appends the seed to seeds.tsv and rewrites seeds.txt. Asks "yes" before spending (YES=1 skips).
. "$(dirname "$0")/lib.sh"
mkdir -p "$BUILD_DIR"
[ -f "$SSH_KEY_FILE.pub" ] || die "no public key at $SSH_KEY_FILE.pub"
if grep -q "^$SEED_NAME " "$SEEDS_TSV" 2>/dev/null; then die "$SEED_NAME is already in seeds.tsv"; fi
ssh_cidr="0.0.0.0/0"
case "$SSH_SOURCE" in
any) ssh_cidr="0.0.0.0/0" ;;
me) me=$(curl -s --max-time 10 https://api.ipify.org || true); [ -n "$me" ] || die "could not learn this Mac's public IP"; ssh_cidr="$me/32" ;;
*) ssh_cidr="$SSH_SOURCE" ;;
esac
if [ "$PROVIDER" = digitalocean ]; then
need doctl "brew install doctl"
key_id=$(doctl compute ssh-key list --format ID,Name --no-header | awk -v n="$SSH_KEY_NAME" '$2 == n { print $1 }')
[ -n "$key_id" ] || key_id=$(doctl compute ssh-key import "$SSH_KEY_NAME" --public-key-file "$SSH_KEY_FILE.pub" --format ID --no-header)
log "plan: $SEED_NAME, $DO_SIZE, $DO_IMAGE, $DO_REGION, reserved IPv4, firewall 22 from $ssh_cidr + 26611 from anywhere"
doctl compute size list --format Slug,Memory,VCPUs,Disk,PriceMonthly,PriceHourly | grep -E "^Slug|^$DO_SIZE "
[ "${YES:-0}" = 1 ] || { printf 'create it now (billing starts) [type yes]: '; read -r a; [ "$a" = yes ] || die "not confirmed"; }
fw=$(doctl compute firewall list --format ID,Name --no-header | awk '$2 == "igneum-seed" { print $1 }')
[ -n "$fw" ] || fw=$(doctl compute firewall create --name igneum-seed --tag-names igneum-seed \
--inbound-rules "protocol:tcp,ports:22,address:$ssh_cidr protocol:tcp,ports:$P2P_PORT,address:0.0.0.0/0,address:::/0 protocol:icmp,address:0.0.0.0/0,address:::/0" \
--outbound-rules "protocol:tcp,ports:all,address:0.0.0.0/0,address:::/0 protocol:udp,ports:all,address:0.0.0.0/0,address:::/0 protocol:icmp,address:0.0.0.0/0,address:::/0" --format ID --no-header)
did=$(doctl compute droplet create "$SEED_NAME" --size "$DO_SIZE" --image "$DO_IMAGE" --region "$DO_REGION" --ssh-keys "$key_id" --tag-names igneum-seed --wait --format ID --no-header)
rip=$(doctl compute reserved-ip create --region "$DO_REGION" --format IP --no-header)
doctl compute reserved-ip-action assign "$rip" "$did" >/dev/null
ip="$rip"; loc="$DO_REGION"; typ="$DO_SIZE"
else
hetzner_auth
if ! hcloud ssh-key describe "$SSH_KEY_NAME" >/dev/null 2>&1; then
hcloud ssh-key create --name "$SSH_KEY_NAME" --public-key-from-file "$SSH_KEY_FILE.pub" >/dev/null; log "uploaded ssh key $SSH_KEY_NAME"
fi
if ! hcloud firewall describe "$FIREWALL_NAME" >/dev/null 2>&1; then
hcloud firewall create --name "$FIREWALL_NAME" --label igneum=seed --label net="$NET" >/dev/null
hcloud firewall add-rule "$FIREWALL_NAME" --direction in --protocol tcp --port 22 --source-ips "$ssh_cidr" --description ssh >/dev/null
hcloud firewall add-rule "$FIREWALL_NAME" --direction in --protocol tcp --port "$P2P_PORT" --source-ips 0.0.0.0/0 --source-ips ::/0 --description igneum-p2p >/dev/null
hcloud firewall add-rule "$FIREWALL_NAME" --direction in --protocol icmp --source-ips 0.0.0.0/0 --source-ips ::/0 --description ping >/dev/null
log "created firewall $FIREWALL_NAME (in: 22 from $ssh_cidr, $P2P_PORT from anywhere, icmp)"
fi
log "plan: $SEED_NAME, $SEED_TYPE, $IMAGE, $SEED_LOCATION, persistent primary IPv4, firewall $FIREWALL_NAME"
hcloud server-type describe "$SEED_TYPE" -o json | python3 -c '
import json, sys
j = json.load(sys.stdin); loc = sys.argv[1]
for p in j["prices"]:
if p["location"] == loc:
print(" %s in %s: USD %.2f per month net (%.2f gross), USD %.4f per hour net, %d TB traffic included (Hetzner API)" % (
j["name"], loc, float(p["price_monthly"]["net"]), float(p["price_monthly"]["gross"]), float(p["price_hourly"]["net"]), int(p.get("included_traffic", 0)) // (1 << 40)))' "$SEED_LOCATION"
log " plus the primary IPv4: USD 0.60 per month net, 0.72 gross (Hetzner pricing API)"
[ "${YES:-0}" = 1 ] || { printf 'create it now (billing starts) [type yes]: '; read -r a; [ "$a" = yes ] || die "not confirmed"; }
if hcloud server describe "$SEED_NAME" >/dev/null 2>&1; then log "$SEED_NAME exists, reusing it"; else
hcloud server create --name "$SEED_NAME" --type "$SEED_TYPE" --image "$IMAGE" --location "$SEED_LOCATION" \
--ssh-key "$SSH_KEY_NAME" --firewall "$FIREWALL_NAME" --label igneum=seed --label role=seed --label net="$NET" >/dev/null
log "created $SEED_NAME"
fi
ip=$(hcloud server ip "$SEED_NAME")
pip=$(hcloud primary-ip list -o noheader -o columns=id,ip | awk -v ip="$ip" '$2 == ip { print $1 }')
if [ -n "$pip" ]; then hcloud primary-ip update "$pip" --auto-delete=false --name "$SEED_NAME-v4" >/dev/null && log "primary IPv4 $ip ($pip) set to persist (auto-delete off)"; fi
loc="$SEED_LOCATION"; typ="$SEED_TYPE"
fi
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$SEED_NAME" "$PROVIDER" "$loc" "$ip" "$typ" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$SEEDS_TSV"
write_seeds_txt
log "seed $SEED_NAME at $ip; seeds.txt now:"; cat "$SEEDS_TXT"
log "waiting for ssh"
for try in $(seq 1 20); do sssh "$ip" true >/dev/null 2>&1 && break; sleep 10; done
sssh "$ip" 'hostname; uname -m; cat /etc/debian_version' || log "WARNING: ssh not up yet; provision-seed.sh retries"
log "next: ./provision-seed.sh $SEED_NAME"