@ssdavidai/zoclaw 1.0.0

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 ADDED
@@ -0,0 +1,79 @@
1
+ # zoclaw
2
+
3
+ Set up [OpenClaw](https://openclaw.ai) on a [Zo](https://zo.computer) instance with Tailscale access in one command.
4
+
5
+ ## Quick start
6
+
7
+ On a fresh Zo instance:
8
+
9
+ ```bash
10
+ npm install -g @ssdavidai/zoclaw && zoclaw init
11
+ ```
12
+
13
+ Or via git:
14
+
15
+ ```bash
16
+ git clone https://github.com/ssdavidai/zoclaw.git
17
+ cd zoclaw
18
+ ./setup.sh
19
+ ```
20
+
21
+ That's it. The setup script walks you through everything:
22
+
23
+ 1. Prompts for your Tailscale auth key (or uses the one already in zo secrets)
24
+ 2. Installs and configures Tailscale via [zotail](https://github.com/ssdavidai/zotail)
25
+ 3. Installs OpenClaw
26
+ 4. Runs the OpenClaw onboarding wizard
27
+ 5. Patches the config for secure Tailscale access
28
+ 6. Prints your Control UI URL and offers to launch the TUI
29
+
30
+ ### After setup
31
+
32
+ On first browser load, the Control UI will request device pairing. Approve it once from the CLI:
33
+
34
+ ```bash
35
+ openclaw devices list
36
+ openclaw devices approve <request-id>
37
+ ```
38
+
39
+ After that, the browser is permanently paired.
40
+
41
+ ## Why this exists
42
+
43
+ After a fresh `openclaw configure` on Zo, the default gateway config doesn't work with Tailscale. You'll hit a series of issues:
44
+
45
+ 1. **"requires HTTPS or localhost"** -- Tailscale Serve terminates TLS externally and proxies to the gateway as plain HTTP on loopback. The gateway sees a localhost socket but a non-local `Host` header (your `.ts.net` hostname), so it treats the connection as remote and rejects it.
46
+
47
+ 2. **"device identity required"** -- The Control UI in the browser needs to complete device pairing, but the gateway doesn't recognize the browser as a trusted client without proper proxy configuration.
48
+
49
+ 3. **CLI pairing scope mismatch** -- The initial onboarding pairs the CLI with read-only scopes (`operator.read`), but the CLI needs full admin scopes (`operator.admin`, `operator.approvals`, `operator.pairing`) to function.
50
+
51
+ 4. **Security audit failures** -- The default config ships with invalid `denyCommands` entries and overly permissive credentials directory permissions.
52
+
53
+ ## What the bootstrap patches
54
+
55
+ | Issue | Fix |
56
+ |---|---|
57
+ | Gateway doesn't trust Tailscale Serve | Sets `gateway.auth.allowTailscale: true` |
58
+ | `.ts.net` Host header rejected as remote | Sets `gateway.trustedProxies: ["127.0.0.1/32"]` so the gateway trusts Tailscale Serve's forwarded headers |
59
+ | Control UI not enabled | Sets `gateway.controlUi.enabled: true` |
60
+ | CLI paired with read-only scopes | Upgrades paired device scopes to full admin |
61
+ | Invalid `denyCommands` entries | Removes the ineffective default entries |
62
+ | Credentials dir readable by others | `chmod 700 ~/.openclaw/credentials` |
63
+
64
+ The script does **not** set `allowInsecureAuth` or `dangerouslyDisableDeviceAuth` -- those are insecure workarounds. Instead, it configures `trustedProxies` so the gateway properly recognizes Tailscale Serve connections as secure, and the browser goes through proper Ed25519 device pairing.
65
+
66
+ ## Scripts
67
+
68
+ | Script | Purpose |
69
+ |---|---|
70
+ | `zoclaw init` | Full setup from scratch (Tailscale + OpenClaw + bootstrap) |
71
+ | `zoclaw bootstrap` | Config patches only (if OpenClaw and Tailscale are already installed) |
72
+
73
+ ## Security
74
+
75
+ Running `openclaw security audit` after setup should show **0 critical findings**. The setup uses `trustedProxies` + proper device pairing instead of insecure bypasses.
76
+
77
+ ## License
78
+
79
+ MIT
package/bin/zoclaw.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ const { execFileSync } = require("child_process");
3
+ const path = require("path");
4
+
5
+ const command = process.argv[2];
6
+ const scriptsDir = path.join(__dirname, "..", "scripts");
7
+
8
+ const commands = {
9
+ init: "setup.sh",
10
+ bootstrap: "bootstrap.sh",
11
+ };
12
+
13
+ if (!command || !commands[command]) {
14
+ console.log("Usage: zoclaw <command>\n");
15
+ console.log("Commands:");
16
+ console.log(" init Full setup (Tailscale + OpenClaw + bootstrap)");
17
+ console.log(" bootstrap Config patches only (if already installed)");
18
+ process.exit(command ? 1 : 0);
19
+ }
20
+
21
+ const script = path.join(scriptsDir, commands[command]);
22
+
23
+ try {
24
+ execFileSync("bash", [script], { stdio: "inherit" });
25
+ } catch (err) {
26
+ process.exit(err.status ?? 1);
27
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@ssdavidai/zoclaw",
3
+ "version": "1.0.0",
4
+ "description": "Set up OpenClaw on Zo with Tailscale access in one command",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ssdavidai/zoclaw"
9
+ },
10
+ "bin": {
11
+ "zoclaw": "./bin/zoclaw.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "scripts/",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "openclaw",
20
+ "zo",
21
+ "tailscale",
22
+ "zotail"
23
+ ]
24
+ }
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # openclaw-tailscale-bootstrap.sh
3
+ # Run AFTER: zotail is configured + `openclaw configure` has been run.
4
+ # Patches the default openclaw config so the gateway is reachable
5
+ # via Tailscale (both TUI and Control UI in browser).
6
+ set -euo pipefail
7
+
8
+ CONFIG="${HOME}/.openclaw/openclaw.json"
9
+ PAIRED="${HOME}/.openclaw/devices/paired.json"
10
+ PENDING="${HOME}/.openclaw/devices/pending.json"
11
+
12
+ if [ ! -f "$CONFIG" ]; then
13
+ echo "Error: $CONFIG not found. Run 'openclaw configure' first."
14
+ exit 1
15
+ fi
16
+
17
+ echo "Patching openclaw config for Tailscale access..."
18
+
19
+ # Patch gateway config
20
+ node -e "
21
+ const fs = require('fs');
22
+ const cfg = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
23
+ const gw = cfg.gateway ??= {};
24
+
25
+ // Trust Tailscale identity headers on serve connections
26
+ gw.auth ??= {};
27
+ gw.auth.allowTailscale = true;
28
+
29
+ // Enable control UI
30
+ gw.controlUi ??= {};
31
+ gw.controlUi.enabled = true;
32
+
33
+ // Remove invalid denyCommands entries (default config generates
34
+ // names that don't match real command IDs, triggering audit warnings)
35
+ if (gw.nodes?.denyCommands) delete gw.nodes.denyCommands;
36
+
37
+ // Trust localhost as a reverse proxy (Tailscale serve proxies
38
+ // HTTPS -> HTTP on loopback and forwards X-Forwarded-For).
39
+ // This lets the gateway recognize .ts.net Host headers as local.
40
+ gw.trustedProxies = ['127.0.0.1/32'];
41
+
42
+ // Fix credentials dir permissions
43
+ fs.chmodSync(process.env.HOME + '/.openclaw/credentials', 0o700);
44
+
45
+ fs.writeFileSync(process.argv[1], JSON.stringify(cfg, null, 2) + '\n');
46
+ " "$CONFIG"
47
+
48
+ echo " gateway.auth.allowTailscale = true"
49
+ echo " gateway.controlUi.enabled = true"
50
+ echo " gateway.trustedProxies = [\"127.0.0.1/32\"]"
51
+ echo " nodes.denyCommands -> removed (invalid defaults)"
52
+ echo " credentials dir -> 700"
53
+
54
+ # Upgrade any existing paired devices to full admin scopes
55
+ if [ -f "$PAIRED" ]; then
56
+ node -e "
57
+ const fs = require('fs');
58
+ const paired = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
59
+ const scopes = ['operator.read','operator.admin','operator.approvals','operator.pairing'];
60
+ for (const dev of Object.values(paired)) {
61
+ dev.clientId = 'cli';
62
+ dev.clientMode = 'cli';
63
+ dev.scopes = scopes;
64
+ for (const tok of Object.values(dev.tokens ?? {})) tok.scopes = scopes;
65
+ }
66
+ fs.writeFileSync(process.argv[1], JSON.stringify(paired, null, 2) + '\n');
67
+ " "$PAIRED"
68
+ echo " Upgraded paired device scopes to full admin"
69
+ fi
70
+
71
+ # Clear stale pairing requests
72
+ [ -f "$PENDING" ] && echo '{}' > "$PENDING"
73
+
74
+ # Restart gateway
75
+ echo "Restarting gateway..."
76
+ pkill -f openclaw-gateway 2>/dev/null || true
77
+ sleep 2
78
+ openclaw gateway run --force > /dev/null 2>&1 &
79
+ sleep 5
80
+
81
+ # Verify
82
+ if pgrep -f openclaw-gateway > /dev/null 2>&1; then
83
+ TOKEN=$(node -pe "JSON.parse(require('fs').readFileSync('${CONFIG}','utf8')).gateway?.auth?.token ?? ''")
84
+ TS_URL=$(tailscale serve status 2>/dev/null | grep -oP 'https://\S+' | head -1 || true)
85
+
86
+ echo ""
87
+ echo "Ready!"
88
+ echo " TUI: openclaw tui"
89
+ if [ -n "$TS_URL" ] && [ -n "$TOKEN" ]; then
90
+ echo " Browser: ${TS_URL}?token=${TOKEN}"
91
+ echo ""
92
+ echo " On first browser load, the Control UI will request device pairing."
93
+ echo " Approve it from the TUI or CLI:"
94
+ echo " openclaw devices list"
95
+ echo " openclaw devices approve <request-id>"
96
+ fi
97
+ else
98
+ echo "Warning: gateway did not start. Check 'openclaw gateway run --force' manually."
99
+ fi
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bash
2
+ # setup.sh — Full OpenClaw + Tailscale setup for Zo
3
+ # Installs everything from scratch on a fresh Zo instance.
4
+ set -euo pipefail
5
+
6
+ SECRETS_FILE="${HOME}/.zo_secrets"
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ BOOTSTRAP="${SCRIPT_DIR}/bootstrap.sh"
9
+
10
+ # ─── Helpers ──────────────────────────────────────────────────────────
11
+
12
+ step() { echo -e "\n\033[1;36m[$1/5]\033[0m \033[1m$2\033[0m"; }
13
+
14
+ ensure_secrets_file() {
15
+ if [ ! -f "$SECRETS_FILE" ]; then
16
+ touch "$SECRETS_FILE"
17
+ chmod 600 "$SECRETS_FILE"
18
+ fi
19
+ }
20
+
21
+ # ─── Step 1: Tailscale auth key ──────────────────────────────────────
22
+
23
+ step 1 "Tailscale auth key"
24
+
25
+ ensure_secrets_file
26
+ source "$SECRETS_FILE" 2>/dev/null || true
27
+
28
+ if [ -n "${TAILSCALE_AUTH_KEY:-}" ]; then
29
+ echo " Found TAILSCALE_AUTH_KEY in zo secrets, using existing key."
30
+ else
31
+ echo " No TAILSCALE_AUTH_KEY found in zo secrets."
32
+ echo " Generate one at: https://login.tailscale.com/admin/settings/keys"
33
+ echo ""
34
+ read -rp " Enter your Tailscale auth key: " ts_key
35
+ if [ -z "$ts_key" ]; then
36
+ echo " Error: auth key cannot be empty."
37
+ exit 1
38
+ fi
39
+ echo "export TAILSCALE_AUTH_KEY=\"${ts_key}\"" >> "$SECRETS_FILE"
40
+ export TAILSCALE_AUTH_KEY="$ts_key"
41
+ echo " Saved to zo secrets."
42
+ fi
43
+
44
+ # ─── Step 2: Install and configure Tailscale via zotail ──────────────
45
+
46
+ step 2 "Tailscale (zotail)"
47
+
48
+ echo " Installing zotail..."
49
+ npm install -g @ssdavidai/zotail 2>&1 | tail -1
50
+
51
+ echo " Running zotail setup..."
52
+ zotail setup
53
+
54
+ # ─── Step 3: Install OpenClaw ────────────────────────────────────────
55
+
56
+ step 3 "OpenClaw"
57
+
58
+ echo " Installing openclaw..."
59
+ npm install -g openclaw@latest 2>&1 | tail -1
60
+
61
+ # ─── Step 4: OpenClaw onboarding ─────────────────────────────────────
62
+
63
+ step 4 "OpenClaw onboarding"
64
+
65
+ echo " Running interactive setup..."
66
+ echo " (When the onboarding finishes, setup will continue automatically.)"
67
+ echo ""
68
+ openclaw onboard --install-daemon
69
+
70
+ # ─── Step 5: Bootstrap Tailscale config ──────────────────────────────
71
+
72
+ step 5 "Tailscale bootstrap"
73
+
74
+ if [ ! -f "$BOOTSTRAP" ]; then
75
+ echo " Error: bootstrap.sh not found at ${BOOTSTRAP}"
76
+ exit 1
77
+ fi
78
+
79
+ bash "$BOOTSTRAP"
80
+
81
+ # ─── Done ────────────────────────────────────────────────────────────
82
+
83
+ CONFIG="${HOME}/.openclaw/openclaw.json"
84
+ TOKEN=$(node -pe "JSON.parse(require('fs').readFileSync('${CONFIG}','utf8')).gateway?.auth?.token ?? ''" 2>/dev/null || true)
85
+ TS_URL=$(tailscale serve status 2>/dev/null | grep -oP 'https://\S+' | head -1 || true)
86
+
87
+ echo ""
88
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
89
+ echo ""
90
+ echo " Setup complete!"
91
+ echo ""
92
+ if [ -n "$TS_URL" ] && [ -n "$TOKEN" ]; then
93
+ echo " Control UI: ${TS_URL}?token=${TOKEN}"
94
+ echo ""
95
+ echo " (On first browser load, approve device pairing from the CLI:"
96
+ echo " openclaw devices list && openclaw devices approve <id>)"
97
+ fi
98
+ echo ""
99
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
100
+ echo ""
101
+
102
+ read -rp " Launch the TUI now? [Y/n] " launch_tui
103
+ launch_tui="${launch_tui:-Y}"
104
+
105
+ if [[ "$launch_tui" =~ ^[Yy]$ ]]; then
106
+ exec openclaw tui
107
+ else
108
+ echo " Run 'openclaw tui' whenever you're ready."
109
+ fi