diff --git a/.gitignore b/.gitignore index e3403e9..33613d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env bin/ telebit-client.env +.telebit-clients/ diff --git a/README.md b/README.md index b42df44..e12e3c9 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ Create a new tunnel key for a subdomain: ./telebitctl bind test ``` -This creates `test.$DOMAIN` and writes `telebit-client.env`. +This creates `test.$DOMAIN` and writes `.telebit-clients/test.env`. Run the tunnel to a local service: @@ -127,10 +127,13 @@ That forwards: https://test.$DOMAIN -> localhost:80 ``` -`up` also installs the active client config to: +`up` uses a per-slug client config. If `.telebit-clients/test.env` already exists, it reuses that key and only updates the local port. If it does not exist, it binds `test` before starting the tunnel. -```text -~/.config/telebit/client.env +Because each slug has its own env file, multiple tunnels can run at the same time: + +```sh +telebitctl up app 3000 +telebitctl up api 8080 ``` Rotate the client key: @@ -147,7 +150,7 @@ Release a subdomain: ## Notes -- `.env`, `telebit-client.env`, and `bin/` are ignored by git. +- `.env`, `.telebit-clients/`, `telebit-client.env`, and `bin/` are ignored by git. - `SECRET` in `.env` is the management/relay master secret. Do not use it directly as a long-lived client key. - Client keys are one-time registration secrets. Use `bind` or `auth refresh` to create device-specific keys. - If `/ws` returns nginx `502`, nginx is probably still proxying to `https://127.0.0.1:8085`; it must use `http://127.0.0.1:8085`. diff --git a/telebitctl b/telebitctl index 1564bce..7aa35c9 100755 --- a/telebitctl +++ b/telebitctl @@ -4,7 +4,7 @@ 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}" +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}" @@ -69,14 +69,36 @@ current_port() { 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:-$(current_port || true)}" - install="${4:-false}" + port="${3:-}" + env_file="${4:-$(client_env_path "$slug")}" + port="${port:-$(env_port "$env_file" || current_port || true)}" port="${port:-80}" - cat > "$CLIENT_ENV" < "$env_file" </dev/null 2>&1 || true - bind_slug "$slug" + 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() { @@ -162,12 +195,18 @@ up_client() { 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 + 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 "$INSTALLED_CLIENT_ENV" + exec "$TELEBIT_BIN" --env "$env_file" } build_client() {