fireclaw 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/fireclaw +23 -21
- package/bin/vm-common.sh +19 -3
- package/bin/vm-ctl +54 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ This repo is a minimal control plane that wires up Firecracker VM lifecycle, net
|
|
|
19
19
|
Host
|
|
20
20
|
├── systemd: firecracker-vmdemo-<id>.service ← runs the VM
|
|
21
21
|
├── systemd: vmdemo-proxy-<id>.service ← socat: localhost:<port> → VM:18789
|
|
22
|
-
├── bridge:
|
|
22
|
+
├── bridge: fc-br0 (172.16.0.0/24) ← shared bridge for all VMs
|
|
23
23
|
│
|
|
24
24
|
└── Firecracker VM (172.16.0.x)
|
|
25
25
|
├── cloud-init: ubuntu user, SSH key, Docker install
|
|
@@ -129,7 +129,7 @@ sudo fireclaw destroy my-bot --force
|
|
|
129
129
|
|
|
130
130
|
## Networking
|
|
131
131
|
|
|
132
|
-
Each VM gets a static IP on a bridge (`
|
|
132
|
+
Each VM gets a static IP on a bridge (`fc-br0`, `172.16.0.0/24`). The host acts as the gateway at `172.16.0.1` with NAT for outbound traffic. A `socat` proxy on the host forwards `127.0.0.1:<HOST_PORT>` to the VM's gateway port (`18789`), so the OpenClaw API is only reachable from localhost.
|
|
133
133
|
|
|
134
134
|
## Environment variables
|
|
135
135
|
|
|
@@ -140,7 +140,7 @@ All scripts respect these overrides:
|
|
|
140
140
|
| `STATE_ROOT` | `/var/lib/fireclaw` |
|
|
141
141
|
| `FC_ROOT` | `/srv/firecracker/vm-demo` |
|
|
142
142
|
| `BASE_PORT` | `18890` |
|
|
143
|
-
| `BRIDGE_NAME` | `
|
|
143
|
+
| `BRIDGE_NAME` | `fc-br0` |
|
|
144
144
|
| `BRIDGE_ADDR` | `172.16.0.1/24` |
|
|
145
145
|
| `SUBNET_CIDR` | `172.16.0.0/24` |
|
|
146
146
|
| `SSH_KEY_PATH` | `~/.ssh/vmdemo_vm` |
|
package/bin/fireclaw
CHANGED
|
@@ -4,30 +4,32 @@ set -euo pipefail
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
|
5
5
|
|
|
6
6
|
usage() {
|
|
7
|
-
|
|
8
|
-
Usage: fireclaw <command> [args...]
|
|
7
|
+
local bold=$'\033[1m' dim=$'\033[2m' reset=$'\033[0m' cyan=$'\033[36m'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
list List instances
|
|
13
|
-
status [id] Show instance status
|
|
14
|
-
start <id> Start instance
|
|
15
|
-
stop <id> Stop instance
|
|
16
|
-
restart <id> Restart instance
|
|
17
|
-
logs <id> [guest|host] Stream logs
|
|
18
|
-
shell <id> [command...] SSH shell or run command in VM
|
|
19
|
-
token <id> Show gateway token
|
|
20
|
-
destroy <id> [--force] Destroy instance
|
|
9
|
+
cat <<EOF
|
|
10
|
+
${bold}fireclaw${reset} — Firecracker microVM control plane
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
vm-setup <flags...> Pass through to vm-setup
|
|
25
|
-
vm-ctl <args...> Pass through to vm-ctl
|
|
12
|
+
${bold}USAGE${reset}
|
|
13
|
+
fireclaw <command> [args...]
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
${bold}COMMANDS${reset}
|
|
16
|
+
${cyan}setup${reset} <flags...> Create and provision a new VM instance
|
|
17
|
+
${cyan}list${reset} List all instances
|
|
18
|
+
${cyan}status${reset} [id] Show instance status (all or one)
|
|
19
|
+
${cyan}start${reset} <id> Start an instance
|
|
20
|
+
${cyan}stop${reset} <id> Stop an instance
|
|
21
|
+
${cyan}restart${reset} <id> Restart an instance
|
|
22
|
+
${cyan}logs${reset} <id> [guest|host] Stream logs
|
|
23
|
+
${cyan}shell${reset} <id> [command...] SSH into VM or run a command
|
|
24
|
+
${cyan}token${reset} <id> Print gateway token
|
|
25
|
+
${cyan}destroy${reset} <id> [--force] Destroy an instance
|
|
26
|
+
|
|
27
|
+
${bold}EXAMPLES${reset}
|
|
28
|
+
${dim}\$${reset} sudo fireclaw setup --instance my-bot --telegram-token <tok> --telegram-users <uid>
|
|
29
|
+
${dim}\$${reset} sudo fireclaw list
|
|
30
|
+
${dim}\$${reset} sudo fireclaw status my-bot
|
|
31
|
+
${dim}\$${reset} sudo fireclaw shell my-bot
|
|
32
|
+
${dim}\$${reset} sudo fireclaw logs my-bot guest
|
|
31
33
|
EOF
|
|
32
34
|
}
|
|
33
35
|
|
package/bin/vm-common.sh
CHANGED
|
@@ -6,12 +6,12 @@ FC_ROOT="${FC_ROOT:-/srv/firecracker/vm-demo}"
|
|
|
6
6
|
STATE_ROOT="${STATE_ROOT:-/var/lib/fireclaw}"
|
|
7
7
|
BASE_PORT="${BASE_PORT:-18890}"
|
|
8
8
|
|
|
9
|
-
BRIDGE_NAME="${BRIDGE_NAME:-
|
|
9
|
+
BRIDGE_NAME="${BRIDGE_NAME:-fc-br0}"
|
|
10
10
|
BRIDGE_ADDR="${BRIDGE_ADDR:-172.16.0.1/24}"
|
|
11
11
|
SUBNET_CIDR="${SUBNET_CIDR:-172.16.0.0/24}"
|
|
12
12
|
|
|
13
13
|
OPENCLAW_IMAGE_DEFAULT="${OPENCLAW_IMAGE_DEFAULT:-ghcr.io/openclaw/openclaw:latest}"
|
|
14
|
-
SSH_KEY_PATH="${SSH_KEY_PATH
|
|
14
|
+
SSH_KEY_PATH="${SSH_KEY_PATH:-/home/ubuntu/.ssh/vmdemo_vm}"
|
|
15
15
|
|
|
16
16
|
log() { printf '==> %s\n' "$*"; }
|
|
17
17
|
warn() { printf 'Warning: %s\n' "$*" >&2; }
|
|
@@ -96,14 +96,30 @@ wait_for_ssh() {
|
|
|
96
96
|
local ip="$1"
|
|
97
97
|
local key="${2:-$SSH_KEY_PATH}"
|
|
98
98
|
local retries="${3:-120}"
|
|
99
|
+
|
|
100
|
+
if [[ ! -r "$key" ]]; then
|
|
101
|
+
if [[ $EUID -ne 0 ]]; then
|
|
102
|
+
die "Cannot read SSH key: $key (try: sudo fireclaw ...)"
|
|
103
|
+
else
|
|
104
|
+
die "SSH key not found: $key"
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
local vm_svc vm_state
|
|
109
|
+
vm_svc="$(vm_service "${INSTANCE_ID:-}")"
|
|
110
|
+
|
|
99
111
|
local i
|
|
100
112
|
for ((i=1; i<=retries; i++)); do
|
|
101
113
|
if ssh -i "$key" -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 "ubuntu@$ip" true >/dev/null 2>&1; then
|
|
102
114
|
return 0
|
|
103
115
|
fi
|
|
116
|
+
vm_state="$(systemctl is-active "$vm_svc" 2>/dev/null)" || vm_state="inactive"
|
|
117
|
+
if [[ "$vm_state" != "active" ]]; then
|
|
118
|
+
die "VM is not running ($(printf '\033[31m%s\033[0m' "$vm_state")). Start it with: sudo fireclaw start ${INSTANCE_ID:-<id>}"
|
|
119
|
+
fi
|
|
104
120
|
sleep 2
|
|
105
121
|
done
|
|
106
|
-
|
|
122
|
+
die "VM is running but SSH did not become reachable at ubuntu@$ip after $((retries * 2))s"
|
|
107
123
|
}
|
|
108
124
|
|
|
109
125
|
check_guest_health() {
|
package/bin/vm-ctl
CHANGED
|
@@ -29,11 +29,45 @@ ssh_run() {
|
|
|
29
29
|
ssh -i "$key" -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null "ubuntu@$ip" "$@"
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
_color() {
|
|
33
|
+
local val="$1"
|
|
34
|
+
local green=$'\033[32m' red=$'\033[31m' yellow=$'\033[33m' reset=$'\033[0m'
|
|
35
|
+
case "$val" in
|
|
36
|
+
active|up) printf '%s%s%s' "$green" "$val" "$reset" ;;
|
|
37
|
+
inactive|down) printf '%s%s%s' "$red" "$val" "$reset" ;;
|
|
38
|
+
failed) printf '%s%s%s' "$red" "$val" "$reset" ;;
|
|
39
|
+
*) printf '%s%s%s' "$yellow" "$val" "$reset" ;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_print_status_table() {
|
|
44
|
+
local -a ids=() ips=() ports=() vms=() proxies=() healths=()
|
|
45
|
+
local id ip port vm proxy health
|
|
46
|
+
|
|
47
|
+
while IFS='|' read -r id ip port vm proxy health; do
|
|
48
|
+
ids+=("$id"); ips+=("$ip"); ports+=("$port")
|
|
49
|
+
vms+=("$vm"); proxies+=("$proxy"); healths+=("$health")
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
[[ ${#ids[@]} -gt 0 ]] || { echo "(no instances)"; return; }
|
|
53
|
+
|
|
54
|
+
local hdr=$'\033[1;37m' reset=$'\033[0m' dim=$'\033[2m'
|
|
55
|
+
printf "${hdr}%-14s %-14s %-7s %-10s %-10s %-8s${reset}\n" \
|
|
56
|
+
"INSTANCE" "IP" "PORT" "VM" "PROXY" "HEALTH"
|
|
57
|
+
printf "${dim}%-14s %-14s %-7s %-10s %-10s %-8s${reset}\n" \
|
|
58
|
+
"--------" "----------" "-----" "------" "-------" "------"
|
|
59
|
+
|
|
60
|
+
for i in "${!ids[@]}"; do
|
|
61
|
+
printf "%-14s %-14s %-7s %-10b %-10b %-8b\n" \
|
|
62
|
+
"${ids[$i]}" "${ips[$i]}" "${ports[$i]}" \
|
|
63
|
+
"$(_color "${vms[$i]}")" "$(_color "${proxies[$i]}")" "$(_color "${healths[$i]}")"
|
|
64
|
+
done
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
cmd_list() {
|
|
33
68
|
shopt -s nullglob
|
|
34
|
-
local
|
|
69
|
+
local rows=()
|
|
35
70
|
for d in "$STATE_ROOT"/.vm-*/; do
|
|
36
|
-
found="true"
|
|
37
71
|
local id
|
|
38
72
|
id="$(basename "$d" | sed 's/^\.vm-//')"
|
|
39
73
|
if [[ ! "$id" =~ ^[a-z0-9_-]+$ ]]; then
|
|
@@ -53,12 +87,12 @@ cmd_list() {
|
|
|
53
87
|
health="up"
|
|
54
88
|
fi
|
|
55
89
|
local vm_state proxy_state
|
|
56
|
-
vm_state="$(systemctl is-active "$(vm_service "$id")" 2>/dev/null ||
|
|
57
|
-
proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null ||
|
|
58
|
-
|
|
90
|
+
vm_state="$(systemctl is-active "$(vm_service "$id")" 2>/dev/null)" || vm_state="inactive"
|
|
91
|
+
proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null)" || proxy_state="inactive"
|
|
92
|
+
rows+=("${id}|${VM_IP}|${HOST_PORT}|${vm_state}|${proxy_state}|${health}")
|
|
59
93
|
done
|
|
60
94
|
shopt -u nullglob
|
|
61
|
-
|
|
95
|
+
printf '%s\n' "${rows[@]}" | _print_status_table
|
|
62
96
|
}
|
|
63
97
|
|
|
64
98
|
cmd_status_one() {
|
|
@@ -67,15 +101,15 @@ cmd_status_one() {
|
|
|
67
101
|
load_instance_env "$id"
|
|
68
102
|
local ssh_key="${SSH_KEY_PATH:-$HOME/.ssh/vmdemo_vm}"
|
|
69
103
|
local vm_state proxy_state health host_health guest_health guest
|
|
70
|
-
vm_state="$(systemctl is-active "$(vm_service "$id")" 2>/dev/null ||
|
|
71
|
-
proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null ||
|
|
104
|
+
vm_state="$(systemctl is-active "$(vm_service "$id")" 2>/dev/null)" || vm_state="inactive"
|
|
105
|
+
proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null)" || proxy_state="inactive"
|
|
72
106
|
health="down"
|
|
73
107
|
host_health="down"
|
|
74
108
|
guest_health="down"
|
|
75
109
|
curl -fsS "http://127.0.0.1:$HOST_PORT/health" >/dev/null 2>&1 && host_health="up"
|
|
76
110
|
guest="unknown"
|
|
77
111
|
if wait_for_ssh "$VM_IP" "$ssh_key" 1; then
|
|
78
|
-
guest="$(ssh_run "$VM_IP" "systemctl is-active openclaw-$id.service" 2>/dev/null ||
|
|
112
|
+
guest="$(ssh_run "$VM_IP" "systemctl is-active openclaw-$id.service" 2>/dev/null)" || guest="unknown"
|
|
79
113
|
if check_guest_health "$id" "$VM_IP" "$ssh_key"; then
|
|
80
114
|
guest_health="up"
|
|
81
115
|
fi
|
|
@@ -83,7 +117,17 @@ cmd_status_one() {
|
|
|
83
117
|
if [[ "$host_health" == "up" || "$guest_health" == "up" ]]; then
|
|
84
118
|
health="up"
|
|
85
119
|
fi
|
|
86
|
-
|
|
120
|
+
|
|
121
|
+
local bold=$'\033[1m' dim=$'\033[2m' reset=$'\033[0m'
|
|
122
|
+
printf "${bold}%s${reset}\n" "$id"
|
|
123
|
+
printf " %-16s %s\n" "IP" "$VM_IP"
|
|
124
|
+
printf " %-16s %s\n" "Proxy port" "$HOST_PORT"
|
|
125
|
+
printf " %-16s %b\n" "VM" "$(_color "$vm_state")"
|
|
126
|
+
printf " %-16s %b\n" "Proxy" "$(_color "$proxy_state")"
|
|
127
|
+
printf " %-16s %b\n" "Guest service" "$(_color "$guest")"
|
|
128
|
+
printf " %-16s %b\n" "Health" "$(_color "$health")"
|
|
129
|
+
printf " %-16s %b\n" " Host health" "$(_color "$host_health")"
|
|
130
|
+
printf " %-16s %b\n" " Guest health" "$(_color "$guest_health")"
|
|
87
131
|
}
|
|
88
132
|
|
|
89
133
|
cmd_status() {
|