Skip to main content

daffi-terminals

demo

A lightweight, browser-based remote terminal manager. Connect any number of Linux/macOS hosts to a central router and open interactive shell sessions in your browser — no SSH, no VPN, no agent configuration required.


Features

Multi-host Connect unlimited remote nodes; each appears as a card in the sidebar
Live terminal Full interactive shell with automatic resize as you resize the browser window
Autoreconnect Workers reconnect to the router automatically after any network interruption
Host info Hostname, OS, kernel, CPU, memory and uptime shown in the header when you open a session
Dark / Light theme Theme switcher with Solarized-inspired palettes, persisted across reloads
Fuzzy search Filter the worker sidebar by name or MAC address
TLS encryption Optional encryption for both the browser connection and the worker transport
Zero footprint on workers Workers need only Python and daffi — no SSH daemon, no open ports

Installation

pip install daffi-terminals

Python 3.8+ is required.


Quick start

Terminal 1 — start the router (the machine your browser will connect to):

dterm start-router \
  --rpc-host 0.0.0.0 \
  --rpc-port 9999 \
  --web-host 0.0.0.0 \
  --web-port 8888

Open http://<router-host>:8888 in your browser.

Terminal 2, 3, … — connect workers (one per host you want to control):

dterm start-worker \
  --rpc-host <router-host> \
  --rpc-port 9999

Each worker appears in the sidebar within seconds. Click a card to open a shell session.


How it works

  Browser
     │  HTTP/WebSocket
     ▼
  Router  ──── RPC (TCP) ────►  Worker A  (any Linux/macOS host)
     │    ──── RPC (TCP) ────►  Worker B
     │    ──── RPC (TCP) ────►  Worker N  (as many as you need)
     │
  Web UI served on --web-port
  Workers registered on --rpc-port

The router is a single process that acts as both the RPC message broker (for worker communication) and the web server (for the browser UI). Workers connect outbound to the router — you never need to open ports on the worker hosts.


CLI reference

dterm start-router

Argument Required Description
--rpc-host yes Address the router listens on for worker connections (use 0.0.0.0 to accept from any host)
--rpc-port yes Port for worker connections
--web-host yes Address the web server listens on (use 0.0.0.0 to accept from any host)
--web-port yes Port for the browser UI
--ssl-cert no TLS certificate for worker connections (PEM). Enables encrypted RPC when combined with --ssl-key
--ssl-key no TLS private key for worker connections (PEM)
--web-ssl-cert no TLS certificate for the web server. Enables HTTPS/WSS when combined with --web-ssl-key
--web-ssl-key no TLS private key for the web server

dterm start-worker

Argument Required Description
--rpc-host yes Hostname or IP of the router
--rpc-port yes Port of the router
--name no Name shown in the UI sidebar. Auto-generated as <hostname>-0x<hex> if omitted
--group no Visual group label. Workers with the same group appear together in a collapsible colored section (up to 7 groups)
--ssl-cert no TLS certificate (must match the router's certificate)
--ssl-key no TLS private key

Examples

Worker groups

groups

Workers can be organized into named groups using the --group flag. Each group gets a distinct color and appears as a collapsible section in the sidebar, sorted alphabetically. Workers without a group are listed at the top.

dterm start-worker --rpc-host router --rpc-port 9999 --group "production"
dterm start-worker --rpc-host router --rpc-port 9999 --group "production"
dterm start-worker --rpc-host router --rpc-port 9999 --group "staging"
dterm start-worker --rpc-host router --rpc-port 9999 --group "staging"
dterm start-worker --rpc-host router --rpc-port 9999   # ungrouped — shown at top

Up to 7 groups are supported, each with a unique color from the built-in rainbow palette. Click a group header to collapse or expand it. The collapsed state is remembered across page reloads.

Multiple workers on the same host

Give each worker a distinct name:

dterm start-worker --rpc-host router --rpc-port 9999 --name "web-server-1"
dterm start-worker --rpc-host router --rpc-port 9999 --name "web-server-2"

Or omit --name and let each instance generate its own unique identifier.

Workers on remote hosts

The router address must be reachable from the worker host. Replace 192.168.1.10 with your router's IP or hostname:

# on the router host
dterm start-router --rpc-host 0.0.0.0 --rpc-port 9999 \
                   --web-host 0.0.0.0 --web-port 8888

# on each remote worker host
dterm start-worker --rpc-host 192.168.1.10 --rpc-port 9999

HTTPS + encrypted worker transport (TLS)

There are two ways to serve the web UI over HTTPS:

Option A — built-in TLS (simple, no extra software)

Pass certificate files directly to the router. See examples/ssl/ for ready-to-run scripts:

# 1. generate a certificate (uses mkcert if available, otherwise openssl)
bash examples/ssl/gen-certs.sh

# 2. start router — HTTPS on the web UI, TLS on the worker port
bash examples/ssl/start-router.sh

# 3. connect workers with TLS
bash examples/ssl/start-worker.sh

Open https://localhost:8888. If you used the openssl fallback (no mkcert), click Advanced → Accept the Risk and Continue once.

Run the web server on plain HTTP and let nginx handle TLS termination. This is the standard production setup and works with any certificate, including Let's Encrypt.

Browser ──HTTPS──► nginx :443 ──HTTP──► dterm web server :8888

Minimal nginx site config:

server {
    listen 443 ssl;
    server_name terminals.example.com;

    ssl_certificate     /etc/letsencrypt/live/terminals.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/terminals.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8888;

        # Required for WebSocket upgrade (used by /director and /terminal)
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Keep terminal sessions alive during long idle periods
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

# Redirect plain HTTP to HTTPS
server {
    listen 80;
    server_name terminals.example.com;
    return 301 https://$host$request_uri;
}

Start the router without any --web-ssl-* flags (nginx owns the cert):

dterm start-router \
  --rpc-host 0.0.0.0 --rpc-port 9999 \
  --web-host 127.0.0.1 --web-port 8888

The worker RPC transport is separate from the web server and can be encrypted independently with --ssl-cert / --ssl-key regardless of which HTTPS option you choose.

Running as a systemd service

Worker on a remote host that should start automatically on boot:

# /etc/systemd/system/dterm-worker.service
[Unit]
Description=daffi-terminals worker
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/dterm start-worker \
    --rpc-host router.example.com \
    --rpc-port 9999
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now dterm-worker

Makefile (development)

make start-router   # localhost:9999 (RPC) + localhost:8888 (web)
make start-worker   # connects to localhost:9999

Targets auto-detect the Python virtual environment in .venv/ inside the project root.


License

MIT — see LICENSE.txt.

Metadata

Release files for daffi-terminals 2.0.1

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

Source distribution (sdist)

Source distribution for daffi-terminals 2.0.1
File Size Uploaded Provenance Actions
daffi_terminals-2.0.1.tar.gz 735.2 kB With provenance Details for daffi_terminals-2.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for daffi-terminals 2.0.1
File Interpreter ABI Platform Actions
daffi_terminals-2.0.1-py3-none-any.whl Python 3 none any Details for daffi_terminals-2.0.1-py3-none-any.whl

Total release size: 1.5 MB

Release files / daffi_terminals-2.0.1.tar.gz

Download URL daffi_terminals-2.0.1.tar.gz
Size 735.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d2d2250e559efe0d44edae36cf082dca546c57ed25ed52e4dd6d54ad5a88bc11
BLAKE2b-256 checksum
How to use checksums
9266e8e5da268b8af3ba66192864ba816cba405efc5493e3f3549d925e3f9610
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 17, 2026.

Transparency log

Release files / daffi_terminals-2.0.1-py3-none-any.whl

Download URL daffi_terminals-2.0.1-py3-none-any.whl
Size 738.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2b8373136a6f5830878646e54673d0e94d7482ffdb859805c510ae2c72c70877
BLAKE2b-256 checksum
How to use checksums
34e2858310ac33635b47b93d1c70a6edb363a31da7d0b5a65151156a24e695be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 17, 2026.

Transparency log

Security for daffi-terminals 2.0.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for daffi_terminals-2.0.1.tar.gz

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 Jun 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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