rmote¶
Asyncio RPC for remote Python processes that have nothing installed.
Motivation¶
Many remote hosts - fresh VMs, CI runners, minimal containers, embedded devices - have Python available but nothing else: no virtualenv, no pip, no pre-installed agent. The usual answer is “install the installer first”, which just pushes the bootstrapping problem one step earlier.
rmote takes a different approach: it compresses and base64-encodes its entire runtime (a few KB),
writes it to the remote’s stdin, and has the remote interpreter execute it directly. The remote
process needs only the Python standard library.
Once connected, you interact with the remote host by writing ordinary Python classes - not YAML files, not shell scripts, not JSON-RPC stubs. Methods are async-native, return values are typed dataclasses, and multiple in-flight calls execute concurrently on both sides.
Prior Art¶
Tool |
Remote requires |
Execution model |
Native async |
|---|---|---|---|
Fabric |
SSH + shell |
Shell commands only |
No |
Ansible |
Python 2.6+ + modules |
Module push (JSON results) |
No |
RPyC zero-deploy |
Python + Plumbum locally |
True RPC, transparent proxies |
Partial |
Mitogen |
Python only |
Compressed bootstrap, RPC |
No |
rmote |
Python stdlib only |
Compressed bootstrap, asyncio RPC |
Yes |
The closest in spirit is Mitogen - it uses the same stdin-injection technique. However,
Mitogen predates Python 3’s async model, is no longer actively maintained, and exposes a
lower-level channel API. rmote is built for asyncio from the ground up: concurrent multi-host
calls are first-class, tool definitions are plain Python classes, and return values are typed
dataclasses rather than JSON blobs.
How It Works¶
graph LR
subgraph local ["Local (your machine)"]
hlp["Protocol\n+ _tools_cache"]
tc["Tool classes\n(source extracted by ToolMeta)"]
end
subgraph remote ["Remote (injected process)"]
rp["Protocol\n(asyncio event loop)"]
ti["Tool instances\n(exec'd from source)"]
end
hlp <-->|"stdin / stdout\npackets"| rp
tc -.->|"SYNC packet\n(first call only)"| ti
Full detail is in Concepts, but the three-step summary:
Bootstrap -
protocol.pyis compressed, base64-encoded, and written to the remote’s stdin. The remote interpreter executes it and writesPROTOCOL READYto signal that the channel is open.Tool sync - On first use, the tool’s source code is extracted locally via
inspectand sent as a SYNC packet. The remoteexec()s it into a fresh module. Subsequent calls skip this step entirely.RPC - Each
await proto(Tool.method, *args)gets a uniquepacket_id. The remote runs the method (sync or async) and returns the result through the same channel. Multiple in-flight calls execute concurrently on both sides via asyncio task spawning.
sequenceDiagram
participant L as Local
participant R as Remote
Note over L,R: Bootstrap
L->>R: exec(decompress(b64decode(payload)))
R-->>L: PROTOCOL READY
Note over L,R: Tool sync (first call only)
L->>R: SYNC tool_source
R-->>L: ACK
Note over L,R: RPC
L->>R: REQUEST {method, args, packet_id}
R-->>L: RESPONSE {result, packet_id}
Quick Example¶
import asyncio
from rmote.protocol import Protocol
from rmote.tools.fs import FileSystem
async def main():
async with await Protocol.from_ssh("web01.example.com") as proto:
hosts_content, uptime = await asyncio.gather(
proto(FileSystem.read_str, "/etc/hosts"),
proto(FileSystem.read_str, "/proc/uptime"),
)
print(hosts_content)
print(uptime)
if __name__ == "__main__":
asyncio.run(main())
Both calls are dispatched immediately - the channel does not wait for the first response before
sending the second. The local event loop matches each response back to its caller by packet_id:
sequenceDiagram
participant L as Local
participant R as Remote
L->>R: REQUEST {FileSystem.read, "/etc/hosts", id=1}
L->>R: REQUEST {FileSystem.read, "/proc/uptime", id=2}
R-->>L: RESPONSE {hosts_content, id=1}
R-->>L: RESPONSE {uptime, id=2}
Install on the local side only - the remote needs nothing:
pip install `rmote`
Built-in Tools¶
Tool |
Manages |
Platform |
|---|---|---|
|
Read, write, glob, idempotent line-in-file |
any |
|
Run commands and shell expressions |
any |
|
systemd units (start/stop/enable/disable) |
Linux |
|
Users and groups |
Linux |
|
Packages and package state |
Debian / Ubuntu |
|
DEB822 sources and GPG keys |
Debian / Ubuntu |
|
Packages and package state |
Arch Linux |
|
Repository sections and GPG keys |
Arch Linux |
|
Remote log level and forwarding |
any |
|
Mako-like template rendering |
any |
|
Remote process exit |
any |
Project Status¶
Version: 0.3.0 - semver: patch releases fix bugs, minor releases add tools or protocol features, major releases indicate breaking wire or API changes.
Stable: core protocol, SSH transport, all 11 built-in tools, templating engine. The entire
``rmote/ package is type-checked with strict mypy.
Not yet supported: Windows remote hosts, non-SSH transports (socket, docker exec), streaming or generator responses, TLS-encrypted channels.
API Reference