@kvbox/server 0.0.0-stage → 0.5.6

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/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@kvbox/server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.6",
4
+ "description": "kvbox host daemon: owns Firecracker microVMs on a Linux host and serves the kvbox API. Managed with the @kvbox/cli CLI.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "kvbox-server": "dist/kvbox-server.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "scripts"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/ghalex/kvbox.git",
20
+ "directory": "packages/server"
21
+ },
22
+ "homepage": "https://kvbox.sh",
23
+ "keywords": [
24
+ "firecracker",
25
+ "microvm",
26
+ "sandbox",
27
+ "vm",
28
+ "server"
29
+ ],
30
+ "os": [
31
+ "linux"
32
+ ],
33
+ "engines": {
34
+ "node": ">=22.13"
35
+ },
36
+ "scripts": {
37
+ "dev": "tsx src/index.ts",
38
+ "build": "tsup"
39
+ },
40
+ "dependencies": {
41
+ "@hono/node-server": "^2.1.5",
42
+ "commander": "^15.0.0",
43
+ "hono": "^4.13.13",
44
+ "undici": "^8.12.3",
45
+ "zod": "^4.6.7"
46
+ },
47
+ "devDependencies": {
48
+ "@kvbox/shared": "workspace:*"
49
+ }
50
+ }
@@ -0,0 +1,318 @@
1
+ #!/usr/bin/env bash
2
+ # Host networking for kvbox: bridge kvbox0 (172.30.0.1/24), IP forwarding, NAT to the
3
+ # default route, and FORWARD accept rules for the bridge (Docker sets FORWARD's policy
4
+ # to DROP, so NAT alone is not enough). Needs root; re-execs itself with sudo.
5
+ # Idempotent. The bridge and the rules vanish on host reboot, so this runs from
6
+ # install-server.sh once and from kvbox-server at start whenever the bridge is missing.
7
+ #
8
+ # net-setup.sh [up] create/repair everything
9
+ # net-setup.sh down remove the rules and the bridge (detaches any taps still on it)
10
+ # net-setup.sh status show what is in place; exit 1 if anything is missing
11
+ # net-setup.sh tap add <name> create tap <name> owned by the calling user, plugged into the bridge
12
+ # net-setup.sh tap add <name> detached the same, but up and not plugged in yet (a restored box keeps
13
+ # its old addresses until kvbox-agent changes them)
14
+ # net-setup.sh tap attach <name> plug a detached tap into the bridge
15
+ # net-setup.sh tap del <name> delete it
16
+ # net-setup.sh proxy add <box-id> <box-ip> <host-port> <guest-port> [tcp|udp]
17
+ # port <host-port> on this host's public side reaches <box-ip>:<guest-port>
18
+ # (DNAT + forward accept, rules tagged kvbox:<box-id>; tcp by default)
19
+ # net-setup.sh proxy rm <box-id> <box-ip> <host-port> <guest-port> [tcp|udp] remove exactly that
20
+ # net-setup.sh relay up <conf> [port] install <conf> (written by kvbox-server relay) as /etc/wireguard/kvbox.conf,
21
+ # keep the tunnel to the relay up, now and after reboots (wg-quick@kvbox), and
22
+ # let the control plane's HTTPS port (default 7777) in on the tunnel interface
23
+ # (a host firewall such as ufw would otherwise drop it; box ports are forwarded)
24
+ # net-setup.sh relay down take the tunnel down and remove the conf
25
+ # net-setup.sh relay status is the tunnel up, when did the relay last answer
26
+ #
27
+ # KVBOX_BRIDGE (default kvbox0) KVBOX_SUBNET (default 172.30.0.0/24)
28
+ # KVBOX_GATEWAY (default 172.30.0.1) the host's address on the bridge
29
+ set -euo pipefail
30
+
31
+ BRIDGE="${KVBOX_BRIDGE:-kvbox0}"
32
+ SUBNET="${KVBOX_SUBNET:-172.30.0.0/24}"
33
+ GATEWAY="${KVBOX_GATEWAY:-172.30.0.1}"
34
+ PREFIX="${SUBNET#*/}"
35
+ TAG="kvbox:$BRIDGE" # iptables comment; `down` finds our rules by it
36
+ action="${1:-up}"
37
+ tap_op="${2:-}"; tap_name="${3:-}"; tap_mode="${4:-}"
38
+ px_op="${2:-}"; px_box="${3:-}"; px_ip="${4:-}"; px_hport="${5:-}"; px_gport="${6:-}"; px_proto="${7:-tcp}"
39
+ relay_op="${2:-}"; relay_conf="${3:-}"; relay_port="${4:-7777}"
40
+ RELAY_IF=kvbox # wg-quick@kvbox reads /etc/wireguard/kvbox.conf
41
+
42
+ if [[ -t 1 ]]; then GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; DIM=$'\033[2m'; NC=$'\033[0m'; else GREEN=""; RED=""; DIM=""; NC=""; fi
43
+ ok() { printf '%s✓%s %s\n' "$GREEN" "$NC" "$*"; }
44
+ skip() { printf '%s–%s %s\n' "$DIM" "$NC" "$*"; }
45
+ fail() { printf '%s✗%s %s\n' "$RED" "$NC" "$*" >&2; exit 1; }
46
+
47
+ case "$action" in
48
+ up|down|status) ;;
49
+ tap) [[ "$tap_op" =~ ^(add|attach|del)$ && "$tap_name" =~ ^[a-z0-9][a-z0-9._-]{0,14}$ && "$tap_mode" =~ ^(|detached)$ ]] \
50
+ || fail "usage: $0 tap add <name> [detached] | tap attach <name> | tap del <name> (name: up to 15 chars, [a-z0-9._-])" ;;
51
+ proxy) [[ "$px_op" =~ ^(add|rm)$ && "$px_box" =~ ^[a-z0-9][a-z0-9._-]{0,14}$ && "$px_ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ \
52
+ && "$px_hport" =~ ^[0-9]{1,5}$ && "$px_gport" =~ ^[0-9]{1,5}$ && "$px_hport" -ne 22 && "$px_proto" =~ ^(tcp|udp)$ ]] \
53
+ || fail "usage: $0 proxy add|rm <box-id> <box-ip> <host-port> <guest-port> [tcp|udp] (host port is never 22)" ;;
54
+ relay) if [[ ! "$relay_op" =~ ^(up|down|status)$ ]] || { [[ "$relay_op" == up ]] && [[ -z "$relay_conf" ]]; } || [[ ! "$relay_port" =~ ^[0-9]{1,5}$ ]]; then
55
+ fail "usage: $0 relay up <conf> [port] | relay down | relay status"
56
+ fi ;;
57
+ *) fail "usage: $0 [up|down|status|tap add|attach|del <name>|proxy add|rm ...|relay up|down|status]" ;;
58
+ esac
59
+ for bin in ip iptables sysctl; do command -v "$bin" >/dev/null 2>&1 || fail "$bin not found"; done
60
+ if [[ "$action" == relay ]]; then
61
+ # The conf is read before sudo drops the caller's view of its home: resolve it while we are still that user.
62
+ [[ "$relay_op" != up ]] || [[ -r "$relay_conf" ]] || fail "cannot read $relay_conf"
63
+ [[ "$relay_op" != up ]] || relay_conf="$(readlink -f "$relay_conf")"
64
+ command -v wg-quick >/dev/null 2>&1 || fail "WireGuard is not installed on this host (apt install wireguard-tools), so it cannot use a relay"
65
+ command -v systemctl >/dev/null 2>&1 || fail "systemd is needed to keep the relay tunnel up across reboots"
66
+ fi
67
+ if [[ $EUID -ne 0 ]]; then
68
+ # From a terminal sudo may prompt; from kvbox-server (no tty) it must not hang.
69
+ # KVBOX_TAP_USER tells the root side who should own a new tap (Firecracker runs as that user).
70
+ export KVBOX_TAP_USER="${KVBOX_TAP_USER:-$(id -un)}"
71
+ if [[ -t 0 ]]; then exec sudo --preserve-env=KVBOX_TAP_USER,KVBOX_BRIDGE,KVBOX_SUBNET,KVBOX_GATEWAY "$0" "$@"; fi
72
+ exec sudo -n --preserve-env=KVBOX_TAP_USER,KVBOX_BRIDGE,KVBOX_SUBNET,KVBOX_GATEWAY "$0" "$@"
73
+ fi
74
+ if [[ "$action" == proxy ]]; then
75
+ command -v iptables-save >/dev/null 2>&1 || fail "iptables-save not found"
76
+ fi
77
+
78
+ # ── rule set ─────────────────────────────────────────────────────────────────
79
+ # RULE_DESC[i] is what we tell the user; RULE_SPEC[i] is "<table> <chain> <rule...>".
80
+ # Added with -I (ahead of Docker's chains), checked with -C, found again by the comment tag.
81
+ RULE_DESC=(
82
+ "NAT: boxes reach the internet using this host's address"
83
+ "firewall: traffic from boxes may leave through $BRIDGE"
84
+ "firewall: replies may come back to boxes"
85
+ )
86
+ RULE_SPEC=(
87
+ "nat POSTROUTING -s $SUBNET ! -o $BRIDGE -m comment --comment $TAG -j MASQUERADE"
88
+ "filter FORWARD -i $BRIDGE -m comment --comment $TAG -j ACCEPT"
89
+ "filter FORWARD -o $BRIDGE -m conntrack --ctstate RELATED,ESTABLISHED -m comment --comment $TAG -j ACCEPT"
90
+ )
91
+ has_rule() { local t=$1 c=$2; shift 2; iptables -t "$t" -C "$c" "$@" 2>/dev/null; }
92
+ add_rule() { local t=$1 c=$2; shift 2; iptables -t "$t" -I "$c" 1 "$@"; }
93
+ del_rule() { local t=$1 c=$2; shift 2; while iptables -t "$t" -C "$c" "$@" 2>/dev/null; do iptables -t "$t" -D "$c" "$@"; done; }
94
+
95
+ bridge_exists() { [[ -d "/sys/class/net/$BRIDGE" ]]; }
96
+ taps_on_bridge() { local t out=""; for t in "/sys/class/net/$BRIDGE/brif"/*; do [[ -e "$t" ]] && out+="${t##*/} "; done; printf '%s' "$out"; }
97
+ bridge_has_ip() { ip -4 -o addr show dev "$BRIDGE" 2>/dev/null | grep -q " inet $GATEWAY/$PREFIX "; }
98
+ # Administrative UP flag only: an empty bridge reports operstate "down" (NO-CARRIER) until a tap joins.
99
+ bridge_is_up() { ip link show "$BRIDGE" 2>/dev/null | head -1 | grep -q "[<,]UP[,>]"; }
100
+
101
+ # ── up ───────────────────────────────────────────────────────────────────────
102
+ do_up() {
103
+ if bridge_exists; then skip "bridge $BRIDGE already exists"; else ip link add "$BRIDGE" type bridge; ok "created bridge $BRIDGE (the virtual switch boxes plug into)"; fi
104
+ if bridge_has_ip; then skip "$BRIDGE already has address $GATEWAY"; else ip addr add "$GATEWAY/$PREFIX" dev "$BRIDGE"; ok "gave this host address $GATEWAY on $BRIDGE (boxes use it as their gateway)"; fi
105
+ if bridge_is_up; then skip "$BRIDGE is already up"; else ip link set "$BRIDGE" up; ok "brought $BRIDGE up"; fi
106
+ if [[ "$(sysctl -n net.ipv4.ip_forward)" == 1 ]]; then skip "IP forwarding already enabled"; else sysctl -q -w net.ipv4.ip_forward=1; ok "enabled IP forwarding (this host routes for the boxes)"; fi
107
+ local i
108
+ for i in "${!RULE_SPEC[@]}"; do
109
+ # shellcheck disable=SC2086 # the rule is a word list by design
110
+ if has_rule ${RULE_SPEC[$i]}; then skip "${RULE_DESC[$i]} (already set)"; else add_rule ${RULE_SPEC[$i]}; ok "${RULE_DESC[$i]}"; fi
111
+ done
112
+ echo
113
+ ok "host network ready: boxes get addresses in $SUBNET on $BRIDGE, gateway $GATEWAY, internet via NAT"
114
+ }
115
+
116
+ # ── down ─────────────────────────────────────────────────────────────────────
117
+ do_down() {
118
+ local i
119
+ for i in "${!RULE_SPEC[@]}"; do
120
+ # shellcheck disable=SC2086
121
+ if has_rule ${RULE_SPEC[$i]}; then del_rule ${RULE_SPEC[$i]}; ok "removed: ${RULE_DESC[$i]}"; else skip "not set: ${RULE_DESC[$i]}"; fi
122
+ done
123
+ if bridge_exists; then
124
+ local taps; taps="$(taps_on_bridge)"
125
+ [[ -z "$taps" ]] || printf '! detaching taps still on %s: %s\n' "$BRIDGE" "$taps"
126
+ ip link del "$BRIDGE"; ok "deleted bridge $BRIDGE"
127
+ else
128
+ skip "bridge $BRIDGE does not exist"
129
+ fi
130
+ # IP forwarding is left as is: other software (Docker) relies on it too.
131
+ local n=0 t rule
132
+ while IFS=$'\t' read -r t rule; do
133
+ [[ -n "$rule" ]] || continue
134
+ # shellcheck disable=SC2086 # iptables-save wrote the rule as a word list
135
+ eval "iptables -t $t -D $rule" && n=$((n + 1))
136
+ done < <(proxy_rules)
137
+ if [[ $n -gt 0 ]]; then ok "removed $n box port forward rule(s)"; else skip "no box port forwards"; fi
138
+ }
139
+
140
+ # ── status ───────────────────────────────────────────────────────────────────
141
+ do_status() {
142
+ local missing=0 i
143
+ bad() { printf '%s✗%s %s\n' "$RED" "$NC" "$*"; missing=1; }
144
+ if bridge_exists && bridge_has_ip && bridge_is_up; then ok "bridge $BRIDGE is up with address $GATEWAY/$PREFIX"
145
+ elif ! bridge_exists; then bad "bridge $BRIDGE does not exist"
146
+ elif ! bridge_has_ip; then bad "bridge $BRIDGE has no address $GATEWAY"
147
+ else bad "bridge $BRIDGE is down"; fi
148
+ if [[ "$(sysctl -n net.ipv4.ip_forward)" == 1 ]]; then ok "IP forwarding enabled"; else bad "IP forwarding disabled"; fi
149
+ for i in "${!RULE_SPEC[@]}"; do
150
+ # shellcheck disable=SC2086
151
+ if has_rule ${RULE_SPEC[$i]}; then ok "${RULE_DESC[$i]}"; else bad "missing: ${RULE_DESC[$i]}"; fi
152
+ done
153
+ local taps; taps="$(taps_on_bridge)"
154
+ if [[ -z "$taps" ]]; then skip "no boxes attached to $BRIDGE yet"; else ok "boxes attached to $BRIDGE: $taps"; fi
155
+ local fwds; fwds="$(proxy_summary)"
156
+ if [[ -z "$fwds" ]]; then skip "no box ports published on this host"; else ok "box ports published on this host:"; printf '%s\n' "$fwds"; fi
157
+ if [[ $missing -eq 0 ]]; then echo; ok "host network is ready"; else echo; printf '%s✗%s host network incomplete; run: kvbox-server net up\n' "$RED" "$NC"; fi
158
+ return $missing
159
+ }
160
+
161
+ # ── tap add|del ──────────────────────────────────────────────────────────────
162
+ # One tap per box: a virtual cable with one end in the VM and the other plugged into the bridge.
163
+ do_tap_add() {
164
+ local tap=$1 mode=${2:-} owner="${KVBOX_TAP_USER:-${SUDO_USER:-root}}"
165
+ bridge_exists || fail "bridge $BRIDGE does not exist; run: kvbox-server net up"
166
+ if [[ -d "/sys/class/net/$tap" ]]; then
167
+ skip "tap $tap already exists"
168
+ else
169
+ ip tuntap add dev "$tap" mode tap user "$owner"
170
+ ok "created tap $tap (owner $owner)"
171
+ fi
172
+ if [[ "$mode" == detached ]]; then
173
+ ip link set "$tap" up
174
+ ok "tap $tap is up, not plugged into $BRIDGE yet"
175
+ else
176
+ do_tap_attach "$tap"
177
+ fi
178
+ }
179
+ do_tap_attach() {
180
+ local tap=$1
181
+ [[ -d "/sys/class/net/$tap" ]] || fail "tap $tap does not exist"
182
+ ip link set "$tap" master "$BRIDGE" up
183
+ ok "tap $tap plugged into $BRIDGE"
184
+ }
185
+ do_tap_del() {
186
+ local tap=$1
187
+ if [[ -d "/sys/class/net/$tap" ]]; then ip link del "$tap"; ok "deleted tap $tap"; else skip "tap $tap does not exist"; fi
188
+ }
189
+
190
+ # ── proxy add|rm ─────────────────────────────────────────────────────────────
191
+ # A box's port on the host's public side (#49: SSH; #30: anything). Three rules per forward, all
192
+ # tagged kvbox:<box-id>: DNAT for traffic arriving from outside, DNAT for traffic the host itself
193
+ # sends to its own address (so the forward works from the host too), and a FORWARD accept so the
194
+ # new connection may enter the bridge (the base rules only let replies back in).
195
+ proxy_specs() {
196
+ local ip=$2 hport=$3 gport=$4 proto=${5:-tcp} tag="kvbox:$1"
197
+ printf '%s\n' \
198
+ "nat PREROUTING ! -i $BRIDGE -p $proto --dport $hport -m comment --comment $tag -j DNAT --to-destination $ip:$gport" \
199
+ "nat OUTPUT -p $proto --dport $hport -m addrtype --dst-type LOCAL -m comment --comment $tag -j DNAT --to-destination $ip:$gport" \
200
+ "filter FORWARD -o $BRIDGE -d $ip -p $proto --dport $gport -m comment --comment $tag -j ACCEPT"
201
+ }
202
+ do_proxy_add() {
203
+ local spec added=0
204
+ while read -r spec; do
205
+ # shellcheck disable=SC2086
206
+ if ! has_rule $spec; then add_rule $spec; added=1; fi
207
+ done < <(proxy_specs "$@")
208
+ if [[ $added -eq 1 ]]; then ok "$5 port $3 on this host now reaches box $1's port $4"; else skip "$5 port $3 on this host already reaches box $1's port $4"; fi
209
+ }
210
+ do_proxy_rm() {
211
+ local spec removed=0
212
+ while read -r spec; do
213
+ # shellcheck disable=SC2086
214
+ if has_rule $spec; then del_rule $spec; removed=1; fi
215
+ done < <(proxy_specs "$@")
216
+ if [[ $removed -eq 1 ]]; then ok "$5 port $3 on this host no longer reaches box $1"; else skip "$5 port $3 on this host was not forwarding to box $1"; fi
217
+ }
218
+ # Every forward rule in place, as "<table>\t<rule without -A>", found by its kvbox:<box-id> tag
219
+ # (the bridge's own rules carry kvbox:<bridge> and are not forwards).
220
+ proxy_rules() {
221
+ local t
222
+ for t in nat filter; do
223
+ iptables-save -t "$t" 2>/dev/null | sed -n 's/^-A //p' | sed 's/--comment "\([^"]*\)"/--comment \1/' \
224
+ | grep -E -- "--comment kvbox:[a-z0-9._-]+( |$)" | grep -v -F -- "--comment $TAG " | sed "s/^/$t\t/" || true
225
+ done
226
+ }
227
+ # One line per published port: "port 31207 -> box a3f9c1 (172.30.0.2:22)".
228
+ proxy_summary() {
229
+ proxy_rules | grep -F "PREROUTING" | sed -E 's/.*-p ([a-z]+) .*--dport ([0-9]+) .*--comment kvbox:([^ ]+) .*--to-destination ([0-9.:]+).*/ \1 port \2 -> box \3 (\4)/'
230
+ }
231
+
232
+ # ── relay up|down|status ─────────────────────────────────────────────────────
233
+ # The host's end of the tunnel to its relay (#50). kvbox-server writes the wg-quick conf (private
234
+ # key inside) under its state dir; root copies it to /etc/wireguard and lets systemd keep the tunnel
235
+ # up. Nothing here knows the relay's rules: the relay forwards its public IP down to us, and SNATs,
236
+ # so the only route we need is the relay's own tunnel address (in the conf's AllowedIPs).
237
+ relay_unit="wg-quick@$RELAY_IF"
238
+ relay_target="/etc/wireguard/$RELAY_IF.conf"
239
+ # Only the control plane's calls arrive for this host itself; box ports are DNAT'd and go through
240
+ # FORWARD, where the bridge rules already let them in ahead of any firewall chain.
241
+ relay_input_spec() { printf '%s' "filter INPUT -i $RELAY_IF -p tcp --dport $1 -m comment --comment kvbox:relay -j ACCEPT"; }
242
+ relay_input_rules() { iptables-save -t filter 2>/dev/null | sed -n 's/^-A //p' | sed 's/--comment "\([^"]*\)"/--comment \1/' | grep -F -- "--comment kvbox:relay " || true; }
243
+ do_relay_up() {
244
+ local src=$1 port=$2
245
+ install -d -m 0700 /etc/wireguard
246
+ if [[ -f "$relay_target" ]] && cmp -s "$src" "$relay_target"; then
247
+ skip "tunnel conf already installed"
248
+ else
249
+ install -m 0600 -o root -g root "$src" "$relay_target"
250
+ ok "installed the tunnel conf as $relay_target (root only)"
251
+ fi
252
+ if systemctl is-active --quiet "$relay_unit"; then
253
+ # Re-read the conf without dropping the tunnel: a changed relay key or endpoint takes effect now.
254
+ if wg syncconf "$RELAY_IF" <(wg-quick strip "$RELAY_IF") 2>/dev/null; then ok "tunnel $RELAY_IF reloaded"; else systemctl restart "$relay_unit"; ok "tunnel $RELAY_IF restarted"; fi
255
+ else
256
+ systemctl enable --now "$relay_unit" >/dev/null 2>&1 || fail "could not bring the tunnel up (journalctl -u $relay_unit)"
257
+ ok "tunnel $RELAY_IF is up and comes back after a reboot"
258
+ fi
259
+ local spec; spec="$(relay_input_spec "$port")"
260
+ # shellcheck disable=SC2086 # the rule is a word list by design
261
+ if has_rule $spec; then skip "the control plane may already reach port $port through the tunnel"; else add_rule $spec; ok "firewall: the control plane may reach port $port through the tunnel"; fi
262
+ # A rule for another port from an earlier `relay up` would stay open for nothing.
263
+ local rule
264
+ while IFS= read -r rule; do
265
+ [[ -n "$rule" ]] || continue
266
+ [[ "$rule" == *"--dport $port "* ]] && continue
267
+ # shellcheck disable=SC2086 # iptables-save wrote the rule as a word list
268
+ eval "iptables -t filter -D $rule" && ok "firewall: closed the tunnel's old port $(sed -E 's/.*--dport ([0-9]+) .*/\1/' <<<"$rule")"
269
+ done < <(relay_input_rules)
270
+ }
271
+ do_relay_down() {
272
+ local rule n=0
273
+ while IFS= read -r rule; do
274
+ [[ -n "$rule" ]] || continue
275
+ # shellcheck disable=SC2086
276
+ eval "iptables -t filter -D $rule" && n=$((n + 1))
277
+ done < <(relay_input_rules)
278
+ if [[ $n -gt 0 ]]; then ok "firewall: the tunnel's port is closed again"; fi
279
+ if systemctl is-enabled --quiet "$relay_unit" 2>/dev/null || systemctl is-active --quiet "$relay_unit"; then
280
+ systemctl disable --now "$relay_unit" >/dev/null 2>&1 || true
281
+ ok "tunnel $RELAY_IF is down"
282
+ else
283
+ skip "tunnel $RELAY_IF was not up"
284
+ fi
285
+ if [[ -f "$relay_target" ]]; then rm -f "$relay_target"; ok "removed $relay_target"; else skip "no tunnel conf installed"; fi
286
+ }
287
+ do_relay_status() {
288
+ [[ -f "$relay_target" ]] || { skip "no relay tunnel configured on this host"; return 1; }
289
+ if ! systemctl is-active --quiet "$relay_unit"; then printf '%s✗%s tunnel %s is down (journalctl -u %s)\n' "$RED" "$NC" "$RELAY_IF" "$relay_unit"; return 1; fi
290
+ local hs; hs="$(wg show "$RELAY_IF" latest-handshakes 2>/dev/null | awk '{print $2}' | head -1)"
291
+ if [[ -z "$hs" || "$hs" == 0 ]]; then
292
+ printf '%s!%s tunnel %s is up but the relay has not answered yet\n' "$RED" "$NC" "$RELAY_IF"; return 1
293
+ fi
294
+ ok "tunnel $RELAY_IF is up; the relay last answered $(( $(date +%s) - hs )) s ago"
295
+ local rules; rules="$(relay_input_rules)"
296
+ if [[ -z "$rules" ]]; then printf '%s✗%s the control plane cannot reach this host through the tunnel (no firewall rule); rerun kvbox server relay\n' "$RED" "$NC"; return 1; fi
297
+ ok "firewall: the control plane may reach port $(sed -E 's/.*--dport ([0-9]+) .*/\1/' <<<"$rules" | head -1) through the tunnel"
298
+ }
299
+
300
+ case "$action" in
301
+ up) do_up ;;
302
+ down) do_down ;;
303
+ status) do_status ;;
304
+ tap) case "$tap_op" in
305
+ add) do_tap_add "$tap_name" "$tap_mode" ;;
306
+ attach) do_tap_attach "$tap_name" ;;
307
+ del) do_tap_del "$tap_name" ;;
308
+ esac ;;
309
+ proxy) case "$px_op" in
310
+ add) do_proxy_add "$px_box" "$px_ip" "$px_hport" "$px_gport" "$px_proto" ;;
311
+ rm) do_proxy_rm "$px_box" "$px_ip" "$px_hport" "$px_gport" "$px_proto" ;;
312
+ esac ;;
313
+ relay) case "$relay_op" in
314
+ up) do_relay_up "$relay_conf" "$relay_port" ;;
315
+ down) do_relay_down ;;
316
+ status) do_relay_status ;;
317
+ esac ;;
318
+ esac
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.