#!/bin/sh # rocketplaneIO — one-command install # # curl -fsSL https://rocketplane.io/install.sh | sh # # What this does (and nothing else): # 1. creates ./rocketplane and downloads the compose bundle from GitHub # (docker-compose.prod.yml, otel-collector-config.yaml, .env template) # 2. resolves the NEWEST published release from GitHub (falls back to `edge`) # 3. generates RP_SESSION_SECRET and datastore passwords into .env # 4. pulls the images and runs `docker compose up -d` # # Everything is self-hosted; nothing phones home except a single unauthenticated # GitHub API call to look up the latest version. Re-running is the UPDATE path: # your .env (secrets) is kept, RP_VERSION is bumped to the newest release, and # the images are re-pulled — so `curl … | sh` again = update to the latest. # # Pin a version yourself by exporting RP_VERSION before running, e.g. # RP_VERSION=v0.2.0 curl -fsSL https://rocketplane.io/install.sh | sh set -eu REPO="olemeyer/rocketplaneIO" REPO_RAW="https://raw.githubusercontent.com/$REPO/main/deploy/compose" DIR="${ROCKETPLANE_DIR:-rocketplane}" say() { printf '\033[1m%s\033[0m\n' "$*"; } fail() { printf 'error: %s\n' "$*" >&2; exit 1; } command -v curl >/dev/null 2>&1 || fail "curl is required" command -v docker >/dev/null 2>&1 || fail "docker is required — https://docs.docker.com/get-docker/" docker info >/dev/null 2>&1 || fail "the docker daemon is not running (or you lack permission)" if docker compose version >/dev/null 2>&1; then COMPOSE="docker compose" elif command -v docker-compose >/dev/null 2>&1; then COMPOSE="docker-compose" else fail "docker compose is required — https://docs.docker.com/compose/install/" fi # Resolve the image tag to install. Honour an explicit RP_VERSION override, # otherwise ask GitHub for the newest release tag (ghcr images drop the leading # "v": release v0.2.0 → image tag 0.2.0). Fall back to `edge` (tracks main) if # the API is unreachable or no release exists yet. resolve_version() { if [ -n "${RP_VERSION:-}" ]; then printf '%s' "${RP_VERSION#v}" return fi v="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \ | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" [ -n "$v" ] && printf '%s' "$v" || printf 'edge' } rand_hex() { if command -v openssl >/dev/null 2>&1; then openssl rand -hex 32 else head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' fi } # Detect the host's primary LAN IP. This is the address an in-cluster agent can # actually reach the control plane at — `localhost` is the agent POD from inside # a cluster, and `host.minikube.internal` doesn't resolve on every setup. The # LAN IP works from minikube, kind and Docker Desktop clusters on this machine, # so the "Connect cluster" command the UI hands out just works out of the box. detect_host_ip() { ip="" if command -v ip >/dev/null 2>&1; then ip="$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oE 'src [0-9.]+' | awk '{print $2}' | head -1)" fi if [ -z "$ip" ] && command -v ipconfig >/dev/null 2>&1; then for i in en0 en1 eth0; do ip="$(ipconfig getifaddr "$i" 2>/dev/null)" && [ -n "$ip" ] && break; done fi [ -z "$ip" ] && command -v hostname >/dev/null 2>&1 && ip="$(hostname -I 2>/dev/null | awk '{print $1}')" printf '%s' "$ip" } say "→ setting up in ./$DIR" mkdir -p "$DIR" cd "$DIR" curl -fsSLO "$REPO_RAW/docker-compose.prod.yml" curl -fsSLO "$REPO_RAW/otel-collector-config.yaml" VERSION="$(resolve_version)" say "→ version: $VERSION$([ "$VERSION" = edge ] && printf ' (no release resolved — tracking main)')" HOST_IP="$(detect_host_ip)" if [ -n "$HOST_IP" ]; then AGENT_URL="http://$HOST_IP:8090" say "→ agent connect URL: $AGENT_URL (auto-detected — reachable from local clusters)" else AGENT_URL="" say "→ could not auto-detect a LAN IP; set RP_AGENT_CONTROLPLANE_URL in .env before connecting a cluster" fi set_kv() { # set_kv KEY VALUE FILE — replace or append KEY=VALUE if grep -q "^$1=" "$3"; then sed -i.bak "s|^$1=.*|$1=$2|" "$3" && rm -f "$3.bak" else printf '%s=%s\n' "$1" "$2" >> "$3"; fi } if [ -f .env ]; then say "→ keeping existing .env (secrets), setting RP_VERSION=$VERSION" set_kv RP_VERSION "$VERSION" .env # only fill the agent URL if the user hasn't set one (respect manual/remote setups) if [ -n "$AGENT_URL" ] && ! grep -qE '^RP_AGENT_CONTROLPLANE_URL=.+' .env; then set_kv RP_AGENT_CONTROLPLANE_URL "$AGENT_URL" .env fi else say "→ generating .env (session secret + datastore passwords)" curl -fsSL "$REPO_RAW/.env.example" -o .env.tmp sed \ -e "s|^RP_SESSION_SECRET=.*|RP_SESSION_SECRET=$(rand_hex)|" \ -e "s|^CLICKHOUSE_PASSWORD=.*|CLICKHOUSE_PASSWORD=$(rand_hex)|" \ -e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(rand_hex)|" \ -e "s|^RP_VERSION=.*|RP_VERSION=$VERSION|" \ -e "s|^RP_AGENT_CONTROLPLANE_URL=.*|RP_AGENT_CONTROLPLANE_URL=$AGENT_URL|" \ .env.tmp > .env rm -f .env.tmp fi say "→ pulling images ($VERSION)" $COMPOSE --env-file .env -f docker-compose.prod.yml pull say "→ starting the platform" $COMPOSE --env-file .env -f docker-compose.prod.yml up -d echo say "rocketplaneIO is starting." echo echo " UI http://localhost:4173 (create the owner account here)" echo " control plane http://localhost:8090 (your clusters connect here)" echo echo "Next: open the UI and hit 'Connect cluster' — the copy-paste command already" echo "points your cluster at the right address$([ -n "$HOST_IP" ] && printf ' (%s)' "$AGENT_URL")." echo if [ -z "$HOST_IP" ]; then echo " note: no LAN IP detected — set RP_AGENT_CONTROLPLANE_URL in $DIR/.env to a" echo " URL your cluster can reach (e.g. http://:8090) and re-run, else the" echo " agent can't dial back. A remote/prod cluster needs a routable/public URL." echo fi