239 lines
5.7 KiB
Bash
Executable File
239 lines
5.7 KiB
Bash
Executable File
#!/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}"
|
|
INSTALLED_CLIENT_ENV="${TELEBIT_INSTALLED_CLIENT_ENV:-$HOME/.config/telebit/client.env}"
|
|
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 <slug>
|
|
telebitctl release <slug>
|
|
telebitctl auth refresh [slug]
|
|
telebitctl up <slug> <local-port>
|
|
|
|
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'
|
|
}
|
|
|
|
write_client_env() {
|
|
slug="$1"
|
|
shared_key="$2"
|
|
port="${3:-$(current_port || true)}"
|
|
install="${4:-false}"
|
|
port="${port:-80}"
|
|
|
|
cat > "$CLIENT_ENV" <<EOF
|
|
TUNNEL_RELAY_URL=wss://$TUNNEL_DOMAIN/ws
|
|
AUTH_URL=https://$TUNNEL_DOMAIN/api
|
|
ACME_HTTP_01_RELAY_URL=https://$TUNNEL_DOMAIN/api/acme-relay
|
|
ACME_RELAY_URL=https://$TUNNEL_DOMAIN/api/acme-relay
|
|
ACME_AGREE=true
|
|
|
|
VENDOR_ID=$VENDOR_ID
|
|
SECRET=$shared_key
|
|
|
|
LOCALS=https:$slug.$DOMAIN:$port
|
|
EOF
|
|
|
|
if [ "$install" = "true" ]; then
|
|
mkdir -p "$(dirname "$INSTALLED_CLIENT_ENV")"
|
|
cp "$CLIENT_ENV" "$INSTALLED_CLIENT_ENV"
|
|
printf 'Updated %s and %s\n' "$CLIENT_ENV" "$INSTALLED_CLIENT_ENV"
|
|
else
|
|
printf 'Updated %s\n' "$CLIENT_ENV"
|
|
fi
|
|
}
|
|
|
|
client_key() {
|
|
[ -f "$CLIENT_ENV" ] || return 1
|
|
sed -n 's/^SECRET=//p' "$CLIENT_ENV" | sed -n '1p'
|
|
}
|
|
|
|
bind_slug() {
|
|
load_server_env
|
|
slug="${1:?bind requires a slug}"
|
|
token=$(admin_token)
|
|
response=$(curl -fsS -X POST "$MGMT_URL/devices" \
|
|
-H "Authorization: Bearer $token" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{ \"slug\": \"$slug\" }")
|
|
|
|
shared_key=$(printf '%s\n' "$response" | sed -n 's/.*"shared_key":"\([^"]*\)".*/\1/p')
|
|
[ -n "$shared_key" ] || die "Could not parse shared_key from response: $response"
|
|
|
|
write_client_env "$slug" "$shared_key" "$(current_port || true)" false
|
|
printf 'Bound %s.%s\n' "$slug" "$DOMAIN"
|
|
}
|
|
|
|
release_slug() {
|
|
load_server_env
|
|
slug="${1:?release requires a slug}"
|
|
token=$(admin_token)
|
|
delete_device "$slug" "$token" true
|
|
}
|
|
|
|
delete_device() {
|
|
slug="$1"
|
|
token="$2"
|
|
strict="${3:-false}"
|
|
tmp=$(mktemp)
|
|
code=$(curl -sS -o "$tmp" -w '%{http_code}' -X DELETE "$MGMT_URL/devices/$slug" \
|
|
-H "Authorization: Bearer $token")
|
|
body=$(cat "$tmp")
|
|
rm -f "$tmp"
|
|
|
|
case "$code:$body" in
|
|
200:*|500:*E_NOT_FOUND*)
|
|
printf 'Released %s.%s\n' "$slug" "$DOMAIN"
|
|
;;
|
|
*)
|
|
[ "$strict" = "true" ] || return 0
|
|
printf '%s\n' "$body" >&2
|
|
die "Release failed with HTTP $code"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
refresh_auth() {
|
|
load_server_env
|
|
slug="${1:-$(current_slug || true)}"
|
|
slug="${slug:-test}"
|
|
token=$(admin_token)
|
|
delete_device "$slug" "$token" false >/dev/null 2>&1 || true
|
|
bind_slug "$slug"
|
|
}
|
|
|
|
up_client() {
|
|
load_server_env
|
|
need_bin "$TELEBIT_BIN"
|
|
slug="${1:?up requires a slug}"
|
|
port="${2:?up requires a local port}"
|
|
key="$(client_key || true)"
|
|
[ -n "$key" ] || die "No client key found. Run: ./telebitctl bind $slug"
|
|
write_client_env "$slug" "$key" "$port" true >/dev/null
|
|
unset SECRET DOMAIN TUNNEL_DOMAIN MGMT_URL POSTGRES_PASSWORD TELEBIT_PORT MGMT_PORT VENDOR_ID
|
|
cd "$HOME"
|
|
exec "$TELEBIT_BIN" --env "$INSTALLED_CLIENT_ENV"
|
|
}
|
|
|
|
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
|