Metadata-Version: 2.4
Name: frp-jump
Version: 0.2.0
Summary: P2P-with-relay-fallback ssh/http/tcp tunnels between Linux boxes, built on frp
Author: Alexander Degtyarev
Author-email: Alexander Degtyarev <a.degtyarev@struhe.com>
License-Expression: MIT
License-File: LICENSE
Requires-Dist: typer>=0.15
Requires-Dist: rich>=13.9
Requires-Dist: cryptography>=43.0
Requires-Dist: tomli-w>=1.1
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic-settings>=2.15.0
Requires-Dist: fastapi>=0.115 ; extra == 'server'
Requires-Dist: uvicorn[standard]>=0.32 ; extra == 'server'
Requires-Dist: jinja2>=3.1 ; extra == 'server'
Requires-Dist: python-multipart>=0.0.17 ; extra == 'server'
Requires-Dist: sqlmodel>=0.0.22 ; extra == 'server'
Requires-Python: >=3.12
Provides-Extra: server
Description-Content-Type: text/markdown

# 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](https://github.com/fatedier/frp) (frpc/frps),
  driven through an abstract `TunnelDriver`/`RelayDriver` interface — see
  [`docs/architecture.md`](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`](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`](https://pypi.org/project/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:

```sh
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):

```sh
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:

```sh
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`](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):

```sh
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:

```sh
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):

```sh
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`](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/`](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`](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

```sh
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`](CONTRIBUTING.md). Changes are tracked in
[`CHANGELOG.md`](CHANGELOG.md).

## License

[MIT](LICENSE)
