Files
rvbox/init-design.md
T

196 lines
9.0 KiB
Markdown

# RVBox - A Reverse Shell Solution for LLM Agent
* It's a C/S architecture, the clients are the one connects to the server, and are controlled by it.
* The clients runs the `rvbox` daemon, actively and persistently connects to a configured server. It registers and identifies itself to the server first, if accepted, then keeps a long connection and accepting commands from the server.
* The server runs the `rvbox-server` daemon, listening on a `wss://` endpoint, waiting for incoming `rvbox` clients. An LLM agent on the server machine then can use `rvc` command to communicate with the server daemon, issuing commands to do all sorts of things:
- listing clients,
- issuing/terminating commands on specific client,
- retrieving running/stopping status of a command,
- getting stdout/stderr, or return code from a command,
- inputing into stdin of a command,
* commands can be issued in two modes: foreground or background, for foreground ones, `rvc` command wait for the return, otherwise, `rvc` returns immediately with metadata of the issued command process on the client.
* for each issued command from `rvc`, the server daemon attaches an `issue_uuid` to it, and sends it to the related client, along with these fields: `issue_timestamp`, `shell_type`, `shell_command_text`. The server also records all these fields into its data store, with another `target_client_id`, to track the full states of every command.
* The client's daemon runs the issued commands, and reports stdout/stderr of all issued commands to the server in real-time, meaning no waiting for new-line flush. Also, multiple commands can be issued to a client daemon, and the client daemon can execute them concurrently. For each running command, the daemon appends new stdout and stderr updates to the server in every short 3-5s window, or only reports the command's status in a longer 10-15s window if no new updates. The server keeps on track of everything, so it can reconstruct the full context/history correctly for each command, when demanded so by `rvc` .
* The connection should be guarded by a properly designed heartbeat mechanism on both C/S sides.
* `rvc` is just a cli command that communicates with the server daemon through local unixsock. Also, the same server daemon control interface can be accessed through an optional simple HTTP json rpc endpoint, e.g. `--json-rpc http://127.0.0.1:6900` by default, so that we can interact with the server in a more structured way when complex or large batch operations are needed.
### Example usages of `rvc` on server side
1. Listing clients
```bash
$ rvc stat
CLIENT Commands Running Connected Time
client_a 5 10 days
client_b 0 10 min
...
```
2. Stating a client
```bash
$ rvc stat client_a
Command ID Command Snippet Status Issue Time
<uuid_1> "cp /path1/file /path2/" Running 10s ago
<uuid_2> "python very_long_computing.py" Running 2 days ago
<uuid_3> "sha256sum /path/very_large_file" Hung 10min ago
```
3. Stating all commands of a client. Note this `--all` can return very long history, so it has a optional pager `--page <NO>` arg. By default the optional `--per-page` is at 20, and accepts 100 at max.
```bash
$ rvc stat client_a --all
Page 1/34
Command ID Command Snippet Status Issue Time
<uuid_1> "cp /path1/file /path2/" Running 10s ago
<uuid_2> "python very_long_computing.py" Running 2 days ago
<uuid_3> "sha256sum /path/very_large_file" Hung(I/O) 10min ago
<uuid_4> "curl -fsLO https://..." Done 15min ago
<uuid_5> "culr -fsLO https://..." Failed(127) 15min ago
<uuid_6> "curl -fsLO https://..." Terminated(130) 15min ago
...
```
4. Stating some commands of client_a
```bash
$ rvc stat client_a <uuid_1>
Command Text: "cp /path1/file /path2/"
Status: Running
Issue Time: 20xx-01-01T10:01:01Z
CWD: "/..."
CPU TIME: 0:00.23
RES RAM: 12K
```
```bash
$ rvc stat client_a <uuid_2>
Command Text: "python very_long_computing.py"
Status: Running
Issue Time: 20xx-12-30T12:01:01Z
CWD: "/..."
CPU TIME: 12h34:45
RES RAM: 892M
```
```bash
$ rvc stat client_a <uuid_3>
Command Text: "sha256sum /path/very_large_file"
Status: Hung
Reason: I/O
Issue Time: 20xx-01-01T10:01:01Z
CWD: "/..."
CPU TIME: 0:10.24
RES RAM: 125K
```
```bash
$ rvc stat client_a <uuid_4>
Command Text: "curl -fsLO https://<download_url>"
Status: Success
Issue Time: 20xx-01-01T10:01:01Z
CWD: "/..."
CPU TIME: 0:10.24
RES RAM: 125K
```
```bash
$ rvc stat client_a <uuid_5>
Command Text: "culr -fsLO https://<download_url>"
Status: Failure
Exit Code: 127
Issue Time: 20xx-01-01T10:01:01Z
Return Time: 20xx-01-01T10:01:01Z
CWD: "/..."
```
```bash
$ rvc stat client_a <uuid_6>
Command Text: "curl -fsLO https://<download url>"
Status: Terminated
Exit Code: 130
Issue Time: 20xx-01-01T10:01:01Z
Return Time: 20xx-01-01T10:02:01Z
CWD: "/..."
```
5. Issue a new command to a client.
- By design, the commands triggered/executed by the client daemon should inherit its user/permission settings.
- By default, the optional `--current-work-dir/--cwd` of the issued command is inherited from the client daemon.
- If `--script` is used to issue a whole script, the script will first be put into the `--cwd` dir, then executed by the client daemon. As soon as it's exited, the script will be reclaimed.
```bash
# wait until return by default
$ rvc run client_a --cwd '/home/user' 'ls'
Desktop Documents Downloads Pictures...
# run in background, return the command id immediately
$ rvc run --background client_a 'python -c "for i in range(15): print(\'hi\'); import time; time.sleep(1)"'
Command ID: <uuid_x>
# if the command is too long or the symbol escapings are too complicated, write a whole script then issue.
$ cat << 'EOF' > ./py_script
heredoc> #!/usr/bin/env python
heredoc> print("what's your name?")
heredoc> name = input("type your name here: ")
heredoc> print(f"Hello, {name}!")
heredoc> EOF
$ rvc run client_a --script ./py_script --cwd /tmp --background
Command ID: <uuid_y>
```
6. Get stdout and/or stderr of a command,
- optionally with `--timestamped` to get a leading timestamp on each line,
- and optionally follow new updates with `--follow/-f`, just like the `tail -f` command
- the output of `--stdout` and `--stderr` are paged, with `--lines-per-page` defaults to 100 and `--page` defaults to 1.
```bash
$ rvc stat client_a <uuid_x> --stdout --stderr --timestamped --page 2 --lines-per-page 3
Page 2/4
[20xx-01-01T10:21:01Z stdout] hi
[20xx-01-01T10:21:02Z stdout] hi
[20xx-01-01T10:21:03Z stdout] hi
```
7. Write new content into stdin of a command
```bash
# write the string into its stdin, and a \n will be appended to the end automatically
$ rvc append client_a <uuid_y> 'Alice'
$ rvc stat client_a <uuid_y> --stdout --stderr --timestamped
[20xx-01-01T10:23:11Z stdout] type your name here:
[20xx-01-01T10:24:09Z stdout] Hello, Alice!
# or write a file content into its stdin
$ rvc append client_a <uuid_y> --file ./name_file
# or patch the current stdin into it
$ rvc append client_a <uuid_y> --attach
Bob
```
8. Send a signal to a command.
- For unix/linux client daemons, this `kill` subcommand behaves just like the normal `kill` command. It sends SIGTERM by default, and can be used to send other signals with args like `-1/-HUP`, `-2/-INT`, etc.
- On windows client daemons, it should only accept SIGTERM and SIGKILL, equivalent to `taskkill /IM` and `taskkill /F /IM`, respectively.
```bash
$ rvc kill client_a <uuid_3>
```
### Implementation requirements
1. We should first do proper design on the protocol - both the C/S interface and the server daemon unixsock/jsonrpc control interface. protobuf should be a solid choice to do the protocol design.
2. Currently we'll implement both C/S daemon in Golang. We'll later implement a Rust version client to adapt on low power devices.
### Technical nuances
1. The `rvbox-server` daemon and the `rvbox` client daemon should always be responsive. The server daemon should always be dispatching clients, recording the status, and listening for requests from `rvc`, etc. And the client daemons should keep on receiving/dispatching incoming commands/requests, monitoring ongoing commands, reporting new updates/status to the server. In ABSOLUTELY NO circumstances should the daemons themselves be crashed, blocked, hung, flooded, unresponsive, etc., due to any possible reason.
2. The heartbeat mechanism that keeps and guards the websocket connection should be robust, clean and correct. When connection exceptions/interruptions/hangs happen, the heartbeat mechanism should detect them in time, and trigger the reconnect, re-register route correctly. It should guard on both the inbound and the outbound traffic. It should not disrupt, hog, block, interrupt, mutate the normal traffic in any way.