Foundry Tunnel Manager
Share your Foundry VTT world without port forwarding.
One binary. Several tunnel providers. Three ways in.
TUI, web dashboard, or native desktop — same tunnels, same config.
Install • Quick start • Interfaces • Providers • Releases
You want players in your Foundry world. Your router, ISP, or CGNAT has other plans. Port forwarding is a pain; random free tunnels mean memorizing flags and babysitting processes.
ftm starts and stops tunnels for you. Pick a provider, point it at Foundry’s port, copy the public URL. When a tunnel dies or is about to expire, you get a notification — not a silent failure mid-session.
It is a single static binary. Providers are downloaded on demand. Config lives under your user directory. The web UI is embedded, so the same binary drives the TUI, the dashboard, and the desktop shell.
![]() |
![]() |
![]() |
|---|---|---|
| TUI — start/stop, logs, copy URL | Web — dashboard at :40500 |
Desktop — same UI in a native window |
TUI — ftm
Full-screen terminal UI. Navigate connections, open logs, copy the public URL, jump to the dashboard.
| Key | Action |
|---|---|
↑ / ↓ |
Navigate |
enter / t |
Start / stop |
l |
Logs |
c |
Copy URL |
w |
Open web dashboard |
o |
Open config |
a / e / d |
Add / edit / delete |
s |
Settings |
? |
Full shortcut list |
esc |
Back to the connection list |
q |
Back, or quit from the list |
Web — always on when ftm runs
Dashboard at http://localhost:40500 (port auto-detects if busy). Themes, live status, manage tunnels from the browser.
ftm --web # dashboard + open browser
ftm --server # dashboard only
ftm --port 8080 # force a portDesktop — release assets ftm-desktop-*
Wails shell around the same embedded UI. Prefer this if you want a window without keeping a terminal open.
Homebrew (macOS and Linux)
# CLI + embedded web dashboard (TUI + web UI in one binary)
brew install sthbryan/tap/ftm-cli
# Native desktop app — Wails shell around the same UI (macOS only)
brew install --cask sthbryan/tap/ftmThe cask runs an ad-hoc codesign and strips quarantine on install, so the first launch works without manual xattr cleanup. Formula installs the CLI under the canonical ftm name regardless of platform.
brew update && brew upgrade ftm-cli
brew update && brew upgrade --cask ftm
brew uninstall --cask --zap ftm # removes config + caches toomacOS / Linux (install script)
curl -fsSL https://raw.githubusercontent.com/sthbryan/ftm/main/install.sh | bashDetects OS and arch, installs the CLI to ~/.local/bin (override with INSTALL_DIR), and clears macOS quarantine when needed.
Linux desktop app (AppImage)
chmod +x ftm-desktop-linux-x86_64.AppImage
./ftm-desktop-linux-x86_64.AppImageOne file, no install step, icon and menu entry included. Runs on any distro with glibc 2.35 or newer (Ubuntu 22.04+, Debian 12+, Fedora 36+, recent Arch).
WebKitGTK 4.1 is bundled into the image (GTK, WebKit, and the helper binaries such as WebKitNetworkProcess/WebKitWebProcess). WebKitGTK release builds resolve those helpers from a path baked into libwebkit2gtk at compile time, so the packaging script (scripts/relocate-webkit.py) rewrites that path to a short fixed location and the AppRun hook symlinks it back into the AppDir. That's why the AppImage is genuinely portable rather than only working on the build host.
| Homebrew | brew install sthbryan/tap/ftm-cli (CLI) or --cask for desktop |
| Install script | The one-liner above — macOS and Linux |
| AppImage | Linux desktop app — one file, no install |
| Download | Releases — CLI + desktop apps |
| From source | make install — needs Go 1.22+ and Bun |
Building from source installs to $(go env GOPATH)/bin. Point it somewhere else with BINDIR:
make install BINDIR=/usr/local/bin| Platform | File |
|---|---|
| Windows | ftm-windows-x64.exe |
| Linux x64 | ftm-linux-x64 |
| Linux ARM64 | ftm-linux-arm64 |
| macOS Intel | ftm-macos-x64 |
| macOS Apple Silicon | ftm-macos-arm64 |
chmod +x ftm-*
# e.g. ~/.local/bin or /usr/local/bin
mv ftm-macos-arm64 ~/.local/bin/ftmOn macOS, if Gatekeeper blocks the binary:
xattr -d com.apple.quarantine ~/.local/bin/ftm| Platform | File |
|---|---|
| Windows | ftm-desktop-windows.exe |
| Linux | ftm-desktop-linux |
| macOS | ftm-desktop-macos.app.zip |
chmod +x ftm-desktop-linux
# macOS: unzip, then if needed:
xattr -d com.apple.quarantine ftm-desktop-macos.app| Flag | Effect |
|---|---|
| (none) | TUI + web server |
--web |
Web only, open browser |
--server |
Web only, no browser |
--port N |
Fix the web port |
--version |
Print version |
--update |
Update to the latest release |
--check |
Check for updates only |
--uninstall |
Remove the install |
ftm # TUI; dashboard at http://localhost:40500
# In the TUI: a → add a tunnel → pick a provider → Foundry port (usually 30000)
# s → start • c → copy public URL • share with playersWeb-only session:
ftm --web| Provider | Notes |
|---|---|
| Cloudflared | Cloudflare Tunnel — solid default; auto-install |
| Pinggy | Quick public URLs |
| Tunnelmole | Lightweight; auto-install where available |
| Bore | Self-hostable / alternative relay |
| localhost.run | SSH-based, no extra binary |
| Serveo | SSH-based, no extra binary |
Missing binaries are downloaded when you start a tunnel that needs them. SSH providers only need a working ssh client.
Go 1.25+ and Bun. Desktop uses Wails v3 (CGO + platform webview; no separate wails CLI required for builds).
make build-full # web assets + bin/ftm with the UI embedded
make build # Go only (uses existing embedded static files)
make web # Svelte build → internal/web/static (+ desktop dist)
make dev # web assets, then go run — use this after a frontend change
make run # go run only, against the assets already embedded
make test # go test ./...
make test-race # go test -race ./...
make vet # go vet ./...
make fmt # gofmt -w, then verify nothing is left unformatted
make install # web + binary → $BINDIR (default $(go env GOPATH)/bin)
make desktop # Wails v3 desktop shell → desktop/build/bin/ftm-desktop
make help # full target listHot reload against a running ftm web server:
cd web-svelte && bun run devmacOS TUI notifications: install alerter (brew install alerter) for native alerts. Desktop/GUI paths use the platform APIs.
make version # interactive menu (patch / minor / major / custom)
make version ARGS="patch" # non-interactive bump
make version ARGS="0.11.0 --dry-run"make version (alias: make release) updates every place that holds the version string (internal/version/version.go, web-svelte/package.json, desktop/wails.json — both version and productVersion), commits with chore(release): vX.Y.Z, creates an annotated tag, and pushes branch + tag to origin. Flags: --dry-run, --no-push, --allow-dirty, --yes/-y. Run make version ARGS="--help" for the full reference.
- One process, three surfaces — TUI, embedded web, and Wails share the same tunnel manager and config.
- Providers as plugins — each tunnel backend implements a small interface; installers download only what you use.
- Live status — the dashboard tracks process state over WebSockets so start/stop/errors show up without refresh.
- Local by default — the web UI is meant for localhost; treat exposing it beyond your machine as out of scope.
MIT License • © 2026 Bryan Villafuerte


