@rubytech/create-maxy 1.0.623 → 1.0.624
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 +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +9 -12
- package/payload/platform/plugins/cloudflare/PLUGIN.md +31 -44
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +13 -875
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +1 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
- package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +108 -0
- package/payload/platform/plugins/cloudflare/references/manual-setup.md +445 -0
- package/payload/platform/plugins/cloudflare/references/reset-guide.md +118 -0
- package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +65 -0
- package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +244 -0
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +96 -5
- package/payload/platform/plugins/docs/references/cloudflare.md +91 -34
- package/payload/platform/templates/agents/admin/IDENTITY.md +10 -4
- package/payload/platform/templates/specialists/agents/personal-assistant.md +9 -9
- package/payload/server/server.js +187 -299
- package/payload/platform/config/cloudflared.yml +0 -17
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +0 -132
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
# Manual Cloudflare tunnel setup — human runbook
|
|
2
|
+
|
|
3
|
+
Step-by-step recovery path for when the Maxy automation fails or is unavailable. Every command below is isolated in its own code block — nothing to parse from prose. Every path is brand-scoped so multiple brands can coexist on the same Linux user.
|
|
4
|
+
|
|
5
|
+
Prerequisites: `cloudflared` installed; VNC running on `DISPLAY=:99`; SSH access to the device.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why two hostnames (admin and public)
|
|
10
|
+
|
|
11
|
+
Each Maxy installation exposes two distinct surfaces from the same device and the same local port. The platform UI distinguishes them by the inbound `Host:` header.
|
|
12
|
+
|
|
13
|
+
- **admin hostname** — the operator's private chat + admin panel. PIN-protected. Only the owner uses it.
|
|
14
|
+
- **public hostname** — the public-facing agent (customer chat, marketing agent, WhatsApp webhook target). Reachable by anyone on the internet.
|
|
15
|
+
|
|
16
|
+
Two tunnel hostnames, one connector, one local service. The tunnel's `ingress:` block routes each hostname to `http://localhost:${PORT}`; the platform UI handles the rest.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Two modes — which are you in?
|
|
21
|
+
|
|
22
|
+
### Mode A — you own the domain (this runbook)
|
|
23
|
+
|
|
24
|
+
You have a Cloudflare account and a domain already on that account. You run every step below on your own device using your own cert.pem. Your hostnames are:
|
|
25
|
+
|
|
26
|
+
- `admin.<yourdomain>`
|
|
27
|
+
- `public.<yourdomain>`
|
|
28
|
+
|
|
29
|
+
### Mode B — you don't own a domain (Maxy-provided subdomain)
|
|
30
|
+
|
|
31
|
+
First-time users without a Cloudflare account or domain use a subdomain of the Maxy-operated domain (`maxy.bot`). Maxy owns the CF account and issues each user a token for a pre-created tunnel. Hostnames are:
|
|
32
|
+
|
|
33
|
+
- `<username>.maxy.bot` — admin surface (e.g. `neo.maxy.bot`, `joel.maxy.bot`)
|
|
34
|
+
- public surface is optional and, when enabled, uses a second Maxy-issued subdomain.
|
|
35
|
+
|
|
36
|
+
Mode B skips Steps 1–5 of this runbook entirely — the user receives a token from Maxy and only runs Step 6 (the connector) with `--token <X>`. Mode B is documented separately in `managed-tunnel-setup.md`.
|
|
37
|
+
|
|
38
|
+
**The rest of this runbook covers Mode A only.**
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Automation — setup-tunnel.sh and reset-tunnel.sh
|
|
43
|
+
|
|
44
|
+
The manual walkthrough below exists so an operator can execute every step by hand when the system is broken or the automation is absent. For a normal setup — and for validating that the runbook's steps produce a working tunnel end-to-end — use the two scripts at `platform/plugins/cloudflare/scripts/`:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
setup-tunnel.sh <brand> <port> <hostname> [<hostname> ...]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
~/setup-tunnel.sh maxy 19200 admin.maxy.bot public.maxy.bot maxy.chat
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Mirrors this runbook verbatim (Steps 0–5b), calls `systemctl --user restart "${BRAND}.service"` at the end to spawn the connector via `resume-tunnel.sh`, then polls each subdomain hostname (up to 60s per host) for a non-530 response. Apex hostnames cannot be routed via CLI (see §Step 4); the script prints an explicit ACTION REQUIRED block naming the dashboard record to edit or add.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
reset-tunnel.sh <brand>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
~/reset-tunnel.sh maxy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Deletes every tunnel on the brand's Cloudflare account (via the brand's cert) and wipes `${CFG_DIR}`. Does **not** touch the platform service or any stray misrouted CNAMEs in DNS — those require a manual dashboard delete.
|
|
69
|
+
|
|
70
|
+
After manual transfer to a device, run `chmod +x ~/setup-tunnel.sh ~/reset-tunnel.sh` before the first invocation.
|
|
71
|
+
|
|
72
|
+
**Walk through manually (instead of scripting) when:**
|
|
73
|
+
- Diagnosing a step that's failing under the script.
|
|
74
|
+
- Recovering a partial state where the scripts assume a clean start.
|
|
75
|
+
- Validating runbook changes before re-baking them into the scripts.
|
|
76
|
+
- Working on a device where the scripts aren't deployed yet.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Step 0 — Set brand context
|
|
81
|
+
|
|
82
|
+
Run this once per shell session. Every subsequent step reads these variables. The `BRAND` value determines the on-disk directory (`${HOME}/.${BRAND}/cloudflared/`) so multiple brands on the same Linux user do not collide.
|
|
83
|
+
|
|
84
|
+
For Maxy:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
export BRAND=maxy
|
|
88
|
+
export PORT=19200
|
|
89
|
+
export CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
90
|
+
mkdir -p "${CFG_DIR}"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For Real Agent:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
export BRAND=realagent
|
|
97
|
+
export PORT=19500
|
|
98
|
+
export CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
99
|
+
mkdir -p "${CFG_DIR}"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Step 1 — OAuth login
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
DISPLAY=:99 cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel login
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Why:** Produces the account-level credential that authorises creating tunnels and routing DNS. One-time per device per brand per account.
|
|
111
|
+
|
|
112
|
+
**Success:** VNC browser opens on the "Authorize Cloudflare Tunnel" page. Click any zone. Terminal prints `You have successfully logged in.`
|
|
113
|
+
|
|
114
|
+
**`cloudflared` quirk — cert.pem lands at the default path regardless of `--origincert`.** The login subcommand always writes to `~/.cloudflared/cert.pem`. Move it into the brand-scoped directory immediately so every subsequent step works off `${CFG_DIR}/cert.pem`:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
mv ~/.cloudflared/cert.pem "${CFG_DIR}/cert.pem"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**If the login fails with `You have an existing certificate`:** cert.pem already exists at the default path from a prior login. Either skip to Step 2 (if the existing cert is correct), or remove it and re-run:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
rm ~/.cloudflared/cert.pem
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**If the browser does not open:** confirm the VNC display exists:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
ls /tmp/.X11-unix/
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
You should see `X99`. If not, start VNC before retrying Step 1.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Step 2 — List existing tunnels
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel list
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Why:** Shows every tunnel on the brand's account by name and UUID. Use before creating (to avoid name collision) and before deleting (to find the UUID).
|
|
143
|
+
|
|
144
|
+
**Success:** A table of NAME, ID (UUID), CREATED, CONNECTIONS. An empty list is fine.
|
|
145
|
+
|
|
146
|
+
**If it fails with `cert.pem not found`:** run Step 1.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Step 3 — Create the tunnel
|
|
151
|
+
|
|
152
|
+
Choose a tunnel name following the convention `{product}-{device}` (e.g. `maxy-neo`, `maxy-pi`, `realagent-laptop`). Then:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel create "${BRAND}-$(hostname -s)"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Why:** Creates a new tunnel on the brand's CF account. Writes the connector credentials file (distinct from cert.pem: this is per-tunnel runtime auth; cert.pem is account auth). The credentials filename is the tunnel UUID.
|
|
159
|
+
|
|
160
|
+
**Success:** Output contains:
|
|
161
|
+
```
|
|
162
|
+
Created tunnel <name> with id <UUID>
|
|
163
|
+
```
|
|
164
|
+
Note the UUID — every subsequent step needs it.
|
|
165
|
+
|
|
166
|
+
**If it fails with `already exists`:** a tunnel with that name exists. Either run Step 2 to find its UUID and skip to Step 4, or delete it:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel delete "${BRAND}-$(hostname -s)"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Then re-run Step 3.
|
|
173
|
+
|
|
174
|
+
**Capture the UUID as an env var so every subsequent step references it:**
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
export TUNNEL_ID=<UUID>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Replace `<UUID>` with the value from Step 3's output.
|
|
181
|
+
|
|
182
|
+
**Note — credentials JSON already lives in `${CFG_DIR}`.** Unlike cert.pem, `cloudflared tunnel create` writes its `<UUID>.json` credentials next to wherever `--origincert` points. Because Step 1's `mv` put cert.pem in `${CFG_DIR}`, the credentials landed there too. No move needed.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Step 4 — Route DNS for each hostname
|
|
187
|
+
|
|
188
|
+
One command per hostname.
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" admin.maxy.bot
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" public.maxy.bot
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Why:** Creates a CNAME in Cloudflare DNS pointing each hostname at `<UUID>.cfargotunnel.com`. `--overwrite-dns` makes it idempotent.
|
|
199
|
+
|
|
200
|
+
**Success:** Output contains `Added CNAME <hostname> which will route to this tunnel.`
|
|
201
|
+
|
|
202
|
+
**If it fails with `zone not found`:** the hostname's parent domain isn't on this brand's Cloudflare account. Either add it in the dashboard (Websites → Add a site) and re-run, or sign into the account that already owns the domain.
|
|
203
|
+
|
|
204
|
+
### Apex hostnames (e.g. `maxy.chat` as a bare root domain)
|
|
205
|
+
|
|
206
|
+
`cloudflared tunnel route dns` **cannot** create a CNAME at a zone apex — standard DNS forbids CNAME at apex (conflicts with SOA/NS), and Cloudflare's workaround (CNAME flattening) is only exposed via the CF API/dashboard, not the CLI. Running `tunnel route dns --overwrite-dns <UUID> maxy.chat` silently misroutes to the first non-matching zone it finds (observed: it created `maxy.chat.maxy.bot` under the wrong zone).
|
|
207
|
+
|
|
208
|
+
**Workflow for apex hostnames:**
|
|
209
|
+
|
|
210
|
+
1. **Manual — create the CNAME once in the dashboard.** CF dashboard → `<apex-zone>` → DNS → Add record. Type=CNAME, Name=`@` (zone apex), Content=`<UUID>.cfargotunnel.com`, Proxy=on. Cloudflare applies CNAME flattening automatically for apex CNAMEs under a CF-managed zone.
|
|
211
|
+
2. **Scripted — add the ingress rule in Step 5's config.yml** (same as any subdomain hostname). Without this, traffic arrives at the connector and falls through to `http_status:404`.
|
|
212
|
+
3. **Scripted — add the hostname to `alias-domains.json`** if the apex should render the public surface (see §Troubleshooting at the end of this runbook for the classification rule).
|
|
213
|
+
|
|
214
|
+
Steps 2 and 3 are part of the normal scripted flow. Only Step (1) is the irreducible manual action for apex hostnames.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Step 5 — Write config.yml
|
|
219
|
+
|
|
220
|
+
The heredoc below expands `${BRAND}`, `${PORT}`, `${CFG_DIR}`, and `${TUNNEL_ID}` at write time. **If any of those variables is empty in the current shell, the file gets written with blank values and cloudflared refuses to start with the cryptic error `"cloudflared tunnel run" requires the ID or name of the tunnel`.** The most common cause is running Step 5 in a fresh shell where Step 0 wasn't re-exported.
|
|
221
|
+
|
|
222
|
+
**Fail-fast guard — run this first.** It exits non-zero if any required variable is unset or empty, so you cannot accidentally write a broken file:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
: "${BRAND:?set Step 0}"; : "${PORT:?set Step 0}"; : "${CFG_DIR:?set Step 0}"; : "${TUNNEL_ID:?set via Step 3}"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Only if that line prints nothing (all vars set) do you proceed to write the config:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
cat > "${CFG_DIR}/config.yml" <<EOF
|
|
232
|
+
tunnel: ${TUNNEL_ID}
|
|
233
|
+
credentials-file: ${CFG_DIR}/${TUNNEL_ID}.json
|
|
234
|
+
ingress:
|
|
235
|
+
- hostname: admin.maxy.bot
|
|
236
|
+
service: http://localhost:${PORT}
|
|
237
|
+
- hostname: public.maxy.bot
|
|
238
|
+
service: http://localhost:${PORT}
|
|
239
|
+
- service: http_status:404
|
|
240
|
+
EOF
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Why:** Tells cloudflared at startup which tunnel to run, where its credentials are, and how to route each hostname to the brand's local service port. The trailing `http_status:404` line is the mandatory catch-all.
|
|
244
|
+
|
|
245
|
+
**Success:** Confirm with:
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
cat "${CFG_DIR}/config.yml"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**If it fails on YAML:** validate with:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" --config "${CFG_DIR}/config.yml" tunnel ingress validate
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Step 5b — Write tunnel.state
|
|
260
|
+
|
|
261
|
+
`resume-tunnel.sh` (the `ExecStartPre=` in the brand's service) reads `${CFG_DIR}/tunnel.state` to discover which tunnel to run. **Without this file the script exits 0 silently and no connector is spawned** — see lines 34-37 of `platform/scripts/resume-tunnel.sh`. The old MCP tool `tunnel-create` used to write this file via `saveTunnelIdentity()` in `cloudflared.ts`; with the raw CLI flow in this runbook nothing does, so we write it explicitly here.
|
|
262
|
+
|
|
263
|
+
Re-run the Step 5a fail-fast guard if your shell is new or if Step 5a was a while ago:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
: "${BRAND:?set Step 0}"; : "${CFG_DIR:?set Step 0}"; : "${TUNNEL_ID:?set via Step 3}"
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Then write the state file:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
cat > "${CFG_DIR}/tunnel.state" <<EOF
|
|
273
|
+
{
|
|
274
|
+
"tunnelId": "${TUNNEL_ID}",
|
|
275
|
+
"tunnelName": "${BRAND}-$(hostname -s)",
|
|
276
|
+
"domain": "maxy.bot",
|
|
277
|
+
"configPath": "${CFG_DIR}/config.yml",
|
|
278
|
+
"credentialsPath": "${CFG_DIR}/${TUNNEL_ID}.json"
|
|
279
|
+
}
|
|
280
|
+
EOF
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Why each field:** `resume-tunnel.sh` reads `tunnelId`, `domain`, `configPath` (all used; domain is for logging, tunnelId for the log line, configPath is passed as `--config` to cloudflared). `credentialsPath` and `tunnelName` are not read by `resume-tunnel.sh` itself but are consumed by other tools (e.g. `tunnel-status` in the cloudflared plugin), so write them anyway.
|
|
284
|
+
|
|
285
|
+
**Success:**
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
cat "${CFG_DIR}/tunnel.state" | jq .tunnelId
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Prints the UUID from Step 3. If it prints empty or null, the heredoc's env expansion failed — re-run the fail-fast guard.
|
|
292
|
+
|
|
293
|
+
**If you skip this step:** `systemctl --user status maxy.service` will show `ExecStartPre=resume-tunnel.sh (code=exited, status=0/SUCCESS)` (no error) but `ps -ef | grep '[c]loudflared'` will show no Maxy connector. Silent failure — the exact gap this step closes.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Step 6 — Restart the brand's user-space service so it respawns the connector with the new config
|
|
298
|
+
|
|
299
|
+
You do **not** run `cloudflared` manually. The brand's existing user-space systemd unit (`~/.config/systemd/user/${BRAND}.service`) declares `ExecStartPre=/home/<user>/${BRAND}/platform/scripts/resume-tunnel.sh`, and that pre-start script reads `${CFG_DIR}/tunnel.state` and `${CFG_DIR}/config.yml` (the files Steps 5 and 5b just wrote) and spawns the connector in the user's cgroup. Restarting the brand service is what picks up the new config.
|
|
300
|
+
|
|
301
|
+
> **Note:** When walking through by hand you run this step yourself. The automation script `platform/plugins/cloudflare/scripts/setup-tunnel.sh` runs it for you — the script is autonomous and completes the deployment, including the service restart and post-restart verification (ps-grep for the connector + curl each subdomain). If you used the script, this step is already done.
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
systemctl --user restart "${BRAND}.service"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Why:** `resume-tunnel.sh` is the deterministic, brand-scoped spawner. Running `cloudflared` manually duplicates the connector (two processes for one tunnel) and races the brand service on every service restart. The service path is the only correct production path.
|
|
309
|
+
|
|
310
|
+
**Success:**
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
systemctl --user status "${BRAND}.service" --no-pager
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Shows `active (running)` with `ExecStartPre=...resume-tunnel.sh (code=exited, status=0/SUCCESS)`. Then confirm the connector is actually alive:
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
ps -ef | grep '[c]loudflared'
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Expect one line per running brand, each with `--config ${CFG_DIR}/config.yml` under your Linux user (not root). Then verify end-to-end from any machine:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
curl -I https://admin.maxy.bot
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
A non-530 response means the tunnel is live.
|
|
329
|
+
|
|
330
|
+
**If the connector is missing from `ps`**, the spawn failed silently. Check `${CFG_DIR}/logs/cloudflared.log` for the reason (most often: `config.yml` was written with empty `tunnel:` because Step 5's fail-fast guard was skipped).
|
|
331
|
+
|
|
332
|
+
**If the hostname returns `530`:** DNS has not propagated yet. Wait 30–60 seconds and retry.
|
|
333
|
+
|
|
334
|
+
**If the hostname returns `connection refused`:** your local service is not listening on `${PORT}`. Start it.
|
|
335
|
+
|
|
336
|
+
**If cloudflared exits with `tunnel not found`:** the `${TUNNEL_ID}` in config.yml does not match an existing tunnel on the brand's account. Re-run Step 2 to confirm.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Step 7 — Production durability (user-space systemd via the platform service)
|
|
341
|
+
|
|
342
|
+
**Do NOT use `sudo cloudflared service install`.** It writes `/etc/systemd/system/cloudflared.service`, copies your config to `/etc/cloudflared/config.yml` (a system-wide path), and runs the connector as root — breaking brand isolation on any device that hosts more than one brand under the same Linux user. We tried this path on 2026-04-19; it created a duplicate connector parallel to the existing user-space one and required an explicit uninstall to undo.
|
|
343
|
+
|
|
344
|
+
The correct production pattern is already in place on every Maxy device: the brand's platform UI itself is a **user-space systemd service** at `~/.config/systemd/user/<brand>.service`, and that service spawns the cloudflared connector as an `ExecStartPre=` via `platform/scripts/resume-tunnel.sh`. When the user-space service restarts, the connector restarts with it. The connector runs as the Linux user (not root), its config is read from `${CFG_DIR}/config.yml` (brand-scoped, never `/etc`), and multiple brands coexist because each has its own unit file (`maxy.service`, `realagent.service`, etc.).
|
|
345
|
+
|
|
346
|
+
**Durability prerequisites (one-time per device):**
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
loginctl enable-linger "$(whoami)"
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Lets user-space services survive logout/reboot. Without linger, user units only start at interactive login.
|
|
353
|
+
|
|
354
|
+
**Confirm the brand's unit is enabled:**
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
systemctl --user is-enabled "${BRAND}.service"
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Expected: `enabled`. If `disabled`, enable it:
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
systemctl --user enable "${BRAND}.service"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**After any change to `${CFG_DIR}/config.yml`, restart the brand's service** — the `ExecStartPre=resume-tunnel.sh` respawns the connector with the new config:
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
systemctl --user restart "${BRAND}.service"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Success:**
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
systemctl --user status "${BRAND}.service"
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Shows `active (running)` with the brand's platform UI + cloudflared alive. A separate `ps -ef | grep '[c]loudflared'` confirms one connector per running brand, all under user `$(whoami)`, none under root.
|
|
379
|
+
|
|
380
|
+
**If you already ran `sudo cloudflared service install` before reading this** — undo it:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
sudo cloudflared service uninstall
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Removes the root-level systemd unit, `/etc/cloudflared/config.yml`, and the duplicate connector. The user-space service remains unaffected.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Full reset
|
|
391
|
+
|
|
392
|
+
If everything is in a bad state and you want to start over. Assumes Step 0 has been run so `${BRAND}` and `${CFG_DIR}` are set.
|
|
393
|
+
|
|
394
|
+
Find the tunnel name:
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel list
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Delete the tunnel (replace `<name>`):
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel delete <name>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Remove the entire brand-scoped cloudflared directory:
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
rm -rf "${CFG_DIR}"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Order matters: delete the tunnel *before* removing cert.pem (delete needs the cert). Then start again from Step 0.
|
|
413
|
+
|
|
414
|
+
If the service was installed (Step 7), also uninstall it:
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
sudo cloudflared service uninstall
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Troubleshooting — admin hostname serves the public agent
|
|
423
|
+
|
|
424
|
+
If `admin.<yourdomain>` is rendering the public-agent UI, the tunnel is fine but the platform UI is treating the admin hostname as public. The platform UI classifies a host as public when either:
|
|
425
|
+
|
|
426
|
+
- the hostname starts with `public.`, or
|
|
427
|
+
- the hostname appears in `${CFG_DIR}/alias-domains.json`.
|
|
428
|
+
|
|
429
|
+
Pre-Task-548 sessions populated this file via the now-deleted `tunnel-add-hostname` MCP tool, which wrote every routed hostname — including `admin.*` — into the alias set. The pollution persists across installs.
|
|
430
|
+
|
|
431
|
+
**Diagnose:**
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
cat "${CFG_DIR}/alias-domains.json"
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
If `admin.<yourdomain>` appears in the array, remove it:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${CFG_DIR}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${CFG_DIR}/alias-domains.json"
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Replace `<yourdomain>` with your actual domain. The platform UI watches the file — no restart required; the next request to the admin hostname routes to the admin surface.
|
|
444
|
+
|
|
445
|
+
Leave legitimate public aliases (e.g. `maxy.chat`, or `<yourdomain>` root acting as public) in the file.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Cloudflare reset — when and how
|
|
2
|
+
|
|
3
|
+
Reset is a heavier action than recovery. `reset-tunnel.sh` deletes every tunnel on the brand's Cloudflare account and wipes the on-disk cloudflared config directory. Use it when the state is genuinely corrupt or when the operator wants a known-good baseline; use patching (targeted cleanup) when the state is mostly correct but one record or process is wrong.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Decision tree — reset or patch?
|
|
8
|
+
|
|
9
|
+
Ask three questions in order. The first `yes` determines the action.
|
|
10
|
+
|
|
11
|
+
1. **Is cert.pem bound to an account the operator no longer wants to be signed into?** (e.g. the operator signed in to the wrong account, or the previous account was scrubbed / rotated / delisted.)
|
|
12
|
+
→ Full reset. The cert authorises everything `cloudflared` does; a wrong-account cert poisons every subsequent step.
|
|
13
|
+
|
|
14
|
+
2. **Is `config.yml` referencing a tunnel UUID that no longer exists on the account, or a hostname that the account no longer owns?**
|
|
15
|
+
→ Full reset. The tunnel the config names is unreachable; recovering per-step is slower than starting clean.
|
|
16
|
+
|
|
17
|
+
3. **Is the only problem a stray CNAME in the dashboard, a rogue token-mode connector process, or an out-of-date `alias-domains.json` entry?**
|
|
18
|
+
→ Patch (see § Patching below). Reset would destroy correct local state alongside the bad record.
|
|
19
|
+
|
|
20
|
+
When no question reaches `yes`, the state is probably recoverable per-step via `references/manual-setup.md`. Invoke `setup-tunnel.sh` again before reaching for reset.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Full reset — `reset-tunnel.sh`
|
|
25
|
+
|
|
26
|
+
### What it does
|
|
27
|
+
|
|
28
|
+
- Reads cert.pem from `${HOME}/.${BRAND}/cloudflared/cert.pem`.
|
|
29
|
+
- Lists every tunnel on the account the cert authorises.
|
|
30
|
+
- Deletes every one of those tunnels via `cloudflared tunnel delete`.
|
|
31
|
+
- Removes `${HOME}/.${BRAND}/cloudflared/` entirely.
|
|
32
|
+
|
|
33
|
+
### What it does not do
|
|
34
|
+
|
|
35
|
+
- It does not touch the platform service (`${BRAND}.service`). Restart the service separately with `systemctl --user restart "${BRAND}.service"` once a fresh tunnel is set up.
|
|
36
|
+
- It does not touch DNS records. Records pointing at `<UUID>.cfargotunnel.com` from a deleted tunnel become stray; delete them in the dashboard (see § Patching below).
|
|
37
|
+
- It does not stop token-mode connector processes. If the device is running `cloudflared ... tunnel run --token <X>` for a tunnel the brand's cert does not own, that connector keeps running.
|
|
38
|
+
- It does not switch accounts. If the bound account was wrong, a fresh `cloudflared tunnel login` from inside `setup-tunnel.sh` on the next run will pick a new one — but the operator must sign in with the correct account in the browser.
|
|
39
|
+
|
|
40
|
+
### Invocation
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
~/reset-tunnel.sh <brand>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
~/reset-tunnel.sh maxy
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The script exits with the number of tunnels deleted. Relay the output verbatim. If the script exits non-zero (e.g. cert missing, cloudflared not installed), quote the error and stop — do not synthesise a recovery path.
|
|
53
|
+
|
|
54
|
+
### After reset
|
|
55
|
+
|
|
56
|
+
Invoke `setup-tunnel.sh` with the operator's chosen hostnames (see `SKILL.md` for the input-collection questions). The script re-runs `cloudflared tunnel login`; the operator authorises a fresh cert from the correct account in the VNC browser.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Patching — targeted cleanup without reset
|
|
61
|
+
|
|
62
|
+
### Stop a token-mode connector the reset script cannot see
|
|
63
|
+
|
|
64
|
+
Token-mode tunnels are created by a central operator (e.g. Maxy-provided subdomains under `maxy.bot`). The connector runs with `--token <X>` and does not consult `cert.pem`, so `reset-tunnel.sh` (which authorises via cert.pem) cannot stop or delete it. Kill the process directly:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pkill -f 'cloudflared.*tunnel run --token'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The pattern matches exactly connectors started with `tunnel run --token`, leaving cert-mode connectors (started without `--token`) alone. Confirm the kill:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
ps -ef | grep '[c]loudflared'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
No lines with `--token` should remain. If multiple token-mode connectors are running, all are killed — scope with a more specific pattern if that is not desired (`pkill -f 'cloudflared.*tunnel run --token eyJ...'` matches the token prefix).
|
|
77
|
+
|
|
78
|
+
### Delete a stray CNAME that survived a tunnel delete
|
|
79
|
+
|
|
80
|
+
When `reset-tunnel.sh` deletes a tunnel, the DNS CNAMEs that pointed at `<UUID>.cfargotunnel.com` remain in the zone — Cloudflare deliberately does not cascade-delete DNS when you delete a tunnel. Clean up in the dashboard:
|
|
81
|
+
|
|
82
|
+
1. See `references/dashboard-guide.md` § "Delete a stray CNAME" for the click-path.
|
|
83
|
+
2. Verify the CNAME's target is the tunnel you just deleted (not a live one).
|
|
84
|
+
3. Delete.
|
|
85
|
+
|
|
86
|
+
When the operator is unsure which records are stray, have them list the zone's CNAMEs (see `references/dashboard-guide.md` § "List CNAME records in a zone") and compare against the currently-running `cloudflared tunnel list` on the device.
|
|
87
|
+
|
|
88
|
+
### Remove a rogue entry from `alias-domains.json`
|
|
89
|
+
|
|
90
|
+
`alias-domains.json` controls which hostnames the platform UI classifies as public (serving the public agent) rather than admin. A prior setup flow may have written an `admin.*` hostname into this file by mistake. Remove it manually:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
cat "${CFG_DIR}/alias-domains.json"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If `admin.<yourdomain>` is listed, strip it:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
jq --arg H "admin.<yourdomain>" 'map(select(. != $H))' "${CFG_DIR}/alias-domains.json" > /tmp/alias.json && mv /tmp/alias.json "${CFG_DIR}/alias-domains.json"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Replace `<yourdomain>` with the actual domain. The platform UI watches the file and reloads without a restart.
|
|
103
|
+
|
|
104
|
+
### Fix a single wrong DNS record without tearing everything down
|
|
105
|
+
|
|
106
|
+
If a hostname's CNAME is pointing at the wrong tunnel (e.g. after an account switch left old records), `cloudflared` can overwrite it once the correct cert is in place:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
cloudflared --origincert "${CFG_DIR}/cert.pem" tunnel route dns --overwrite-dns "${TUNNEL_ID}" <hostname>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This runs the same logic `setup-tunnel.sh` runs internally for each subdomain. Apex hostnames do not work here — the dashboard is the only path (see `references/dashboard-guide.md` § "Edit an apex CNAME").
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Failure discipline
|
|
117
|
+
|
|
118
|
+
When `reset-tunnel.sh` or a patch command exits non-zero, report the failure with the exact output. Cite the relevant section above for the next action. Do not chain alternative recovery attempts — the agent's job ends at the boundary of a sanctioned surface, and improvisation outside that boundary is the discipline violation IDENTITY.md § Cloudflare operations exists to prevent.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Reset a brand's Cloudflare tunnel state.
|
|
3
|
+
# Inverse of setup-tunnel.sh; same scope boundary (tunnel state only).
|
|
4
|
+
#
|
|
5
|
+
# Deletes every tunnel on the brand's Cloudflare account (using the brand's
|
|
6
|
+
# cert.pem) and wipes the brand-scoped cloudflared state directory. Leaves
|
|
7
|
+
# the platform service alone — the operator decides whether to stop/restart
|
|
8
|
+
# ${BRAND}.service.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# reset-tunnel.sh <brand>
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
# reset-tunnel.sh maxy
|
|
15
|
+
#
|
|
16
|
+
# Does NOT delete stray misrouted CNAMEs in DNS (e.g. admin.maxy.bot.maxy.chat
|
|
17
|
+
# from an earlier wrong-zone OAuth). Those are zone-specific DNS records the
|
|
18
|
+
# CLI cannot enumerate from here; remove them manually in the CF dashboard.
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
if [ "$#" -lt 1 ]; then
|
|
23
|
+
echo "Usage: $0 <brand>" >&2
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
BRAND="$1"
|
|
28
|
+
CFG_DIR="${HOME}/.${BRAND}/cloudflared"
|
|
29
|
+
CERT="${CFG_DIR}/cert.pem"
|
|
30
|
+
|
|
31
|
+
if [ ! -f "${CERT}" ]; then
|
|
32
|
+
echo "No cert.pem at ${CERT} — nothing to delete via CLI."
|
|
33
|
+
echo "Removing ${CFG_DIR} if present."
|
|
34
|
+
rm -rf "${CFG_DIR}"
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Delete every tunnel on the account (the cert is per-account for tunnel CRUD).
|
|
39
|
+
NAMES="$(cloudflared --origincert "${CERT}" tunnel list --output json 2>/dev/null \
|
|
40
|
+
| jq -r '.[]?.name' | sort -u)"
|
|
41
|
+
if [ -z "${NAMES}" ]; then
|
|
42
|
+
echo "No tunnels to delete on this brand's account."
|
|
43
|
+
else
|
|
44
|
+
while IFS= read -r NAME; do
|
|
45
|
+
[ -z "${NAME}" ] && continue
|
|
46
|
+
echo "Deleting tunnel: ${NAME}"
|
|
47
|
+
cloudflared --origincert "${CERT}" tunnel delete "${NAME}"
|
|
48
|
+
done <<< "${NAMES}"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Wipe the brand-scoped cloudflared state directory.
|
|
52
|
+
rm -rf "${CFG_DIR}"
|
|
53
|
+
echo "Removed ${CFG_DIR}"
|
|
54
|
+
|
|
55
|
+
echo ""
|
|
56
|
+
echo "Reset complete for brand=${BRAND}."
|
|
57
|
+
echo ""
|
|
58
|
+
echo "If earlier runs produced stray misrouted CNAMEs (e.g. admin.maxy.bot.maxy.chat"
|
|
59
|
+
echo "when the cert was scoped to the wrong zone), delete those records manually in"
|
|
60
|
+
echo "the Cloudflare dashboard — the CLI cannot enumerate them from here."
|
|
61
|
+
echo ""
|
|
62
|
+
echo "The platform service (${BRAND}.service) was not touched. To release any"
|
|
63
|
+
echo "running cloudflared connector started by ${BRAND}.service's ExecStartPre,"
|
|
64
|
+
echo "either restart the service (which will no-op on resume since tunnel.state"
|
|
65
|
+
echo "is gone) or leave it running until you re-run setup-tunnel.sh."
|