fix: harden 0.6.1 runtime checks
This commit is contained in:
parent
98af5a3e8f
commit
f4695953bc
@ -1,10 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
## 0.6.1 - Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Tailscale exit-node bootstrap paths to prefer tailnet metadata and fail clearly when remote exit-node egress is not active.
|
||||
- Fixed `run --no-sync` timing summaries so they report `sync_skipped=true`.
|
||||
|
||||
## 0.6.0 - 2026-05-07
|
||||
|
||||
|
||||
@ -401,13 +401,38 @@ func validateTailscaleExitNodeEgress(ctx context.Context, server Server, target
|
||||
if strings.TrimSpace(meta.ExitNode) == "" {
|
||||
return nil
|
||||
}
|
||||
command := `set -eu
|
||||
prefs="$(tailscale debug prefs 2>/dev/null || true)"
|
||||
command := tailscaleExitNodeEgressCheckScript()
|
||||
if out, err := runSSHCombinedOutput(ctx, target, command); err != nil {
|
||||
detail := strings.TrimSpace(out)
|
||||
if detail == "" {
|
||||
detail = err.Error()
|
||||
}
|
||||
return exit(5, "tailscale exit node %s joined but remote internet egress failed; verify the exit node is approved and forwarding internet traffic: %s", meta.ExitNode, detail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tailscaleExitNodeEgressCheckScript() string {
|
||||
return `set -eu
|
||||
if ! command -v tailscale >/dev/null 2>&1; then
|
||||
printf '%s\n' "tailscale is not installed for exit-node egress check" >&2
|
||||
exit 87
|
||||
fi
|
||||
prefs="$(tailscale debug prefs 2>/dev/null)" || {
|
||||
printf '%s\n' "tailscale prefs unavailable for exit-node egress check" >&2
|
||||
exit 88
|
||||
}
|
||||
case "$prefs" in
|
||||
*'"ExitNodeID": ""'*|*'"ExitNodeID":""'*)
|
||||
printf '%s\n' "exit node is not selected in tailscale prefs" >&2
|
||||
exit 86
|
||||
;;
|
||||
*'"ExitNodeID":'*)
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "tailscale prefs did not include ExitNodeID" >&2
|
||||
exit 89
|
||||
;;
|
||||
esac
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
timeout 12 sh -c 'curl -4fsS --connect-timeout 5 https://ifconfig.me/ip || curl -4fsS --connect-timeout 5 https://icanhazip.com' >/tmp/crabbox-exit-node-ip
|
||||
@ -417,12 +442,4 @@ else
|
||||
fi
|
||||
test -s /tmp/crabbox-exit-node-ip
|
||||
`
|
||||
if out, err := runSSHCombinedOutput(ctx, target, command); err != nil {
|
||||
detail := strings.TrimSpace(out)
|
||||
if detail == "" {
|
||||
detail = err.Error()
|
||||
}
|
||||
return exit(5, "tailscale exit node %s joined but remote internet egress failed; verify the exit node is approved and forwarding internet traffic: %s", meta.ExitNode, detail)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -65,6 +66,24 @@ func TestBootstrapNetworkHonorsExplicitPublic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTailscaleExitNodeEgressCheckFailsClosed(t *testing.T) {
|
||||
script := tailscaleExitNodeEgressCheckScript()
|
||||
for _, want := range []string{
|
||||
"command -v tailscale",
|
||||
"tailscale debug prefs",
|
||||
"tailscale prefs unavailable",
|
||||
"tailscale prefs did not include ExitNodeID",
|
||||
"exit node is not selected",
|
||||
} {
|
||||
if !strings.Contains(script, want) {
|
||||
t.Fatalf("egress check script missing %q:\n%s", want, script)
|
||||
}
|
||||
}
|
||||
if strings.Contains(script, "debug prefs 2>/dev/null || true") {
|
||||
t.Fatalf("egress check script must not ignore tailscale prefs failures:\n%s", script)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTailscaleHostname(t *testing.T) {
|
||||
got := renderTailscaleHostname("CBX-{slug}-{provider}-{id}", "cbx_abcdef123456", "Blue Lobster", "aws")
|
||||
if got != "cbx-blue-lobster-aws-cbx-abcdef123456" {
|
||||
|
||||
@ -464,6 +464,7 @@ func (a App) runCommand(ctx context.Context, args []string) (err error) {
|
||||
fmt.Fprintf(a.Stderr, "sync complete in %s\n", timings.sync.Round(time.Millisecond))
|
||||
recorder.Event("sync.finished", "synced", fmt.Sprintf("duration=%s skipped=false", timings.sync.Round(time.Millisecond)))
|
||||
} else {
|
||||
timings.syncSkipped = true
|
||||
recorder.Event("sync.finished", "synced", "skipped by --no-sync")
|
||||
}
|
||||
afterSync:
|
||||
|
||||
@ -48,6 +48,21 @@ func TestFormatRunSummaryIncludesGitHydrateSkipReason(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatRunSummaryNoSync(t *testing.T) {
|
||||
got := formatRunSummary(runTimings{
|
||||
syncSkipped: true,
|
||||
}, 500*time.Millisecond, 0)
|
||||
for _, want := range []string{
|
||||
"sync=0s",
|
||||
"sync_skipped=true",
|
||||
"exit=0",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("summary missing %q in %q", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimingJSONShape(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
err := writeTimingJSON(&buf, timingReportFromRun("aws", "cbx_123", "blue-crab", runTimings{
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
package cli
|
||||
|
||||
var version = "0.6.0"
|
||||
var version = "0.6.1"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/crabbox-plugin",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"description": "OpenClaw plugin for running Crabbox remote testbox workflows",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
4
worker/package-lock.json
generated
4
worker/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/crabbox-worker",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@openclaw/crabbox-worker",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"dependencies": {
|
||||
"@novnc/novnc": "1.6.0",
|
||||
"aws4fetch": "^1.0.20",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/crabbox-worker",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user