From 0d68111bc24e6293b4de8d00295ca9135050e333 Mon Sep 17 00:00:00 2001 From: Jan Novak Date: Tue, 11 Aug 2026 22:40:24 +0200 Subject: [PATCH] Add demo scripts: egress IP check and bulk proxy creation docs/demo/show-egress-ips.sh probes each healthy proxy from the discovery API against an IP-echo site; create-kubernetes-proxies.sh and create-gcp-proxies.sh bulk-create demo Proxies, the gcp one spreading them across randomly picked EU zones. Co-Authored-By: Claude --- docs/demo/create-gcp-proxies.sh | 94 +++++++++++++++++++ docs/demo/create-kubernetes-proxies.sh | 48 ++++++++++ docs/demo/show-egress-ips.sh | 74 +++++++++++++++ .../2026-08-11-2220-demo-scripts.md | 38 ++++++++ 4 files changed, 254 insertions(+) create mode 100755 docs/demo/create-gcp-proxies.sh create mode 100755 docs/demo/create-kubernetes-proxies.sh create mode 100755 docs/demo/show-egress-ips.sh create mode 100644 docs/plans-executions/2026-08-11-2220-demo-scripts.md diff --git a/docs/demo/create-gcp-proxies.sh b/docs/demo/create-gcp-proxies.sh new file mode 100755 index 0000000..3a3665d --- /dev/null +++ b/docs/demo/create-gcp-proxies.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# Demo: create N Managed proxies backed by the gcp provider, named +# proxy-gcp-demo-1 .. proxy-gcp-demo-N, each in a randomly picked EU zone +# so the fleet gets egress IPs from different locations. +# +# Usage: +# ./create-gcp-proxies.sh +# +# Optional environment: +# NAMESPACE namespace to create the proxies in (default: current context) +# GCP_PROVIDER provider NAME from providers.yaml (default: gcp-eu) +# ZONES space-separated zone list to pick from (default: EU zones below) +set -euo pipefail + +if [ $# -ne 1 ] || ! [[ "$1" =~ ^[1-9][0-9]*$ ]]; then + echo "usage: $(basename "$0") (positive integer)" >&2 + exit 1 +fi +count="$1" +provider="${GCP_PROVIDER:-gcp-eu}" + +# GCP zones in the EU where e2-micro is generally available. Override with +# ZONES="zone1 zone2 ..." if your project has quota only in some of them. +default_zones=( + europe-west1-b europe-west1-c europe-west1-d # Belgium + europe-west2-a europe-west2-b europe-west2-c # London + europe-west3-a europe-west3-b europe-west3-c # Frankfurt + europe-west4-a europe-west4-b europe-west4-c # Netherlands + europe-west6-a europe-west6-b europe-west6-c # Zurich + europe-west8-a europe-west8-b europe-west8-c # Milan + europe-west9-a europe-west9-b europe-west9-c # Paris + europe-central2-a europe-central2-b europe-central2-c # Warsaw + europe-north1-a europe-north1-b europe-north1-c # Finland + europe-southwest1-a europe-southwest1-b europe-southwest1-c # Madrid +) +if [ -n "${ZONES:-}" ]; then + read -r -a zones <<< "${ZONES}" +else + zones=("${default_zones[@]}") +fi + +if ! command -v kubectl &> /dev/null; then + echo "ERROR: kubectl is required but not installed" >&2 + exit 1 +fi + +ns_args=() +if [ -n "${NAMESPACE:-}" ]; then + ns_args=(-n "${NAMESPACE}") +fi + +for i in $(seq 1 "${count}"); do + zone="${zones[RANDOM % ${#zones[@]}]}" + echo "Creating proxy-gcp-demo-${i} in ${zone} ..." + kubectl apply "${ns_args[@]}" -f - << EOF +apiVersion: crawl.example.com/v1alpha1 +kind: Proxy +metadata: + name: proxy-gcp-demo-${i} +spec: + mode: Managed + provider: ${provider} # must match a provider NAME in providers.yaml + placement: + zone: ${zone} + machineType: e2-micro + # debian-cloud images have no cloud-init, so spec.cloudInit (passed as + # user-data metadata) would be silently ignored there. Ubuntu images do. + image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2404-lts-amd64 + port: 3128 + cloudInit: + inline: | + #cloud-config + package_update: true + packages: + - squid + write_files: + - path: /etc/squid/conf.d/proxy-operator.conf + content: | + http_access allow all + via off + forwarded_for off + runcmd: + - systemctl restart squid + attributes: + geo: eu + zone: ${zone} + purpose: crawl +EOF +done + +echo "" +echo "Created ${count} proxies. VMs take a few minutes to provision and pass" +echo "the health check. Watch them come up with:" +echo " kubectl get px ${ns_args[*]:-} -w" diff --git a/docs/demo/create-kubernetes-proxies.sh b/docs/demo/create-kubernetes-proxies.sh new file mode 100755 index 0000000..226ccf4 --- /dev/null +++ b/docs/demo/create-kubernetes-proxies.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Demo: create N Managed proxies backed by the kubernetes-pod provider, +# named proxy-kubernetes-demo-1 .. proxy-kubernetes-demo-N. Pods share the +# cluster's egress IP — this exercises the full lifecycle, not distinct +# egress paths (use create-gcp-proxies.sh for that). +# +# Usage: +# ./create-kubernetes-proxies.sh +# +# Optional environment: +# NAMESPACE namespace to create the proxies in (default: current context) +set -euo pipefail + +if [ $# -ne 1 ] || ! [[ "$1" =~ ^[1-9][0-9]*$ ]]; then + echo "usage: $(basename "$0") (positive integer)" >&2 + exit 1 +fi +count="$1" + +if ! command -v kubectl &> /dev/null; then + echo "ERROR: kubectl is required but not installed" >&2 + exit 1 +fi + +ns_args=() +if [ -n "${NAMESPACE:-}" ]; then + ns_args=(-n "${NAMESPACE}") +fi + +for i in $(seq 1 "${count}"); do + echo "Creating proxy-kubernetes-demo-${i} ..." + kubectl apply "${ns_args[@]}" -f - << EOF +apiVersion: crawl.example.com/v1alpha1 +kind: Proxy +metadata: + name: proxy-kubernetes-demo-${i} +spec: + mode: Managed + provider: kubernetes + attributes: + geo: local + purpose: crawl +EOF +done + +echo "" +echo "Created ${count} proxies. Watch them come up with:" +echo " kubectl get px ${ns_args[*]:-} -w" diff --git a/docs/demo/show-egress-ips.sh b/docs/demo/show-egress-ips.sh new file mode 100755 index 0000000..f0940ed --- /dev/null +++ b/docs/demo/show-egress-ips.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Demo: list proxies from the discovery API and show the egress IP each +# healthy one provides, by calling an IP-echo site through it. +# +# Usage: +# ./show-egress-ips.sh e.g. ./show-egress-ips.sh localhost:8090 +# +# Optional environment: +# TOKEN bearer token for the discovery API (see docs/api.md) +# IP_ECHO_URL site that returns the caller's IP as JSON +# (default: https://api.ipify.org?format=json) +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "usage: $(basename "$0") (e.g. localhost:8090)" >&2 + exit 1 +fi +BASE_URL="$1" +IP_ECHO_URL="${IP_ECHO_URL:-https://api.ipify.org?format=json}" + +for tool in curl jq; do + if ! command -v "${tool}" &> /dev/null; then + echo "ERROR: ${tool} is required but not installed" >&2 + exit 1 + fi +done + +auth_args=() +if [ -n "${TOKEN:-}" ]; then + auth_args=(-H "Authorization: Bearer ${TOKEN}") +fi + +echo "Fetching proxies from ${BASE_URL}/v1/proxies ..." +if ! proxies_json=$(curl -sS --fail "${auth_args[@]}" "${BASE_URL}/v1/proxies"); then + echo "ERROR: could not fetch proxy list from ${BASE_URL}" >&2 + exit 1 +fi +if ! echo "${proxies_json}" | jq -e . > /dev/null; then + echo "ERROR: response from ${BASE_URL}/v1/proxies is not valid JSON" >&2 + exit 1 +fi + +total=$(echo "${proxies_json}" | jq -r '.count') +echo "Found ${total} proxies" +echo "" + +probed=0 +skipped=0 +failed=0 +while IFS= read -r proxy; do + id=$(echo "${proxy}" | jq -r '.id') + ip=$(echo "${proxy}" | jq -r '.ip') + port=$(echo "${proxy}" | jq -r '.port') + healthy=$(echo "${proxy}" | jq -r '.healthy') + + if [ "${healthy}" != "true" ]; then + echo "--- skipping ${id} (unhealthy) ---" + echo "" + skipped=$((skipped + 1)) + continue + fi + + echo "=== via ${id} — http://${ip}:${port} ===" + if response=$(curl -sS --max-time 10 -x "http://${ip}:${port}" "${IP_ECHO_URL}"); then + echo "${response}" | jq . 2> /dev/null || echo "${response}" + probed=$((probed + 1)) + else + echo "WARNING: request through ${id} failed" >&2 + failed=$((failed + 1)) + fi + echo "" +done < <(echo "${proxies_json}" | jq -c '.proxies[]') + +echo "Done: ${total} proxies — ${probed} probed, ${skipped} skipped (unhealthy), ${failed} failed" diff --git a/docs/plans-executions/2026-08-11-2220-demo-scripts.md b/docs/plans-executions/2026-08-11-2220-demo-scripts.md new file mode 100644 index 0000000..56dc928 --- /dev/null +++ b/docs/plans-executions/2026-08-11-2220-demo-scripts.md @@ -0,0 +1,38 @@ +# Execution: Demo scripts + +Plan: [2026-08-11-2220-demo-scripts.md](../plans/2026-08-11-2220-demo-scripts.md) + +- [x] Step 0 — Branch `feat/demo-scripts` + plan commit +- [x] Step 1 — `docs/demo/show-egress-ips.sh` +- [x] Extra (added iteratively, not in the original plan) — proxy-creation scripts + +## Step 0 + Step 1 + +Branched off `main`, committed the plan alone, then wrote +`docs/demo/show-egress-ips.sh`: takes `BASE_URL` as its argument, lists +`/v1/proxies` (bearer auth via optional `TOKEN` env), probes each healthy +proxy with `curl -x http://ip:port` against an IP-echo site +(`IP_ECHO_URL`, default ipify JSON), banners which proxy each request goes +through, skips unhealthy ones, and ends with a probed/skipped/failed +summary. Per user request the script was left uncommitted for iteration +and no verification beyond `bash -n` was run. + +## Extra — create-kubernetes-proxies.sh, create-gcp-proxies.sh + +Added on the same branch before the first commit: + +- `create-kubernetes-proxies.sh ` — creates + `proxy-kubernetes-demo-1..N` with the kubernetes provider, spec taken + from `config/samples/proxy_kubernetes.yaml`, applied via + `kubectl apply -f -` heredocs. Optional `NAMESPACE` env. +- `create-gcp-proxies.sh ` — creates `proxy-gcp-demo-1..N` from the + user-supplied gcp-eu manifest (e2-micro, Ubuntu 24.04, Squid + cloud-init), each with a zone picked randomly from a hardcoded list of + 27 EU zones so the fleet gets egress IPs from different locations. + Env overrides: `ZONES`, `GCP_PROVIDER` (default `gcp-eu`), `NAMESPACE`. + +Worth noting: beyond the user's sample manifest, the gcp script also +writes the picked zone into `attributes.zone`, so the discovery API +exposes each proxy's location and leases can select on it. The zone list +is static — if a project lacks quota in some region, `ZONES` narrows the +pool; nothing validates zones against the live project.