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 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: fcbr0 (172.16.0.0/24) ← shared bridge for all VMs
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 (`fcbr0`, `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.
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` | `fcbr0` |
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
- cat <<'EOF'
8
- Usage: fireclaw <command> [args...]
7
+ local bold=$'\033[1m' dim=$'\033[2m' reset=$'\033[0m' cyan=$'\033[36m'
9
8
 
10
- Primary commands:
11
- setup <flags...> Create and provision a new VM instance
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
- Compatibility commands:
23
- ctl <vm-ctl-args...> Pass through to vm-ctl
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
- Examples:
28
- sudo fireclaw setup --instance my-bot --telegram-token <token> --telegram-users <uid>
29
- sudo fireclaw list
30
- sudo fireclaw status my-bot
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:-fcbr0}"
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:-$HOME/.ssh/vmdemo_vm}"
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
- return 1
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 found="false"
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 || echo inactive)"
57
- proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null || echo inactive)"
58
- echo "$id ip=$VM_IP port=$HOST_PORT vm=$vm_state proxy=$proxy_state health=$health host_health=$host_health guest_health=$guest_health"
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
- [[ "$found" == "true" ]] || echo "(no instances)"
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 || echo inactive)"
71
- proxy_state="$(systemctl is-active "$(proxy_service "$id")" 2>/dev/null || echo inactive)"
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 || echo unknown)"
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
- echo "$id ip=$VM_IP port=$HOST_PORT vm=$vm_state proxy=$proxy_state guest=$guest health=$health host_health=$host_health guest_health=$guest_health"
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fireclaw",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Firecracker microVM control plane for isolated OpenClaw instances",
5
5
  "bin": {
6
6
  "fireclaw": "bin/fireclaw"