Skip to main content

frp-jump

Connect Linux boxes (including Wiren Board controllers) to each other over the internet using the SSH/HTTP/TCP clients you already have — no manual port juggling, and no caring whether the link ended up peer-to-peer or relayed through your server.

  • Tunneling is frp (frpc/frps), driven through an abstract TunnelDriver/RelayDriver interface — see docs/architecture.md.
  • P2P with relay fallback is frp's own xtcp + fallbackTo feature: a device pair first tries a direct (hole-punched) connection, and transparently falls back to relaying through your server if that doesn't complete within a timeout. Not something this project implements itself.
  • Security: a private CA (run by your server) issues an mTLS cert to every enrolled device, so nothing unenrolled can reach the relay at all; a per-pair secret means even enrolled devices can't reach each other's services without an explicit grant; the WebUI is admin-only, behind passwordless magic-link auth (no passwords, no SMTP — links are generated by the server and you hand-deliver them yourself). Regular users never touch the WebUI at all — after their first device is enrolled, they self-serve entirely from the CLI (adding more of their own devices, connecting to each other) scoped to devices they own. Revoking a device or grant takes effect on its next sync, not instantly — see docs/architecture.md for what that does and doesn't cover.
  • UX goal: after setup, ssh <name> and http://127.0.0.1:<port> just work with stock clients, whether the path underneath is p2p or relayed.

Installing the CLI

Published on PyPI as frp-jump, Python 3.12+ required (already present on any recent Debian/Ubuntu, including Wiren Board controllers). It splits into two lean pieces sharing one package, so a device install doesn't pull in the server's dependencies:

python3 -m venv .venv    # needs the venv module: on Debian/Ubuntu that's
                          # a separate package, `apt install python3-venv`

# on the server box:
.venv/bin/pip install 'frp-jump[server]'   # pulls in fastapi/uvicorn/sqlmodel too
.venv/bin/frp-jump-server ...

# on every device you want to connect (including headless/IoT ones):
.venv/bin/pip install frp-jump             # lean: no server-only deps
.venv/bin/frp-jump-client ...

Put .venv/bin on PATH, or call the binaries by their full path.

Every command has full --help text with runnable examples — start there if anything below is unclear (frp-jump-client <command> --help).

Quick start

On the server (a box with a public IP/domain):

export FRP_JUMP_RELAY_PUBLIC_ADDR=tunnel.example.com   # or a bare IP
frp-jump-server init --admin-email you@example.com
# -> prints an admin login link: open it in a browser to sign in
frp-jump-server run

Lost that link, or need to sign in from somewhere else? It's not your only way in — mint a fresh one any time:

frp-jump-server login-link you@example.com

(Under the systemd deployment below, login-link needs the same FRP_JUMP_* env vars as run, which it won't pick up on its own outside of systemd's EnvironmentFile — see packaging/scripts/frp-jump-login-link for a copy-pasteable wrapper.)

In the WebUI: Add device for your own first device, or a friend's (set their email as the owner — every device after that one is theirs to add via their own CLI, no further admin action). It prints a one-time frp-jump-client enroll <url> <token> command; run that on the device itself (not on the server):

frp-jump-client enroll https://tunnel.example.com <token-from-webui>
frp-jump-client run          # foreground; wrap with systemd for real use

From there, everything is self-service — no more admin action needed for that person, ever, even to add a tenth device or reconfigure who talks to whom:

frp-jump-client add-device                      # mint a token to chain-enroll
                                                 # one more of your own devices
frp-jump-client list                            # devices you own
frp-jump-client connect wb01 22                 # wire yourself up to port 22
                                                 # on your device "wb01"
frp-jump-client status                          # see what's exposed/consumed,
                                                 # and local addresses once synced
frp-jump-client disconnect wb01                 # tear it back down
frp-jump-client delete-device old-laptop        # gone for good, frees the name

Once synced (each side's agent polls every agent_poll_interval_seconds, default 30s):

ssh wb01                     # just works -- `connect`'s local profile name
                              # doubles as the ssh_config Host alias

Configuration

Everything configurable lives in one place: src/frp_jump/common/settings.py — set via FRP_JUMP_<FIELD> environment variables, or a TOML file ($FRP_JUMP_CONFIG_FILE, else the first of ./frp-jump.toml, ~/.config/frp-jump/config.toml, /etc/frp-jump/config.toml that exists). Env vars win over the file. Nothing else in the codebase hardcodes a port, TTL, or version pin.

The only setting with no sane default is relay_public_addr — the address other devices dial to reach your relay; frp-jump-server init/run refuse to start without it.

systemd

See packaging/systemd/. The client unit's comments explain a real gotcha: a device that consumes an SSH grant needs the agent running as the actual human (so it can maintain their real ~/.ssh/config) — a --user unit, not a system one, unless you point FRP_JUMP_SSH_CONFIG_PATH at that user's config explicitly. A device that only exposes services (e.g. a Wiren Board controller) is fine as a system service.

On the server, also install packaging/scripts/frp-jump-login-link to /usr/local/bin/ (chmod 755, root:root) — a one-line wrapper around server login-link that loads the systemd unit's env file for you, so minting a fresh admin login link doesn't mean hand-assembling env $(sudo cat /etc/frp-jump/server.env | xargs) sudo -u frp-jump ... every time.

Development

uv sync
uv run pytest tests/unit -q        # fast, no network
uv run ruff check .
uv run pytest tests/integration -m integration -q   # downloads real frp binaries; loopback only

The integration test proves the whole chain works over loopback (real frp binaries, real mTLS, real xtcp-timeout-then-stcp-fallback), but it can't prove real NAT hole-punching across two separate networks — that's the one thing to manually check on your own machines after this lands.

Layout

src/frp_jump/
  common/     PKI (private CA), opaque tokens, settings, DB models
  driver/     TunnelDriver/RelayDriver abstraction; driver/frp/ = the frp
              implementation (config rendering, binary download+checksum,
              process supervision)
  server/     control-plane: registry (CRUD), auth (magic links), the
              agent-facing API, the WebUI, bootstrap (`server init`)
  agent/      runs on every device: enroll, the sync loop, local profiles,
              ssh_config management
  cli/        `frp-jump-server ...` / `frp-jump-client ...`

Contributing

See CONTRIBUTING.md. Changes are tracked in CHANGELOG.md.

License

MIT

Release files for frp-jump 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for frp-jump 0.2.0
File Size Uploaded
frp_jump-0.2.0.tar.gz 45.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for frp-jump 0.2.0
File Interpreter ABI Platform
frp_jump-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 104.0 kB

Release files / frp_jump-0.2.0.tar.gz

Download URL frp_jump-0.2.0.tar.gz
Size 45.3 kB
Tags Source
SHA-256 checksum
How to use checksums
de0127ef1854e01bd82a476f035f5956ecb6255d9c530a9df2a1f2f0c3f68cee
BLAKE2b-256 checksum
How to use checksums
90b2d01f579d3cfc2f68c4c698b28ea3a000b521713da9d16baf0a5afacdd08e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / frp_jump-0.2.0-py3-none-any.whl

Download URL frp_jump-0.2.0-py3-none-any.whl
Size 58.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cb58d4f7f785756a8ef0f884fa4039ca03467d8820cf3b99b647026409d161f2
BLAKE2b-256 checksum
How to use checksums
f677844f9a387fe70920b952bf8446ee9494f0b7a490deae502d24159c4e799c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page