public-ingress · git:20260825.c3e752c · 2026-08-25 · sha256 fdbb9dcc912a6a11

public-ingress git:20260825.c3e752cB

Immutable. This exact content is served forever at /api/v1/blob/fdbb9dcc912a6a11.

---
name: public-ingress
description: Set up and manage ngrok-based public ingress for local assistants; do not use this in managed mode when platform callback routing is available
compatibility: "Designed for Vellum personal assistants"
metadata:
  emoji: "🌍"
  vellum:
    category: "system"
    display-name: "Public Ingress"
    activation-hints:
      - "Local assistant needs a public webhook or OAuth callback URL"
      - "ngrok tunnel setup for a non-managed assistant"
    avoid-when:
      - "Running in a platform-managed assistant with platform callback routing available"
---

You are setting up and managing a public ingress tunnel so that external services (Telegram webhooks, OAuth callbacks, etc.) can reach the local Vellum gateway. This skill uses ngrok to create a secure tunnel and persists the public URL as `ingress.publicBaseUrl`.

If managed platform callback routing is available, stop and do not continue with ngrok. In platform-managed deployments, Telegram/Twilio/OAuth callback routing should use the platform callback route flow instead of local public ingress.

## Overview

The Vellum gateway listens locally and needs a publicly reachable URL for:

- Telegram webhook delivery
- Google/Slack OAuth redirect callbacks
- Any other inbound webhook traffic

This skill installs ngrok, configures authentication, starts a tunnel, discovers the public URL, and saves it to the assistant's ingress config.

On Windows, use the PowerShell blocks instead of the POSIX ones.

## Step 0: Reject Managed Callback Environments

Check whether managed platform callback routing is available:

```bash
assistant platform status --json
```

If the result shows `isPlatform: true` and `available: true`, stop here. Tell the user that this assistant should use the platform callback route flow instead of ngrok, and do not install or start ngrok.

## Step 1: Check Current Ingress Status

First, check whether ingress is already configured:

```bash
assistant config get ingress.publicBaseUrl
assistant config get ingress.enabled
```

The local gateway URL is available as the `$INTERNAL_GATEWAY_BASE_URL` environment variable (defaults to `http://127.0.0.1:7830`).

The commands return:

- `ingress.publicBaseUrl` - currently configured public ingress URL (if any)
- `ingress.enabled` - whether ingress is enabled

If `publicBaseUrl` is already set and the tunnel is running (check via `curl -s http://127.0.0.1:4040/api/tunnels`), tell the user the current status and ask if they want to reconfigure or if this is sufficient.

On Windows, check the tunnel with:

```powershell
Invoke-RestMethod http://127.0.0.1:4040/api/tunnels
```

## Step 2: Install ngrok

Check if ngrok is installed:

```bash
ngrok version
```

If not installed, install it:

**macOS (Homebrew):**

```bash
brew install ngrok/ngrok/ngrok
```

**Windows (Microsoft Store via WinGet):**

```powershell
winget install ngrok -s msstore
```

**Linux (snap):**

```bash
sudo snap install ngrok
```

**Linux (apt - alternative):**

```bash
curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt update && sudo apt install ngrok
```

After installation, verify with `ngrok version`.

## Step 3: Authenticate ngrok

Check if ngrok already has an auth token configured:

```bash
ngrok config check
```

If not authenticated:

1. Tell the user: "You need an ngrok account to create tunnels. If you don't have one, sign up at https://dashboard.ngrok.com/signup - it's free."
2. Once they have an account, run `assistant credentials prompt` (via the bash tool) to securely collect their auth token. **Never ask the user to paste the token directly in chat.**

   ```bash
   assistant credentials prompt --service ngrok --field authtoken \
     --label "ngrok Auth Token" \
     --description "Get your auth token from https://dashboard.ngrok.com/get-started/your-authtoken" \
     --usage-description "ngrok authentication token for creating public tunnels"
   ```

3. Once the credential is stored, reveal it and apply it to ngrok:

   ```bash
   ngrok config add-authtoken "$(assistant credentials reveal --service ngrok --field authtoken)"
   ```

   On Windows:

   ```powershell
   $token = assistant credentials reveal --service ngrok --field authtoken
   ngrok config add-authtoken $token
   ```

   If no value is returned, re-run `assistant credentials prompt` and try again.

Verify authentication succeeded by checking `ngrok config check` again.

## Step 4: Start the Tunnel

Before starting, check for an existing ngrok process to avoid duplicates:

```bash
curl -s http://127.0.0.1:4040/api/tunnels 2>/dev/null
```

On Windows:

```powershell
Invoke-RestMethod http://127.0.0.1:4040/api/tunnels -ErrorAction SilentlyContinue
```

If a tunnel is already running, check whether it points to the correct local target. If so, skip to Step 5. If it points elsewhere, stop it first:

```bash
pkill -f ngrok || true
sleep 1
```

On Windows:

```powershell
Get-Process ngrok -ErrorAction SilentlyContinue | Stop-Process
Start-Sleep -Seconds 1
```

Start ngrok in the background tunneling to the local gateway URL:

```bash
nohup ngrok http "$INTERNAL_GATEWAY_BASE_URL" --log=stdout > /tmp/ngrok.log 2>&1 &
echo $! > /tmp/ngrok.pid
```

On Windows:

```powershell
$logPath = Join-Path $env:TEMP "ngrok.log"
$errorLogPath = Join-Path $env:TEMP "ngrok-error.log"
Start-Process ngrok -ArgumentList @("http", $env:INTERNAL_GATEWAY_BASE_URL, "--log=stdout") -RedirectStandardOutput $logPath -RedirectStandardError $errorLogPath -WindowStyle Hidden
```

Wait a few seconds for the tunnel to establish.

## Step 4b: Verify Port Alignment

Before discovering the public URL, verify that ngrok is forwarding to the same port the gateway is actually listening on. A mismatch here causes silent failures - webhooks appear to be delivered but never reach the gateway.

Query the ngrok tunnel's target port and the gateway's configured port, then compare them:

```bash
curl -s http://127.0.0.1:4040/api/tunnels | python3 -c "
import sys, json, re

data = json.load(sys.stdin)
tunnels = data.get('tunnels', [])
if not tunnels:
    print('ERROR: no active ngrok tunnel found')
    sys.exit(1)

addr = tunnels[0].get('config', {}).get('addr', '')
match = re.search(r':(\d+)$', addr)
if not match:
    print(f'ERROR: could not extract port from ngrok tunnel addr: {addr}')
    sys.exit(1)

print(match.group(1))
"
```

```bash
echo "$INTERNAL_GATEWAY_BASE_URL" | grep -oE '[0-9]+$'
```

On Windows, resolve both ports without using the POSIX snippets:

```powershell
$tunnel = (Invoke-RestMethod http://127.0.0.1:4040/api/tunnels).tunnels[0]
$tunnelPort = ([uri]$tunnel.config.addr).Port
$gatewayPort = ([uri]$env:INTERNAL_GATEWAY_BASE_URL).Port
@{ tunnelPort = $tunnelPort; gatewayPort = $gatewayPort }
```

Compare the two port numbers. If they differ, warn the user:

> **Port mismatch detected:** ngrok is forwarding to port **X** but the gateway is listening on port **Y**. Webhooks will not reach the gateway. Stop ngrok (Step 4), then re-run this skill to start ngrok on the correct port.

If the ports match, proceed silently to Step 5.

## Step 5: Discover the Public URL

Query the ngrok local API for the tunnel's public URL:

```bash
curl -s http://127.0.0.1:4040/api/tunnels | python3 -c "
import sys, json
data = json.load(sys.stdin)
tunnels = data.get('tunnels', [])
for t in tunnels:
    url = t.get('public_url', '')
    if url.startswith('https://'):
        print(url)
        sys.exit(0)
for t in tunnels:
    url = t.get('public_url', '')
    if url:
        print(url)
        sys.exit(0)
print('ERROR: no tunnel found')
sys.exit(1)
"
```

If no tunnel is found, check `/tmp/ngrok.log` for errors and report them to the user.

On Windows, discover the URL and inspect the logs with:

```powershell
$tunnel = (Invoke-RestMethod http://127.0.0.1:4040/api/tunnels).tunnels | Where-Object { $_.public_url -like "https://*" } | Select-Object -First 1
$tunnel.public_url
Get-Content (Join-Path $env:TEMP "ngrok.log") -ErrorAction SilentlyContinue
Get-Content (Join-Path $env:TEMP "ngrok-error.log") -ErrorAction SilentlyContinue
```

## Step 6: Persist the Ingress Setting

Save the discovered public URL and enable ingress:

```bash
assistant config set ingress.publicBaseUrl "<public-url>"
assistant config set ingress.enabled true
```

Verify it was saved:

```bash
assistant config get ingress.publicBaseUrl
assistant config get ingress.enabled
```

## Step 7: Report Completion

Summarize the setup:

- **Public URL:** `<the-url>` (this is your `ingress.publicBaseUrl`)
- **Local gateway target:** `$INTERNAL_GATEWAY_BASE_URL`
- **ngrok dashboard:** http://127.0.0.1:4040

Provide useful follow-up commands:

- **Check tunnel status:** `curl -s http://127.0.0.1:4040/api/tunnels | python3 -c "import sys,json; [print(t['public_url']) for t in json.load(sys.stdin)['tunnels']]"`
- **View ngrok logs:** `cat /tmp/ngrok.log`
- **Restart tunnel:** `pkill -f ngrok; sleep 1; nohup ngrok http "$INTERNAL_GATEWAY_BASE_URL" --log=stdout > /tmp/ngrok.log 2>&1 &`
- **Stop tunnel:** `pkill -f ngrok`
- **Rotate URL:** Stop and restart ngrok (free tier assigns a new URL each time; update `ingress.publicBaseUrl` afterward)

**Important:** On ngrok's free tier, the public URL changes every time the tunnel restarts. After restarting, re-run this skill or manually update `ingress.publicBaseUrl` and any registered webhooks (e.g., Telegram).

## Troubleshooting

### ngrok not installed

Run the install commands in Step 2. On macOS, make sure Homebrew is installed first (`brew --version`).

### Auth token invalid or expired

Sign in to https://dashboard.ngrok.com, copy a fresh token from the "Your Authtoken" page, and re-run Step 3.

### ngrok API (port 4040) not responding

The ngrok process may not be running. On macOS or Linux, check with `ps aux | grep ngrok`. On Windows, use `Get-Process ngrok -ErrorAction SilentlyContinue`. If it is not running, start it per Step 4. If it is running but port 4040 is unresponsive, check `/tmp/ngrok.log` (`$env:TEMP\ngrok.log` on Windows) for errors.

### Gateway not reachable on local target

Re-check the local gateway target with `echo $INTERNAL_GATEWAY_BASE_URL`. Run `curl -s "$INTERNAL_GATEWAY_BASE_URL/healthz"` to verify it is reachable. If the gateway is not running, start the assistant first.

On Windows, use `$env:INTERNAL_GATEWAY_BASE_URL` and `Invoke-RestMethod "$env:INTERNAL_GATEWAY_BASE_URL/healthz"`.

### "Too many connections" or tunnel limit errors

ngrok's free tier allows one tunnel at a time. Stop any other ngrok tunnels before starting a new one.

### ngrok port doesn't match gateway port

**Symptom:** Webhooks return connection refused or timeouts even though both ngrok and the gateway appear to be running.

**Cause:** ngrok is forwarding to a different port than the gateway is listening on. This can happen if the gateway port was changed after ngrok was started, or if ngrok was started manually with a hardcoded port.

**Fix:** Stop ngrok (Step 4), verify the gateway URL with `echo $INTERNAL_GATEWAY_BASE_URL`, then re-run this skill to start ngrok on the correct port.

### ngrok automatically restarts with wrong port

On macOS, if the ngrok process automatically re-spawns on the incorrect port, check for a launch agent at `~/Library/LaunchAgents/com.ngrok.tunnel.plist`. If it exists, remove or update it.