remctl
Secure, LAN-based remote PC control and administration.
remctl lets you control machines on your local network from the command line —
lock, shutdown, execute shell commands with streaming output, transfer files,
take screenshots, and check system status. All communication is authenticated
using Ed25519 keys and encrypted with AES-256-GCM.
⚠️ Intended for your own trusted LAN devices only.
remctlis not designed for Internet-facing use. Do not expose it via port forwarding or NAT.
Installation
pip install remctl
From source
# TODO: user adds PyPI API token via env var / twine config before publishing
git clone <repo-url>
cd remctl
pip install -e ".[dev]"
Quick Start (Easy Mode)
On the target machine (the node)
# First run starts a short setup wizard (name, admin, acceptance mode),
# then runs the agent in the foreground:
remctl node start
The wizard asks three things:
- Node name — a friendly name broadcast on your LAN (e.g.
livingroom-pc). - Allow admin (exec/shell) commands? — grants the
fulltier to new controllers, otherwise they getstandard(no exec). - Acceptance mode for new controllers:
auto— trust any controller that connects (easiest, least safe)ask— ask for approval on every connectionask-new— ask only the first time a controller connects (recommended)
You can re-run the wizard any time with remctl node setup, or start without it
using the stored config via remctl node start.
On the controlling machine
# Find nodes on the LAN (lists them by name)
remctl ctl scan
# Connect to one by name — this pairs it and makes it the active node
remctl ctl connect livingroom-pc
# Now run actions on the active node
remctl ctl actions list # what can I do?
remctl ctl actions status
remctl ctl actions lock
remctl ctl actions shutdown --delay 60
remctl ctl shell "whoami" # = actions exec
If you have more than one node, target a specific one with --node:
remctl ctl actions status --node bedroom-pc
remctl ctl nodes # list paired nodes + active
remctl ctl forget livingroom-pc # remove a paired node
Advanced: Manual Two-Way Pairing
If you prefer explicit key exchange (no acceptance-mode prompts), use the original flow. Both sides must still trust each other:
# Node: show its fingerprint
remctl node pair --show
remctl node start
# Controller: pair + show its own fingerprint for the node to trust
remctl ctl pair <node-ip>
remctl ctl pair-show
# Node: trust the controller's public key file
remctl node trust-controller /path/to/controller_identity.pub
Legacy per-command form (still works, defaults to the active node):
remctl ctl <target> status
remctl ctl <target> lock
remctl ctl <target> exec "whoami"
remctl ctl <target> screenshot -o screenshot.png
remctl ctl <target> push ./file.txt /remote/path/file.txt
remctl ctl <target> pull /remote/log.log ./log.log
Command Reference
| Command | Description |
|---|---|
remctl node start |
First-run wizard (if needed) then start the node agent |
remctl node setup |
Run the setup wizard (name, admin, acceptance mode) |
remctl node install-service |
Install as OS service |
remctl ctl scan |
Discover nodes on the LAN (by name) |
remctl ctl connect <name> |
Pair with a node and make it active |
remctl ctl nodes |
List paired nodes + active |
remctl ctl forget <name> |
Remove a paired node |
remctl ctl actions list |
List available actions + tiers |
remctl ctl actions <action> [--node <name>] |
Run a named action on the active node |
remctl ctl shell "<cmd>" |
Alias for actions exec |
remctl ctl discover |
Discover nodes on LAN (legacy) |
remctl ctl pair <target> |
Pair with a node (manual flow) |
remctl ctl <target> lock |
Lock the remote workstation |
remctl ctl <target> shutdown --delay 60 |
Shutdown with optional delay |
remctl ctl <target> restart |
Restart |
remctl ctl <target> sleep |
Sleep/Suspend |
remctl ctl <target> logout |
Log out current user |
remctl ctl <target> closefocused |
Close active window |
remctl ctl <target> exec <command> |
Execute shell command (streamed) |
remctl ctl <target> status |
Show system status (CPU/mem/disk/uptime) |
remctl ctl <target> screenshot -o out.png |
Capture screenshot |
remctl ctl <target> push <local> <remote> |
Push file to node |
remctl ctl <target> pull <remote> <local> |
Pull file from node |
remctl ctl <target> cancel |
Cancel pending shutdown/restart |
Security Model (Summary)
- Identity: Ed25519 keypairs generated on first run.
- Authentication: Node accepts controllers per its acceptance mode
(
auto/ask/ask-new). Default isask-new— no auto-trust unless you opt in. The manual two-waytrust-controllerflow still works. - Encryption: X25519 ECDH key exchange → AES-256-GCM, with forward secrecy.
- Replay protection: Per-message sequence numbers + 30-second timestamp window.
- Permission tiers:
observe(status/screenshot),standard(commands + files),full(includesexec). Default for new controllers:standard. - Audit log: All commands logged unredacted at
~/.remctl/audit.log.
See docs/SECURITY.md for the full threat model.
Configuration
All config files live in ~/.remctl/:
| File | Purpose |
|---|---|
identity.key |
Private key (keep secure) |
identity.pub |
Public key |
known_nodes.json |
Paired nodes (ctl side) |
known_controllers.json |
Trusted controllers (node side) |
node_config.json |
Node settings (name, bind, port, acceptance mode, tiers) |
ctl_config.json |
Controller settings (active node) |
audit.log |
Command audit log |
Platform Support
| Platform | Status |
|---|---|
| Windows | First-class |
| Linux | Second-class (best-effort) |
| macOS | Not yet implemented |
Publishing to PyPI
python -m build
twine upload dist/*
You need a PyPI API token (not your password):
- Create a free account at https://pypi.org
- Go to https://pypi.org/manage/account/token/ and generate a token scoped to
remctl - Copy
.env.exampleto.envand fill in the token, or set these env vars:
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
See .env.example for the full list of configurable secrets. No credentials are included in the repository.
Metadata
Release files for remctl 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| remctl-0.1.1.tar.gz | 31.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| remctl-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.2 kB
Release files / remctl-0.1.1.tar.gz
| Download URL | remctl-0.1.1.tar.gz |
|---|---|
| Size | 31.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1cefc1a1fc261475171e5dfb8d518fcea0f0f765f78579f572d9063a77cb1385
|
|
BLAKE2b-256 checksum How to use checksums |
6b034f23ce737ce4b13d7a21915a28e6e8917847508a4ef8ff5af7f373a6d5e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / remctl-0.1.1-py3-none-any.whl
| Download URL | remctl-0.1.1-py3-none-any.whl |
|---|---|
| Size | 32.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eba9c97e5ff6df7de0ec85398197d9c08338ef4f3635ba34f380eb4e60731579
|
|
BLAKE2b-256 checksum How to use checksums |
36e9f26b12e9961c643593bb8014908194e03dbdc5db4f89d311dd0f0aa5baa6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|