Skip to main content

Lightweight FRP deployment and maintenance CLI with structured proxy management, audit logging, and MCP support for LLM-assisted operations.

Project description

frpdeck

frpdeck is a lightweight Python 3.11+ CLI for managing FRP instances from structured source files. It focuses on practical single-host operations: initialize instance directories, validate configuration, render generated artifacts, sync managed runtime files, apply changes locally, inspect state, and maintain structured proxy definitions without introducing a larger control plane.

It is also MCP-friendly. frpdeck includes a local stdio MCP thin wrapper so an LLM can assist with structured proxy maintenance against one bound instance directory at a time.

Highlights

  • Lightweight FRP deployment and maintenance workflows for client and server instances.
  • Structured proxy management backed by proxies.yaml, with import, typed add, update, remove, and preview support.
  • Stable JSON outputs for automation and scripting.
  • Append-only audit logging and revision snapshots for write operations.
  • Local stdio MCP support for LLM-assisted proxy maintenance.

Installation

Install from PyPI:

pip install frpdeck
frpdeck --help

Install from source when you want a local checkout:

python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install .

For development:

python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'

Documentation

Key design notes now live under docs/:

Features

  • init creates a new client or server instance directory.
  • validate checks source config only: schema, placeholder values, token sources, path resolution, and simple proxy conflicts.
  • render generates FRP TOML, proxy includes, and systemd units under rendered/ only.
  • sync mirrors managed files from rendered/ into runtime/config only.
  • reload calls frpc reload -c ... for client instances using the current runtime/config.
  • apply validates, renders, syncs runtime files, installs binaries if needed, installs the systemd unit, and restarts the service.
  • restart and status operate on the configured systemd service.
  • check-update and upgrade support GitHub latest releases and offline archives.
  • doctor checks Linux/systemd availability, instance files, and basic write permissions.
  • python -m frpdeck.mcp.server starts a local stdio MCP server that exposes proxy-management tools and read-only status resources.

All mutating commands support --sudo. When a non-root user passes --sudo, frpdeck re-execs the full command via sudo before loading instance config or touching managed files. Without --sudo, mutating commands fail early with a retry hint when required paths are not readable or writable by the current user.

Current scope

frpdeck is a focused operations tool, not a full FRP control platform. It currently centers on structured instance management, proxy maintenance, local apply workflows, auditing, and MCP-assisted maintenance. HTTP control planes, remote auth layers, and web dashboards are intentionally out of scope for now.

Non-goals

  • Remote HTTP transport for MCP
  • Authentication or authorization for remote MCP access
  • Web dashboard or visualization service
  • Remote centralized control
  • Interactive TOML editing

Quick start

Running frpdeck with no arguments now shows the built-in command help, including common entry points such as init, apply, proxy, status, and python -m frpdeck.mcp.server.

Initialize a client instance:

frpdeck init client my-client

The generated client scaffold includes a sample HTTP proxy in proxies.yaml so the route fields are visible in the initial config shape.

Edit the generated configuration and secret material:

${EDITOR:-vi} ./my-client/node.yaml
${EDITOR:-vi} ./my-client/proxies.yaml
mkdir -p ./my-client/secrets
printf 'replace-me\n' > ./my-client/secrets/token.txt

instance_name is the logical identity stored in node.yaml. It may differ from the directory name; status, service naming defaults, and audit data use instance_name, not instance_dir.name.

Validate the source configuration:

frpdeck validate --instance ./my-client

Render generated files:

frpdeck render --instance ./my-client

Mirror the rendered snapshot into runtime config without restarting anything:

frpdeck sync --instance ./my-client --sudo

Apply an instance to the configured runtime paths:

frpdeck apply --instance ./my-client --sudo

For offline install or replacement from a local FRP archive:

frpdeck apply --instance ./my-client --archive /path/to/frp_0.65.0_linux_amd64.tar.gz --sudo

Inspect runtime state:

frpdeck status --instance ./my-client

Apply emits stage-by-stage progress in text mode so it is clear when validation, rendering, binary download/install, runtime sync, systemd install, and restart are happening.

Command semantics

  • validate reads node.yaml and proxies.yaml, validates them, and exits. It does not write rendered/ or runtime/config.
  • render writes the full generated snapshot into rendered/. It does not touch runtime/config, reload FRP, or restart systemd.
  • sync mirrors the managed rendered snapshot into runtime/config. It does not run validation, rendering, reload, or restart logic.
  • reload asks frpc to reload using the current runtime/config. If runtime config is missing, run sync or apply first.
  • apply is the full operational path: validate, render, sync, install/upgrade the managed binary if needed, install the systemd unit, and restart the service.
  • proxy preview is a temporary client-side preview of proxy include output. It does not modify rendered/. Top-level render writes the full instance snapshot into rendered/.

Write commands accept --sudo and re-exec the entire command via sudo when needed. This applies to instance-scaffold, render/sync/apply/reload/restart/upgrade/uninstall workflows, structured proxy mutations, and MCP wrapper install or uninstall. You can still run sudo frpdeck ... manually, but frpdeck ... --sudo is the preferred retry path because the command itself can fail early before doing partial work.

Uninstall installed artifacts while keeping source configuration:

frpdeck uninstall --instance ./my-client

Delete the instance directory as well:

frpdeck uninstall --instance ./my-client --purge

Typical workflows

Client instance

  1. Run frpdeck init client your-client.
  2. Replace PLEASE_FILL_SERVER_ADDR and domain placeholders in node.yaml and proxies.yaml.
  3. Create secrets/token.txt with the real token.
  4. Run frpdeck validate --instance ./your-client.
  5. Run frpdeck render --instance ./your-client.
  6. Run frpdeck apply --instance ./your-client --sudo.
  7. Run frpdeck status --instance ./your-client.

For offline binary management, apply --archive, upgrade --archive, and binary.local_archive are all supported.

Server instance

  1. Run frpdeck init server your-server.
  2. Create secrets/token.txt.
  3. If you want FRP vhost routing, explicitly set server.vhost_http_port and/or server.vhost_https_port in node.yaml.
  4. If you want subdomain-based routing, also set server.subdomain_host.
  5. Run frpdeck validate --instance ./your-server.
  6. Run frpdeck render --instance ./your-server.
  7. Run frpdeck apply --instance ./your-server --sudo.

Server vhost modes

By default, a new server instance does not set server.vhost_http_port, server.vhost_https_port, or server.subdomain_host.

  • With the default scaffold, rendered frps.toml does not bind 80 or 443 and does not enable subdomain host handling.
  • When you explicitly set server.vhost_http_port or server.vhost_https_port, frpdeck renders those values into frps.toml.
  • When you explicitly set server.subdomain_host, frpdeck renders subDomainHost.

Example server config with vhost enabled:

server:
  bind_addr: 0.0.0.0
  bind_port: 7000
  vhost_http_port: 80
  vhost_https_port: 443
  subdomain_host: frp.example.com
  log:
    to: runtime/logs/frps.log
    level: info
    max_days: 7
    disable_print_color: true
  auth:
    method: token
    token_file: secrets/token.txt

HTTP/HTTPS proxies

Client proxy definitions for http and https stay in proxies.yaml with the existing snake_case source config style.

HTTP with custom_domains:

proxies:
  - name: app_http
    type: http
    local_ip: 127.0.0.1
    local_port: 8080
    custom_domains:
      - app.example.com

HTTPS with custom_domains:

proxies:
  - name: app_https
    type: https
    local_ip: 127.0.0.1
    local_port: 8443
    custom_domains:
      - secure.example.com

HTTP with subdomain:

proxies:
  - name: app_subdomain
    type: http
    local_ip: 127.0.0.1
    local_port: 8080
    subdomain: app

custom_domains and subdomain may be set together. That is supported by the implementation, although in practice it is usually clearer to choose the one that matches the deployment pattern.

http and https proxies must define at least one of:

  • custom_domains
  • subdomain

Blank strings are rejected for custom_domains, subdomain, and server.subdomain_host.

Proxy CLI shortcuts

Import one proxy mapping from a YAML file:

frpdeck proxy import ./app-http.yaml --instance ./my-client

Update one existing proxy from a YAML patch file:

frpdeck proxy update ssh ./ssh-patch.yaml --instance ./my-client

Add an HTTP proxy with one or more custom domains:

frpdeck proxy add http \
  --instance ./my-client \
  --name app-http \
  --local-port 8080 \
  --custom-domain app.example.com \
  --custom-domain www.example.com

Add an HTTPS proxy:

frpdeck proxy add https \
  --instance ./my-client \
  --name app-https \
  --local-port 8443 \
  --custom-domain secure.example.com

Add an HTTP proxy using a subdomain:

frpdeck proxy add http \
  --instance ./my-client \
  --name app-subdomain \
  --local-port 8080 \
  --subdomain app

--custom-domain is repeatable, and it can be combined with --subdomain when you want both selectors on the same proxy.

MCP

frpdeck ships with a local stdio MCP thin wrapper over structured proxy CRUD, import, and preview tools plus read-only status resources. It is designed to bind to one instance directory at a time and is best used through a generated wrapper script.

Recommended workflow: generate a bound wrapper script with frpdeck mcp install-stdio-wrapper and point your MCP client at that script. Prefer the generated wrapper over writing your own unless you have a specific reason to customize startup behavior. The wrapper binds to your chosen instance directory and, by default, embeds the Python interpreter running frpdeck when the script is created. Use --python /path/to/python if you need to override that explicitly.

In practice, wrapper scripts are most commonly generated for client instances, because proxy configuration is usually managed on the client side. That is a usage pattern rather than a hard restriction: the MCP wrapper is tied to an instance directory, not to a separate client-only mode in the documentation.

Recommended MCP setup

On the FRP machine, change into your instance directory and generate the wrapper:

cd /path/to/your-instance
frpdeck mcp install-stdio-wrapper

This is equivalent to:

frpdeck mcp install-stdio-wrapper --instance /path/to/your-instance

The command writes /path/to/your-instance/start-mcp-stdio.sh, binds that script to the resolved absolute instance path, and embeds the Python interpreter that is running frpdeck at generation time. Replace the example path with your own instance directory.

If you need to start the server manually without the wrapper, you can still use:

python -m frpdeck.mcp.server

For a bound one-instance server, the direct form is:

python -m frpdeck.mcp.server --instance-dir /path/to/your-instance

Before configuring Claude Code, manually verify the SSH command from the Claude Code machine. Replace the host name and path with your own SSH destination and instance directory:

ssh your-ssh-host /path/to/your-instance/start-mcp-stdio.sh

That command should normally stay attached and wait for stdin/stdout traffic because the MCP stdio server is waiting for client messages. If it exits immediately or prints an error, fix the remote Python environment, instance path, or SSH setup first.

Once the manual SSH command works, add the MCP entry in Claude Code:

claude mcp add --scope user --transport stdio frpdeck -- \
  ssh your-ssh-host /path/to/your-instance/start-mcp-stdio.sh

Current MCP scope is intentionally small:

  • Local stdio MCP server only.
  • Structured proxy CRUD/import/preview only; instance-level validate/sync/apply stay in the CLI.
  • No HTTP transport.
  • No remote auth layer.
  • No web UI.

Audit and safety notes

Write operations append audit records under state/audit/audit.jsonl, and proxy mutations also create revision snapshots under state/revisions/. This is intended to make changes traceable and manually recoverable without turning the tool into a full control plane.

SSH and BatchMode

BatchMode yes is useful for unattended or scripted SSH sessions because it disables interactive password prompts and host-key confirmation. Do not treat it as the first step.

Recommended order:

  1. Manually run the SSH wrapper command until it works without prompts.
  2. Confirm that host keys are trusted and key-based auth is already working.
  3. Only then consider enabling BatchMode yes in ~/.ssh/config.

Example SSH config shape:

Host your-frp-host
    HostName <host-or-ip>
    User <user>
    IdentityFile ~/.ssh/id_ed25519
    # Add BatchMode yes only after manual SSH testing succeeds
    # BatchMode yes

Test fixtures

Repository fixtures now live under tests/fixtures/instances/. They exist for tests and development reference only. Daily usage should start from frpdeck init ..., not by editing fixture directories directly.

Notes on paths

  • Relative paths in YAML are resolved against the instance directory, not the shell working directory.
  • Rendered systemd units always use absolute runtime paths.
  • By default, runtime files are installed under runtime/ inside the instance directory, while the systemd unit is written to /etc/systemd/system.
  • FRP's own logs are controlled by client.log or server.log and are written into generated frpc/frps config.
  • frpdeck's own logs are configured by top-level frpdeck_logging inside node.yaml.
  • Source configuration remains YAML. node.yaml is always present, while proxies.yaml is used for client proxy definitions and may be absent on server instances. There is no separate runtime config file for frpdeck in the current design.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

frpdeck-1.1.3.tar.gz (100.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

frpdeck-1.1.3-py3-none-any.whl (104.6 kB view details)

Uploaded Python 3

File details

Details for the file frpdeck-1.1.3.tar.gz.

File metadata

  • Download URL: frpdeck-1.1.3.tar.gz
  • Upload date:
  • Size: 100.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for frpdeck-1.1.3.tar.gz
Algorithm Hash digest
SHA256 d3470985dd2f44288362e0847ef38e6ed68b04fde731ba4e42e7e52602829726
MD5 61e31a43e4c5b4fbf2bdd719206f101f
BLAKE2b-256 03504d475467c668b77c05add00ff4045fc94869f86208d432773cc97356ccb1

See more details on using hashes here.

Provenance

The following attestation bundles were made for frpdeck-1.1.3.tar.gz:

Publisher: publish.yml on eserie-fox/frpdeck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file frpdeck-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: frpdeck-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 104.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for frpdeck-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 8027627f7c9e822fb5fc8fdecedd6a594421486c22bffa9cc4d041c87d595d46
MD5 66b48fe1f3a5ad36acf492772fab064a
BLAKE2b-256 4c9cf534c53464bfac5e42184875a68615eb5a19028cc0e9d049efed36651cc6

See more details on using hashes here.

Provenance

The following attestation bundles were made for frpdeck-1.1.3-py3-none-any.whl:

Publisher: publish.yml on eserie-fox/frpdeck

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page