#!/bin/sh set -eu ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) SERVER_ENV="${TELEBIT_SERVER_ENV:-$ROOT_DIR/.env}" CLIENT_ENV="${TELEBIT_CLIENT_ENV:-$ROOT_DIR/telebit-client.env}" CLIENT_ENV_DIR="${TELEBIT_CLIENT_ENV_DIR:-$ROOT_DIR/.telebit-clients}" TELEBIT_BIN="${TELEBIT_BIN:-$ROOT_DIR/bin/telebit}" SIGNJWT_BIN="${SIGNJWT_BIN:-$ROOT_DIR/bin/signjwt}" usage() { cat <<'USAGE' Usage: telebitctl build [os] [arch] telebitctl bind telebitctl release telebitctl auth refresh [slug] telebitctl up Examples: ./telebitctl build ./telebitctl bind test ./telebitctl up test 80 ./telebitctl auth refresh test ./telebitctl release test USAGE } die() { printf '%s\n' "$*" >&2 exit 1 } load_server_env() { [ -f "$SERVER_ENV" ] || die "Missing $SERVER_ENV. Copy .env.example to .env and set DOMAIN, TUNNEL_DOMAIN, and SECRET." set -a # shellcheck disable=SC1090 . "$SERVER_ENV" set +a : "${DOMAIN:?DOMAIN is required in $SERVER_ENV}" : "${TUNNEL_DOMAIN:?TUNNEL_DOMAIN is required in $SERVER_ENV}" : "${SECRET:?SECRET is required in $SERVER_ENV}" VENDOR_ID="${VENDOR_ID:-renman-dev}" MGMT_URL="${MGMT_URL:-https://$TUNNEL_DOMAIN/api}" } need_bin() { [ -x "$1" ] || die "Missing executable $1. Run: ./telebitctl build" } admin_token() { need_bin "$SIGNJWT_BIN" "$SIGNJWT_BIN" \ --expires-in 5m \ --vendor-id "$VENDOR_ID" \ --secret "$SECRET" \ --machine-ppid "$SECRET" | awk '/^eyJ/ {print; exit}' } current_slug() { [ -f "$CLIENT_ENV" ] || return 1 sed -n "s#^LOCALS=https:\\([^:]*\\):.*#\\1#p" "$CLIENT_ENV" | sed "s#\\.$DOMAIN\$##" | sed -n '1p' } current_port() { [ -f "$CLIENT_ENV" ] || return 1 sed -n 's#^LOCALS=https:[^:]*:\([0-9][0-9]*\).*#\1#p' "$CLIENT_ENV" | sed -n '1p' } safe_slug() { case "$1" in ""|*/*|*..*|.*|*-|-*|*[^A-Za-z0-9-]*) die "Invalid slug: $1" ;; esac } client_env_path() { slug="$1" safe_slug "$slug" printf '%s/%s.env\n' "$CLIENT_ENV_DIR" "$slug" } env_port() { env_file="$1" [ -f "$env_file" ] || return 1 sed -n 's#^LOCALS=https:[^:]*:\([0-9][0-9]*\).*#\1#p' "$env_file" | sed -n '1p' } write_client_env() { slug="$1" shared_key="$2" port="${3:-}" env_file="${4:-$(client_env_path "$slug")}" port="${port:-$(env_port "$env_file" || current_port || true)}" port="${port:-80}" mkdir -p "$(dirname "$env_file")" cat > "$env_file" <&2 die "Release failed with HTTP $code" ;; esac } refresh_auth() { load_server_env slug="${1:-$(current_slug || true)}" slug="${slug:-test}" env_file=$(client_env_path "$slug") token=$(admin_token) delete_device "$slug" "$token" false >/dev/null 2>&1 || true shared_key=$(create_device "$slug" "$token") write_client_env "$slug" "$shared_key" "$(env_port "$env_file" || current_port || true)" "$env_file" printf 'Bound %s.%s\n' "$slug" "$DOMAIN" } up_client() { load_server_env need_bin "$TELEBIT_BIN" slug="${1:?up requires a slug}" port="${2:?up requires a local port}" env_file=$(client_env_path "$slug") key="$(client_key "$env_file" || true)" if [ -z "$key" ]; then token=$(admin_token) key=$(create_device "$slug" "$token") fi write_client_env "$slug" "$key" "$port" "$env_file" >/dev/null unset SECRET DOMAIN TUNNEL_DOMAIN MGMT_URL POSTGRES_PASSWORD TELEBIT_PORT MGMT_PORT VENDOR_ID cd "$HOME" exec "$TELEBIT_BIN" --env "$env_file" } build_client() { os="${1:-}" arch="${2:-}" if [ -z "$os" ]; then case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; *) die "Unsupported OS. Pass os and arch explicitly." ;; esac fi if [ -z "$arch" ]; then case "$(uname -m)" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) die "Unsupported arch. Pass os and arch explicitly." ;; esac fi mkdir -p "$ROOT_DIR/bin" docker build \ -f "$ROOT_DIR/Dockerfile.client" \ --build-arg "TELEBIT_REF=${TELEBIT_REF:-master}" \ --build-arg "TARGETOS=$os" \ --build-arg "TARGETARCH=$arch" \ --output "type=local,dest=$ROOT_DIR/bin" \ "$ROOT_DIR" chmod +x "$ROOT_DIR/bin/telebit" "$ROOT_DIR/bin/signjwt" printf 'Built bin/telebit and bin/signjwt for %s/%s\n' "$os" "$arch" } cmd="${1:-}" case "$cmd" in build) shift build_client "$@" ;; bind) shift bind_slug "$@" ;; release) shift release_slug "$@" ;; auth) shift sub="${1:-}" [ "$sub" = "refresh" ] || die "Unknown auth command: ${sub:-}. Expected: auth refresh [slug]" shift || true refresh_auth "$@" ;; up) shift up_client "$@" ;; -h|--help|help|"") usage ;; *) usage exit 1 ;; esac