spinuptui 0.20.0 → 0.20.1
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/CHANGELOG.md +19 -1
- package/README.md +124 -579
- package/package.json +1 -1
- package/src/dev/fixtures.ts +494 -5
package/README.md
CHANGED
|
@@ -11,162 +11,70 @@
|
|
|
11
11
|
Built with <a href="https://opentui.com">OpenTUI</a> and <a href="https://bun.sh">Bun</a>.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Screenshots
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
└──────────────────────────────────────┘ └─────────────────────────────────────┘
|
|
28
|
-
```
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src=".github/assets/screenshot-dashboard.png" alt="SpinupTUI Dashboard" width="100%">
|
|
18
|
+
</p>
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src=".github/assets/screenshot-servers.png" alt="SpinupTUI Servers" width="49%">
|
|
21
|
+
<img src=".github/assets/screenshot-stacks.png" alt="SpinupTUI Stacks" width="49%">
|
|
22
|
+
</p>
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src=".github/assets/screenshot-search.png" alt="SpinupTUI Search" width="49%">
|
|
25
|
+
<img src=".github/assets/screenshot-events.png" alt="SpinupTUI Events" width="49%">
|
|
26
|
+
</p>
|
|
29
27
|
|
|
30
28
|
## Features
|
|
31
29
|
|
|
32
|
-
- **Fleet dashboard** —
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- **
|
|
65
|
-
|
|
66
|
-
(a
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
ever touches a site's own hosting records, never your MX/TXT/other records. (See
|
|
81
|
-
"DNS hosts, access & editing" below.)
|
|
82
|
-
- **Database backup & sync** — on a linked WordPress site (Search tab), press `d`
|
|
83
|
-
to download a gzipped production database backup into the copy's `sql/` folder
|
|
84
|
-
(**read-only on production**), or — opt-in — press `p` to pull production into
|
|
85
|
-
your **local** database (backs up local first, rewrites URLs, runs your existing
|
|
86
|
-
`sync.d` hook). Works with Standard WP and Bedrock, no per-site config. (See
|
|
87
|
-
"Database backup & sync" below.)
|
|
88
|
-
- **Production media fallback** — after a DB pull, your local site shows broken
|
|
89
|
-
images because the (often huge) media library isn't synced. Press `m` on a
|
|
90
|
-
linked WordPress site to drop a small mu-plugin that serves any **missing-locally**
|
|
91
|
-
upload from production — so images resolve without copying a single file. Works
|
|
92
|
-
on any local stack (it's pure PHP, not web-server config). (See "Production media
|
|
93
|
-
fallback" below.)
|
|
94
|
-
- **Upgrade a site's PHP version** — press `u` on a site to pick a new PHP
|
|
95
|
-
version and apply it (`PUT /sites/{id}/php`), then watch the upgrade event run
|
|
96
|
-
to completion. (See "Upgrading PHP" below.)
|
|
97
|
-
- **Enable / disable HTTPS** — press `H` on a site to toggle its certificate.
|
|
98
|
-
Enabling issues a free Let's Encrypt cert (the domain must already resolve to
|
|
99
|
-
the server); disabling removes it — the confirm screen warns plainly that
|
|
100
|
-
https:// visitors will see errors until it's re-enabled. Same background
|
|
101
|
-
event-tracking as the PHP upgrade.
|
|
102
|
-
- **Purge cache** — press `P` on a site to clear its page cache and WordPress
|
|
103
|
-
object cache together (SpinupWP has no enable/disable for either on an
|
|
104
|
-
existing site, only purge). Low-risk and fully reversible — both rebuild on
|
|
105
|
-
the next page load.
|
|
106
|
-
- **Server actions** — press `a` on a server to reboot it or restart a service
|
|
107
|
-
(Nginx / PHP-FPM / MySQL / Redis). Servers needing a reboot show a `↻rbt`
|
|
108
|
-
badge, and the overlay reads the box over SSH to show *why* (the pending
|
|
109
|
-
kernel/security packages). (See "Server actions" below.)
|
|
110
|
-
- **Create & connect a server** — press `c` on the Servers tab to provision a new
|
|
111
|
-
server (DigitalOcean / Vultr / Linode / Hetzner), priced from the provider catalog
|
|
112
|
-
and pre-filled to match a selected server. Then press `V` on any server that
|
|
113
|
-
doesn't yet have a **site at its own hostname** to connect it end to end: SpinupTUI
|
|
114
|
-
writes the DNS A record, creates a
|
|
115
|
-
placeholder "vanity" site, enables HTTPS, hands you off to add your SSH key, and
|
|
116
|
-
publishes a status page — turning a bare server into one you can actually work
|
|
117
|
-
with. Long builds run in the background and survive a restart. (See "Creating &
|
|
118
|
-
connecting servers" below.)
|
|
119
|
-
- **Clone a whole server to a new one** — press `C` on a server to clone one or
|
|
120
|
-
more of its sites onto a fresh (or existing) destination, **without touching DNS
|
|
121
|
-
until you say so**. A guided wizard plans + sizes the sites, provisions or picks
|
|
122
|
-
the destination, pulls each site server-to-server (Standard WP **and** Bedrock),
|
|
123
|
-
verifies the clone against the source, and finally repoints DNS on your word.
|
|
124
|
-
Runs in the background with a header badge. (See "Cloning a server" below.)
|
|
125
|
-
- **Privileged writes over SSH (sudo + SSH keys)** — the SpinupWP API can't manage
|
|
126
|
-
SSH keys or sudo users, so SpinupTUI does it directly. Press `S` on a server to
|
|
127
|
-
**connect sudo** for the session (optionally **remembered in your macOS Keychain**
|
|
128
|
-
so it auto-unlocks next time), then `K` on a site to **grant or revoke** your
|
|
129
|
-
personal key and/or SpinupTUI's dedicated machine key — one site or every site on the
|
|
130
|
-
server. (See "Privileged writes over SSH" below.)
|
|
131
|
-
- **Uptime Kuma monitoring** — press `m` on a site to wire it into your own
|
|
132
|
-
[Uptime Kuma](https://github.com/louislam/uptime-kuma) instance (one-time
|
|
133
|
-
connect, 2FA supported, verified by a real login). Vanity pages double as
|
|
134
|
-
health endpoints (`?healthz` returns 503 under CPU pressure / low disk;
|
|
135
|
-
`?format=json&key=…` serves full metrics) and get a load push monitor fed by a
|
|
136
|
-
once-a-minute cron — Kuma graphs server load, and a silent cron acts as a
|
|
137
|
-
dead-man's switch. Monitor status flows back in: a Monitor row in Details, a
|
|
138
|
-
"▼ N monitors down" header badge, uptime/response/load sparklines in the
|
|
139
|
-
Health view, and reboots automatically open a Kuma maintenance window so
|
|
140
|
-
planned downtime never pages you. Regular sites get a homepage monitor
|
|
141
|
-
(up/down + cert-expiry alerts) — client site files are never touched.
|
|
142
|
-
Showed a push URL or health key on a screencast? `r` in the `m` overlay
|
|
143
|
-
**rotates both secrets in place** — the old ones die immediately, the Kuma
|
|
144
|
-
monitor (and its history) survives. Vanity sites are marked `⌂` in the
|
|
145
|
-
Servers tab so they never hide among domain-named servers' sites.
|
|
146
|
-
(See `docs/uptime-kuma.md` for recipes.)
|
|
147
|
-
- **Site monitoring: fingerprints, a doctor, and a Redis sentinel** — press `M`
|
|
148
|
-
on any site (Servers / Stacks / Search) for the monitoring overlay. `f`
|
|
149
|
-
calibrates a **front-page check**: SpinupTUI reads your healthy front page,
|
|
150
|
-
derives a template-identity fingerprint from what WordPress itself stamps
|
|
151
|
-
into the markup (not a headline someone might edit), proves it discriminates,
|
|
152
|
-
and registers a Kuma keyword monitor at your chosen window (5m–1h) — catching
|
|
153
|
-
"the cache is serving the wrong page while HTTP stays 200". `d` runs the
|
|
154
|
-
**site doctor**: a read-only cache differential (cached vs. bypassed render,
|
|
155
|
-
proven by the server's own `fastcgi-cache` headers) plus a wp-admin door
|
|
156
|
-
probe, diagnosing stale caches and **partial outages** — cached pages fine
|
|
157
|
-
while everything uncached throws 500 (a dead Redis does exactly this) — and
|
|
158
|
-
ending in a copyable runbook. `n` wires **alerts**: SpinupTUI detects the
|
|
159
|
-
notification providers configured in Kuma by name and toggles them per site.
|
|
160
|
-
And servers get a **Redis sentinel**: the heartbeat cron pings Redis every
|
|
161
|
-
minute and reports up/down to its own `{server} redis` monitor.
|
|
162
|
-
(See "Site monitoring" below.)
|
|
163
|
-
- **Completion toasts** — background writes that take a while (a PHP upgrade, a
|
|
164
|
-
server reboot/restart, resolving your fleet's DNS) raise a non-focus-stealing
|
|
165
|
-
toast when they finish, so you're not left guessing after you've moved on.
|
|
166
|
-
- **Release notes** — after SpinupTUI updates to a new version, a one-time overlay
|
|
167
|
-
shows what changed (sourced straight from that version's GitHub release —
|
|
168
|
-
no separate feed to maintain). Press `n` in `?` Help to see the current
|
|
169
|
-
version's notes again any time.
|
|
30
|
+
- **Fleet dashboard** — connection status, disk usage, pending reboots/upgrades,
|
|
31
|
+
WP update counts, EOL Ubuntu flags, and a recent-activity feed for every server.
|
|
32
|
+
- **Server & site browser** — a three-pane navigator with an at-a-glance status
|
|
33
|
+
column (SSH keys, HTTPS, cache, backups, linked copy, pending updates).
|
|
34
|
+
- **Stack detection & fleet composition** — classifies every site as Standard WP,
|
|
35
|
+
Bedrock, or Non-WP, with a PHP-version breakdown and an on-demand SSH probe for
|
|
36
|
+
precise identification. → [docs/stack-detection.md](docs/stack-detection.md)
|
|
37
|
+
- **Global search** — fuzzy search across every server and site by name, domain,
|
|
38
|
+
or IP, with inline actions on the result.
|
|
39
|
+
- **Events feed** — recent provisioning/operation activity with per-event detail.
|
|
40
|
+
- **Live server health** (`h`) — real-time CPU/load/memory/disk over SSH.
|
|
41
|
+
→ [docs/server-health.md](docs/server-health.md)
|
|
42
|
+
- **Installed plugins & themes** (`p`) — real `wp plugin`/`theme list` over SSH,
|
|
43
|
+
with per-item version and update status. → [docs/plugins-themes.md](docs/plugins-themes.md)
|
|
44
|
+
- **Open in browser** (`o`) / **SSH into a site** (`s`) — one-key shortcuts.
|
|
45
|
+
- **Link local working copies** (`L`) — link a site to its local checkout, with
|
|
46
|
+
auto-discovery and git-drift indicators. → [docs/local-working-copies.md](docs/local-working-copies.md)
|
|
47
|
+
- **DNS migration lens** (`n` / `N`) — see and edit a site's hosting records,
|
|
48
|
+
repoint it to another server. → [docs/dns-access-editing.md](docs/dns-access-editing.md)
|
|
49
|
+
- **Database backup & sync** (`d` / `p`, Search tab) — download or pull a
|
|
50
|
+
production DB into a linked local copy. → [docs/database-backup-sync.md](docs/database-backup-sync.md)
|
|
51
|
+
- **Production media fallback** (`m`, Search tab) — serve missing-locally images
|
|
52
|
+
from production after a DB pull. → [docs/production-media-fallback.md](docs/production-media-fallback.md)
|
|
53
|
+
- **Upgrade a site's PHP version** (`u`) — pick a version, apply it, watch the
|
|
54
|
+
event complete. → [docs/php-upgrade.md](docs/php-upgrade.md)
|
|
55
|
+
- **Enable / disable HTTPS** (`H`) — toggle a site's certificate.
|
|
56
|
+
- **Purge cache** (`P`) — clear page + object cache together.
|
|
57
|
+
- **Server actions** (`a`) — reboot or restart a service, with the real reason a
|
|
58
|
+
reboot is pending. → [docs/server-actions.md](docs/server-actions.md)
|
|
59
|
+
- **Create & connect a server** (`c` / `V`) — provision a server, then wire it up
|
|
60
|
+
end-to-end (DNS, a vanity site, HTTPS, SSH-key handoff).
|
|
61
|
+
→ [docs/creating-connecting-servers.md](docs/creating-connecting-servers.md)
|
|
62
|
+
- **Clone a whole server to a new one** (`C`) — a guided wizard that plans,
|
|
63
|
+
copies, verifies, and cuts over DNS on your word.
|
|
64
|
+
→ [docs/cloning-a-server.md](docs/cloning-a-server.md) ·
|
|
65
|
+
[how it works](docs/clone-wizard-explained.md)
|
|
66
|
+
- **Privileged writes over SSH** (`S` / `K`) — grant or revoke SSH keys directly,
|
|
67
|
+
since the API can't. → [docs/privileged-ssh-writes.md](docs/privileged-ssh-writes.md)
|
|
68
|
+
- **Uptime Kuma monitoring** (`m`) — wire a site into your own
|
|
69
|
+
[Uptime Kuma](https://github.com/louislam/uptime-kuma) for uptime, load, and
|
|
70
|
+
health checks. → [docs/uptime-kuma.md](docs/uptime-kuma.md)
|
|
71
|
+
- **Site monitoring: fingerprints, a doctor, and a Redis sentinel** (`M`) — a
|
|
72
|
+
front-page identity check, a cache/outage doctor, and alert wiring.
|
|
73
|
+
→ [docs/site-monitoring.md](docs/site-monitoring.md)
|
|
74
|
+
- **Completion toasts** — a non-focus-stealing toast when a background write
|
|
75
|
+
(PHP upgrade, reboot, DNS resolve, …) finishes.
|
|
76
|
+
- **Release notes** — an in-app "what's new" after every update, sourced
|
|
77
|
+
straight from that version's GitHub release.
|
|
170
78
|
|
|
171
79
|
> The tool is **read-only by default** and works great with a Read Only API
|
|
172
80
|
> token. The write actions — creating a server, connecting it with a vanity site,
|
|
@@ -346,460 +254,95 @@ search box.
|
|
|
346
254
|
|
|
347
255
|
## Server health (SSH)
|
|
348
256
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
**read-only** command (`cat /proc/*`, `df`, `ps`) and renders the result.
|
|
353
|
-
|
|
354
|
-
- **Connection target** is derived from the API: it connects as one of the
|
|
355
|
-
server's `site_user`s at the server's IP (`site_user@ip`). No extra config
|
|
356
|
-
needed if `ssh site_user@ip` already works from your terminal.
|
|
357
|
-
- **Non-interactive:** it uses `BatchMode=yes`, so if key auth isn't already set
|
|
358
|
-
up it fails fast with a hint rather than prompting for a password.
|
|
359
|
-
- **Override the SSH user** (e.g. to use `root` or a sudo user) with the
|
|
360
|
-
`SPINUPWP_SSH_USER` environment variable, or `"sshUser"` in the config file.
|
|
361
|
-
- A persistent `ControlMaster` connection keeps repeated polls fast.
|
|
362
|
-
|
|
363
|
-
Nothing is ever written to the server.
|
|
257
|
+
`h` on a server opens a real-time SSH view — CPU, load, memory/swap, disk, top
|
|
258
|
+
processes — read-only, using your local SSH keys. Full details:
|
|
259
|
+
[docs/server-health.md](docs/server-health.md).
|
|
364
260
|
|
|
365
261
|
## Stack detection
|
|
366
262
|
|
|
367
|
-
The **Stacks** tab (`3`)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
-
|
|
371
|
-
already returns: **Non-WP**, **Bedrock** (WordPress with a `/web/` webroot), or
|
|
372
|
-
**Standard WP**. The left pane shows counts and bars; the right pane shows the
|
|
373
|
-
fleet-wide **PHP version distribution** with end-of-life versions flagged.
|
|
374
|
-
|
|
375
|
-
- **Tier 2 — on-demand SSH probe.** Press `d` on a site (in the Stacks or
|
|
376
|
-
Servers tab) to inspect its filesystem **read-only** and identify it precisely:
|
|
377
|
-
**WordPress** (with version), **Bedrock**, **WHMCS**, **Laravel**, or
|
|
378
|
-
**Static HTML**. Press `D` to probe an entire stack in list order (bounded SSH
|
|
379
|
-
concurrency). A conclusive probe **overrides** the Tier-1 guess — so a site the
|
|
380
|
-
API mislabels (e.g. WordPress installed outside SpinupWP's installer reports
|
|
381
|
-
`is_wordpress=false`) moves into its true bucket. The Non-WP bucket expands
|
|
382
|
-
into named sub-rows (WHMCS / Laravel / Static HTML / Unknown / unprobed).
|
|
383
|
-
|
|
384
|
-
Probes reuse the same SSH access as the health view (`site_user@ip`, your local
|
|
385
|
-
keys, `BatchMode`) and are **read-only**. Results are cached to
|
|
386
|
-
`~/.config/spinupwp-tui/stack-cache.json`, hydrated at startup, so detections
|
|
387
|
-
survive restarts without re-running SSH.
|
|
263
|
+
The **Stacks** tab (`3`) classifies every site as Standard WP, Bedrock, or Non-WP
|
|
264
|
+
from API data alone, then lets you SSH-probe (`d` / `D`) for a precise ID (WHMCS,
|
|
265
|
+
Laravel, Static HTML, WordPress version) that overrides a mislabeled guess. Full
|
|
266
|
+
details: [docs/stack-detection.md](docs/stack-detection.md).
|
|
388
267
|
|
|
389
268
|
## Installed plugins & themes
|
|
390
269
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
**current version**, and the **new version** when an update is available (`→ 1.2.3`
|
|
396
|
-
in gold; `✓ current` otherwise), grouped into `PLUGINS` and `THEMES` sections each
|
|
397
|
-
with an update badge.
|
|
398
|
-
|
|
399
|
-
It's strictly **read-only**: it runs wp-cli as the site user using your local SSH
|
|
400
|
-
keys (the same non-interactive auth the health view uses), and **detects the real
|
|
401
|
-
WordPress directory itself** rather than trusting the `public_folder` setting — so
|
|
402
|
-
it works on standard `/public/` installs and Bedrock (`web/wp`) alike, and even on
|
|
403
|
-
sites SpinupWP misclassifies as non-WordPress (it finds WordPress core over SSH).
|
|
404
|
-
Non-WordPress sites get a clear "no WordPress core found" message.
|
|
405
|
-
|
|
406
|
-
`↑↓` / `jk` scroll, `r` re-reads over SSH, `Esc` / `q` / `p` closes.
|
|
270
|
+
`p` on a site (Servers tab) lists its real `wp plugin`/`theme list` over SSH —
|
|
271
|
+
status, version, and available updates — the detail the API only gives as bare
|
|
272
|
+
counts. Read-only, works on Bedrock and misclassified sites too. Full details:
|
|
273
|
+
[docs/plugins-themes.md](docs/plugins-themes.md).
|
|
407
274
|
|
|
408
275
|
## Upgrading PHP
|
|
409
276
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
release schedule (so new versions like 8.5 appear automatically). After you
|
|
414
|
-
confirm, the app calls `PUT /sites/{id}/php` and polls the resulting event until
|
|
415
|
-
it finishes.
|
|
416
|
-
|
|
417
|
-
- **Needs a Read/Write token.** SpinupWP exposes no token-scope endpoint, so a
|
|
418
|
-
read-only token is detected when the upgrade comes back `403` — you'll get a
|
|
419
|
-
clear "token is read-only" message and nothing changes. Swap in a Read/Write
|
|
420
|
-
token (`spinuptui login`) to actually apply upgrades.
|
|
421
|
-
- **On-demand install.** If the chosen version isn't installed on the server yet,
|
|
422
|
-
SpinupWP installs it first; the event simply takes a little longer.
|
|
423
|
-
- **Pending platform upgrade.** If the site's server has a pending SpinupWP
|
|
424
|
-
platform upgrade, it can't be managed via the API until that runs — the picker
|
|
425
|
-
is blocked and points you to open the server in the web app (`w`).
|
|
426
|
-
- **Runs in the background.** The upgrade is tracked in the app's store, so you
|
|
427
|
-
can press `Esc` to close the modal and it keeps going — the site's row shows a
|
|
428
|
-
spinner and the target version (`→8.3`) until it settles, then refreshes to the
|
|
429
|
-
new version (or flags `⬆!` if it failed). The SiteDetail "PHP" field shows the
|
|
430
|
-
same in-progress state. You can launch upgrades on several sites at once. When an
|
|
431
|
-
upgrade finishes, a **toast** confirms it (`example.com upgraded to PHP 8.3`) —
|
|
432
|
-
useful since it often completes after you've closed the modal and moved on.
|
|
277
|
+
`u` on a site picks a new PHP version and applies it (`PUT /sites/{id}/php`),
|
|
278
|
+
tracking the event to completion in the background. Needs a Read/Write token.
|
|
279
|
+
Full details: [docs/php-upgrade.md](docs/php-upgrade.md).
|
|
433
280
|
|
|
434
281
|
## Server actions
|
|
435
282
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
`POST /servers/{id}/reboot` or `/services/{svc}/restart` and tracks the event to
|
|
440
|
-
completion — same background behavior as PHP upgrades (close the overlay and the
|
|
441
|
-
server's row keeps a spinner).
|
|
442
|
-
|
|
443
|
-
- **Needs a Read/Write token** (like PHP upgrades).
|
|
444
|
-
- **Reboot visibility.** Servers with a pending reboot show a `↻rbt` badge in the
|
|
445
|
-
Servers list and on the Dashboard's "Needs attention" panel.
|
|
446
|
-
- **Why a reboot is pending.** The API only exposes a `reboot_required` boolean —
|
|
447
|
-
no reason. So when you open the overlay on a flagged server, the app reads
|
|
448
|
-
Ubuntu's `/var/run/reboot-required` + `.pkgs` over SSH (read-only, reusing the
|
|
449
|
-
health view's connection) and shows the pending packages — typically a
|
|
450
|
-
**kernel/security update**. This is labeled as OS-level context, not as
|
|
451
|
-
SpinupWP's internal logic (a fleet-wide check confirmed the boolean tracks that
|
|
452
|
-
file 1:1).
|
|
453
|
-
- **Reboot is the big one** — its confirmation calls out that it takes the whole
|
|
454
|
-
server down briefly (every site on it); a service restart is a brief blip.
|
|
455
|
-
- **A toast on completion.** A reboot can take minutes; when it (or a restart)
|
|
456
|
-
finishes, a non-focus-stealing toast tells you (`example.com rebooted` /
|
|
457
|
-
`Nginx restarted on example.com`), so you don't have to keep checking.
|
|
283
|
+
`a` on a server reboots it or restarts a service (Nginx/PHP-FPM/MySQL/Redis),
|
|
284
|
+
and shows *why* a reboot is pending (the actual OS packages, read over SSH).
|
|
285
|
+
Needs a Read/Write token. Full details: [docs/server-actions.md](docs/server-actions.md).
|
|
458
286
|
|
|
459
287
|
## Site monitoring
|
|
460
288
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
- **`a` — uptime monitors.** Regular sites get a homepage monitor (up/down +
|
|
468
|
-
cert-expiry). Vanity sites get the full server treatment: healthz + load
|
|
469
|
-
push + a **Redis sentinel** — the heartbeat cron runs `redis-cli ping` every
|
|
470
|
-
minute and reports up/down to a `{server} redis` monitor (registered only
|
|
471
|
-
after probing that Redis actually answers on that box). Why it matters: with
|
|
472
|
-
SpinupWP's default object-cache drop-in, a dead Redis is **fatal (HTTP 500)
|
|
473
|
-
for every request that misses the page cache** — cached pages keep serving
|
|
474
|
-
200, so only the sentinel notices, within a minute.
|
|
475
|
-
- **`f` — the front-page check.** SpinupTUI reads your *healthy* front page,
|
|
476
|
-
derives a template-identity fingerprint from WordPress's own `body_class()`
|
|
477
|
-
output (`class="home`, `page-id-N`, …, falling back to the canonical link),
|
|
478
|
-
validates it against a throwaway search render to prove it discriminates,
|
|
479
|
-
and registers a Kuma keyword monitor at your chosen window (5m default —
|
|
480
|
-
the check is served straight from the page cache, so it costs the site
|
|
481
|
-
nothing). A red check means "answering 200 but serving the WRONG page" —
|
|
482
|
-
the stale/corrupt-page-cache incident — and shows as `WRONG PAGE SERVED` in
|
|
483
|
-
Details. Re-running `f` recalibrates the same monitor in place (history
|
|
484
|
-
survives a redesign).
|
|
485
|
-
- **`d` — the site doctor.** Read-only, zero setup, works without Kuma. It
|
|
486
|
-
fetches the page twice — once normally, once with the cache-bypass cookie —
|
|
487
|
-
and lets the server's `fastcgi-cache: HIT/BYPASS` headers prove which layer
|
|
488
|
-
answered each request, then compares template identity between the two. It
|
|
489
|
-
also knocks on the wp-admin door (the login page — never cached; Bedrock's
|
|
490
|
-
relocated `/wp/` login handled). Verdicts: healthy · stale-cache (with a
|
|
491
|
-
copyable runbook; the Elementor one-command flush is suggested only when
|
|
492
|
-
Elementor is detected in the served markup) · **partial-outage** (cached
|
|
493
|
-
pages 200, fresh renders or wp-admin 5xx — Redis or a PHP fatal; visitors
|
|
494
|
-
look fine, admins are broken) · recalibrate (`f` jumps straight there) ·
|
|
495
|
-
down · inconclusive.
|
|
496
|
-
- **`n` — alert wiring.** SpinupTUI detects the notification providers configured
|
|
497
|
-
in your Kuma (by name — Telegram, email, whatever you've set up) and shows
|
|
498
|
-
whether each is attached to this site's monitors (`✓` all / `◐` some / `○`
|
|
499
|
-
none); `⏎` toggles a provider across all of the site's checks at once.
|
|
500
|
-
Creating the provider (bot tokens etc.) is the one step that stays in Kuma.
|
|
501
|
-
|
|
502
|
-
The design principle across all of it: **the page cache makes sites look
|
|
503
|
-
healthy while things break behind it** — every check watches either *through*
|
|
504
|
-
the cache on purpose (is the right thing being served?) or deliberately
|
|
505
|
-
*around* it (is the machinery behind it alive?).
|
|
289
|
+
`M` on a site opens the monitoring overlay — uptime/Redis monitors, a
|
|
290
|
+
template-identity front-page check, a cache/outage doctor, and Kuma alert
|
|
291
|
+
wiring, all against your own Uptime Kuma instance. Full details:
|
|
292
|
+
[docs/site-monitoring.md](docs/site-monitoring.md) ·
|
|
293
|
+
[docs/uptime-kuma.md](docs/uptime-kuma.md).
|
|
506
294
|
|
|
507
295
|
## Creating & connecting servers
|
|
508
296
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
server's provider, region, and size (or from scratch on an empty fleet). It prices
|
|
514
|
-
the build from the provider's catalog (DigitalOcean / Vultr / Linode / Hetzner),
|
|
515
|
-
suggests a hostname from your fleet's naming convention, and lets you switch
|
|
516
|
-
provider (`p`), region (`g`), or size (`e`) and toggle backups (`b`) before
|
|
517
|
-
confirming. Because the SpinupWP API can't list your configured server providers,
|
|
518
|
-
the first time you build on a provider the overlay asks for its SpinupWP provider
|
|
519
|
-
id (Account Settings → Server Providers) and saves it — once per provider. The
|
|
520
|
-
build fires `POST /servers` and tracks the ~10-minute provision in the background.
|
|
521
|
-
|
|
522
|
-
**Connect it with a vanity site (`V`).** A brand-new server has **no site**, so
|
|
523
|
-
there's nothing to attach an SSH key to and no way for SpinupTUI to reach it — empty
|
|
524
|
-
servers are flagged in **amber** in the Servers list. A busy server benefits from
|
|
525
|
-
the same thing (a status page at its own hostname + a site user to hold your key),
|
|
526
|
-
so `V` is offered on **any server that doesn't yet have a site at its own
|
|
527
|
-
hostname** — it also appears under **Manage** in the server's Details panel. It
|
|
528
|
-
builds the small placeholder ("vanity") site end to end:
|
|
529
|
-
|
|
530
|
-
1. **DNS** — writes an `A` record for the hostname → the server IP (AWS Route 53),
|
|
531
|
-
using the connection from the DNS module.
|
|
532
|
-
2. **Propagate** — waits for the record to resolve (so Let's Encrypt can issue);
|
|
533
|
-
after ~2 minutes it offers to skip SSL for now or keep waiting.
|
|
534
|
-
3. **Site** — creates a blank site (`installation_method: "blank"`).
|
|
535
|
-
4. **HTTPS** — enables a free Let's Encrypt certificate.
|
|
536
|
-
5. **SSH key** — deep-links you to the site's **SFTP & SSH → Site User** to add
|
|
537
|
-
your key (the API can't do this), then waits for you to confirm.
|
|
538
|
-
6. **Publish** — seeds a minimal, brand-neutral status page (it reads its own
|
|
539
|
-
hostname, so it's reusable on any server). Press `o` to open the live site.
|
|
540
|
-
|
|
541
|
-
The whole build is a **resumable background job**: close the overlay and a header
|
|
542
|
-
badge keeps tracking it; press `V` on the server to reopen it (even after the site
|
|
543
|
-
exists). It survives quitting and relaunching the app. If a step fails, the overlay
|
|
544
|
-
shows where, with `r` to retry or `x` to discard.
|
|
297
|
+
`c` on the Servers tab provisions a new server (priced from the provider
|
|
298
|
+
catalog); `V` connects any server with no hostname site end-to-end — DNS, a
|
|
299
|
+
placeholder site, HTTPS, and an SSH-key handoff. Needs a Read/Write token. Full
|
|
300
|
+
details: [docs/creating-connecting-servers.md](docs/creating-connecting-servers.md).
|
|
545
301
|
|
|
546
302
|
## Privileged writes over SSH (sudo & SSH keys)
|
|
547
303
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
**Connect sudo (`S`).** Press `S` on a server and enter its SpinupWP **sudo user**
|
|
553
|
-
and that user's **sudo password** once. SpinupTUI validates them against the live server
|
|
554
|
-
(`sudo -S -p '' true`) and then holds the password **in memory for the session only**
|
|
555
|
-
— the username persists to config, the password is never written to plaintext config.
|
|
556
|
-
A connected server shows a green `● sudo` badge on its row; `S` again disconnects.
|
|
557
|
-
|
|
558
|
-
- **Remember in the macOS Keychain (opt-in).** Tick the toggle when connecting and the
|
|
559
|
-
password is saved to your **login Keychain** (service `spinup-sudo`, one item per
|
|
560
|
-
server) — never to `config.json`, which only keeps the username and a `keychain`
|
|
561
|
-
marker. Next time you press `S` on that server, sudo **auto-unlocks** with no
|
|
562
|
-
retyping (the first read may show macOS's own "allow access" prompt — choose Always
|
|
563
|
-
Allow). Press `f` to forget the saved password; disconnecting (`x`) a saved server
|
|
564
|
-
offers a no-password **reconnect** rather than the credential form. Off macOS the
|
|
565
|
-
toggle is absent and sudo stays in-memory per session.
|
|
566
|
-
|
|
567
|
-
**Grant / revoke an SSH key (`K`).** With sudo connected, press `K` on a site to write
|
|
568
|
-
keys into the site user's `authorized_keys`:
|
|
569
|
-
|
|
570
|
-
- **Pick which keys** — any of **your personal keys** (discovered from `~/.ssh/*.pub`
|
|
571
|
-
and your ssh-agent, so you can SSH/SFTP as yourself) and/or SpinupTUI's dedicated
|
|
572
|
-
**`spinup-tui` machine key** (an ed25519 identity generated once into the config dir,
|
|
573
|
-
deliberately **never added to your SpinupWP account** so SpinupWP's key reconciliation
|
|
574
|
-
can't clobber it). Your selection is remembered.
|
|
575
|
-
- **Pick the scope** — just this site, or **every site on the server** in one pass
|
|
576
|
-
(per-site progress, with a retry for any that fail).
|
|
577
|
-
- **Grant or remove** — `a`/`r` toggles the mode; removing pulls exactly the chosen
|
|
578
|
-
key lines and leaves every other key (including SpinupWP-managed ones) untouched. The
|
|
579
|
-
remote script is **idempotent** and a confirm overlay shows the exact command first.
|
|
580
|
-
- Site rows show what's granted at a glance — **👤** (your key) and/or **🔑** (the
|
|
581
|
-
machine key).
|
|
304
|
+
The API can't manage SSH keys or sudo users, so SpinupTUI does it directly:
|
|
305
|
+
`S` connects sudo on a server (optionally remembered in the macOS Keychain),
|
|
306
|
+
then `K` grants or revokes an SSH key on one site or the whole server. Full
|
|
307
|
+
details: [docs/privileged-ssh-writes.md](docs/privileged-ssh-writes.md).
|
|
582
308
|
|
|
583
309
|
## Cloning a server
|
|
584
310
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
1. **Plan** — pick which of the source's sites to clone (all selected by default;
|
|
592
|
-
`space` toggles). SpinupTUI sizes each one live (disk + database) into a payload total
|
|
593
|
-
so you know what you're moving; a concurrency cap protects the busy source.
|
|
594
|
-
2. **Destination** — provision a fresh server pre-matched to the source (reusing the
|
|
595
|
-
`c` flow), or `d` to pick an existing server as the target — each listed with its
|
|
596
|
-
current site count, so you can see at a glance how busy a candidate already is.
|
|
597
|
-
3. **Connect** — connect sudo on **both** servers. The clone is a server-to-server
|
|
598
|
-
**pull**: the destination pulls each site directly from the source over SSH (no
|
|
599
|
-
bytes routed through your laptop), authenticating with a key granted onto the source
|
|
600
|
-
for the job and **revoked when it's done**.
|
|
601
|
-
4. **Git access** (only when a Bedrock site is selected) — each repo gets its **own
|
|
602
|
-
read-only deploy key**, generated locally and never persisted: the public half
|
|
603
|
-
goes on the repo (added for you via `gh` when it's installed and authed, or shown
|
|
604
|
-
for a manual add — `o` opens the repo's deploy-key settings, `y` copies the key),
|
|
605
|
-
and the pair rides the site create so SpinupWP installs it as the new site's git
|
|
606
|
-
identity. Per-site keys are what let any number of Bedrock repos land on one
|
|
607
|
-
server — GitHub allows a deploy key on only **one** repository account-wide, so a
|
|
608
|
-
shared server key stops working at the second repo.
|
|
609
|
-
5. **Clone sites** — a live roster runs the sites concurrently, each advancing
|
|
610
|
-
`create → pull → config → verify → done` with **live transfer progress** (bytes,
|
|
611
|
-
rate, elapsed; database pulls show a true percent). Three stacks are handled:
|
|
612
|
-
**Standard WP** (files + database, with `wp-config` re-stamped for the
|
|
613
|
-
destination), **Bedrock** (git-native — created from the repo, `composer install`
|
|
614
|
-
over SSH, uploads + secrets pulled, `.env` re-stamped), and **files-only** for
|
|
615
|
-
non-WordPress sites (redirect shells, static/PHP — opt-in in Plan, no database).
|
|
616
|
-
The pull **detects each site's real webroot** rather than trusting settings —
|
|
617
|
-
`public/`-style layouts (with `wp-config.php` one level above the webroot) are
|
|
618
|
-
preserved, and mid-move layouts are normalized on the destination. **Additional
|
|
619
|
-
domains carry over** automatically (with their redirect settings), so the clone
|
|
620
|
-
answers for every hostname the source did.
|
|
621
|
-
6. **Verify** — drill into any cloned site for a source-vs-clone comparison (wp-cli
|
|
622
|
-
facts + an HTTP check that hits the **new** server while DNS still points at the old
|
|
623
|
-
one; files-only sites compare file count, size, and HTTP instead).
|
|
624
|
-
7. **DNS cutover** — the wizard **waits for your explicit go** (`c`) after the roster
|
|
625
|
-
settles, then repoints `A` records across each site's domains (apex + additional)
|
|
626
|
-
to the new server in one batched, partial-aware pass; `↑↓`/`space` include or
|
|
627
|
-
exclude individual records first. `www`-style records that follow the apex are
|
|
628
|
-
skipped, not clobbered. Cloudflare **proxied** records repoint automatically too
|
|
629
|
-
(their origin IP is always PATCHable even though their TTL stays fixed to
|
|
630
|
-
automatic). Records in zones you can't edit show your DNS access note and `⏎`
|
|
631
|
-
opens that zone's registrar console with the zone name copied — ready to paste.
|
|
632
|
-
|
|
633
|
-
The clone runs in the **background** — pressing `Esc` doesn't abandon it; a header
|
|
634
|
-
badge (`⠹ Cloning … — press C`) surfaces the in-flight job and `C` reopens the live
|
|
635
|
-
roster. `←` (or `h`) steps **back a screen**: the setup steps go back freely, and
|
|
636
|
-
once cloning has started the one back edge is DNS cutover → the clone roster — so
|
|
637
|
-
you can re-verify or retry a site before flipping live traffic. **Every job writes a full log** (`~/.config/spinupwp-tui/logs/`, passwords
|
|
638
|
-
redacted, self-pruning) — `⏎` on a failed site shows the complete error and `r`
|
|
639
|
-
retries just that site. Pairs naturally with the DNS module: **lower the TTLs**
|
|
640
|
-
(`n`/`N` → `⏎`) a day or two ahead so the cutover propagates fast.
|
|
311
|
+
`C` on a server opens a guided wizard that plans, sizes, and clones one or more
|
|
312
|
+
sites (Standard WP, Bedrock, or files-only) to a new or existing destination
|
|
313
|
+
over SSH, verifies each clone, and only repoints DNS when you approve it — all
|
|
314
|
+
in the background. Needs a Read/Write token + sudo on both ends. Full details:
|
|
315
|
+
[docs/cloning-a-server.md](docs/cloning-a-server.md) ·
|
|
316
|
+
[how it works](docs/clone-wizard-explained.md).
|
|
641
317
|
|
|
642
318
|
## Local working copies
|
|
643
319
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
— no SpinupWP writes.
|
|
649
|
-
|
|
650
|
-
- **Auto-discover (`S`, Stacks tab).** Scan one or more folders and match their
|
|
651
|
-
subdirectories to sites — by git remote, Bedrock `WP_HOME`, or folder name —
|
|
652
|
-
then batch-link the matches.
|
|
653
|
-
- **"Needs a local copy" report (`f`, Stacks tab).** Lists the managed sites you
|
|
654
|
-
have no usable local copy for (never linked, or a missing path), filterable by
|
|
655
|
-
stack.
|
|
656
|
-
- **Markers & drift.** Linked sites show `◆` in the lists; a linked, on-disk copy
|
|
657
|
-
shows its local git drift (`⇡N unpushed` / `● uncommitted`), read from the repo
|
|
658
|
-
with no network.
|
|
659
|
-
|
|
660
|
-
Config keys: `localRoots` (folders to scan) and `localSites` (per-site path +
|
|
661
|
-
local URL — tool-agnostic: Valet, Cove, LocalWP, Herd, DDEV, …).
|
|
320
|
+
`L` on a site links its local checkout (path + local URL); `t`/`v` open it. The
|
|
321
|
+
Stacks tab can auto-discover copies (`S`) and report sites still missing one
|
|
322
|
+
(`f`); linked copies show their local git drift. Full details:
|
|
323
|
+
[docs/local-working-copies.md](docs/local-working-copies.md).
|
|
662
324
|
|
|
663
325
|
## Database backup & sync
|
|
664
326
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
- **Download a backup (`d`).** Exports the production database with `wp-cli` into
|
|
672
|
-
a stage file *outside* the public webroot, gzips it, downloads it into the linked
|
|
673
|
-
copy's `sql/` folder, and removes the remote copy. Needs **no local WP-CLI** —
|
|
674
|
-
the export runs on the server. Available whenever a WordPress site is linked. A
|
|
675
|
-
spinner on the site's row tracks an in-flight download even if you close the
|
|
676
|
-
overlay; the saved path and size are shown on completion.
|
|
677
|
-
- **Pull production → local (`p`, opt-in).** A full refresh of your **local**
|
|
678
|
-
database from production: it backs up the local DB first (to
|
|
679
|
-
`sql/local_<timestamp>.sql.gz`), exports + downloads production, imports it
|
|
680
|
-
locally, rewrites production URLs → your local URL (`wp search-replace`), and
|
|
681
|
-
runs an optional `bin/sync.d/post-import.sh` hook if the project has one.
|
|
682
|
-
**This overwrites your local database**, so it's **off by default** — enable it
|
|
683
|
-
with `localSync` (see "Optional settings"). It needs a working local WP-CLI; if
|
|
684
|
-
it's missing you get a clear error rather than a broken run.
|
|
685
|
-
|
|
686
|
-
Everything is detected automatically, for Standard WP **and** Bedrock:
|
|
687
|
-
|
|
688
|
-
- the **remote document root** from the API (`/sites/{domain}/files{public_folder}`),
|
|
689
|
-
- the **SSH target** from the site/server (`{site_user}@{server_ip}`),
|
|
690
|
-
- the **local WordPress root** from the linked path (where `wp` runs — wp-config
|
|
691
|
-
for Standard WP, `wp-cli.yml` for Bedrock),
|
|
692
|
-
- the **local URL** for the rewrite from the link's local URL, falling back to the
|
|
693
|
-
project's `.env` `WP_HOME`.
|
|
694
|
-
|
|
695
|
-
If your project already has a `bin/sync.d/post-import.sh` (e.g. Elementor URL
|
|
696
|
-
swaps, plugin toggles), it runs with `WEB_DIR`, `SYNC_REMOTE_HOST`, and
|
|
697
|
-
`SYNC_LOCAL_HOST` set — so existing per-project tweaks carry over with no extra
|
|
698
|
-
configuration. When a project has **no** hook yet, the `p` confirm screen
|
|
699
|
-
explains what the hook is and offers `s` to scaffold an **inert** sample
|
|
700
|
-
`bin/sync.d/post-import.sh` (every example commented out, documented with the env
|
|
701
|
-
vars above) for you to edit. Backups stay **gzipped** in `sql/`; decompress with
|
|
702
|
-
`gunzip` when you need one.
|
|
703
|
-
|
|
704
|
-
Both `d` and `p` show their progress as a **building checklist** inside the
|
|
705
|
-
overlay — each step gets a `✓` as it completes, the running step spins, and a
|
|
706
|
-
failure marks the exact step with `✕` — so you can see everything that happened,
|
|
707
|
-
ending with the saved paths. It keeps running if you close the overlay (`Esc`);
|
|
708
|
-
reopen with the same key to watch it through.
|
|
327
|
+
On a linked WordPress site (Search tab), `d` downloads a gzipped production DB
|
|
328
|
+
backup into `sql/`; `p` (opt-in via `localSync`) pulls production into your
|
|
329
|
+
**local** database, backing it up first and rewriting URLs. Works with
|
|
330
|
+
Standard WP and Bedrock. Full details:
|
|
331
|
+
[docs/database-backup-sync.md](docs/database-backup-sync.md).
|
|
709
332
|
|
|
710
333
|
## Production media fallback
|
|
711
334
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
production.
|
|
717
|
-
|
|
718
|
-
It works by dropping a small, self-contained mu-plugin into your local copy
|
|
719
|
-
(`wp-content/mu-plugins/` or Bedrock's `web/app/mu-plugins/`). The plugin rewrites
|
|
720
|
-
any uploads URL whose file isn't present locally to the production origin —
|
|
721
|
-
covering the standard media functions, page-builder output (e.g. Elementor inline
|
|
722
|
-
CSS and gallery data), and **legacy paths** left over from a Standard-WP →
|
|
723
|
-
Bedrock conversion. Because it decides "missing" from the real document root and
|
|
724
|
-
redirects to the **same path** on production, your production routing (CDN/S3
|
|
725
|
-
redirects included) resolves whatever it's handed.
|
|
726
|
-
|
|
727
|
-
- **It runs in WordPress, not your web server**, so it works the same on Valet,
|
|
728
|
-
Herd, LocalWP, DDEV, MAMP — anything. No nginx/Apache config to get right.
|
|
729
|
-
- **Local-only and read-only on production** — it just hotlinks your own
|
|
730
|
-
production images. It self-disables when running on the production domain, so
|
|
731
|
-
it's inert if it ever gets deployed.
|
|
732
|
-
- **The plugin's presence is the on/off state** (no config flag). Press `m` to
|
|
733
|
-
toggle; `u` from the overlay updates it in place when a newer version ships.
|
|
734
|
-
- Needs production reachable and hotlinking allowed (it won't work behind staging
|
|
735
|
-
Basic-Auth). Images delivered by external CSS files or async JS/REST are the one
|
|
736
|
-
thing server-side rewriting can't catch.
|
|
335
|
+
`m` on a linked WordPress site drops a small mu-plugin that serves any
|
|
336
|
+
missing-locally upload straight from production — no file copying, works on
|
|
337
|
+
any local stack. Full details:
|
|
338
|
+
[docs/production-media-fallback.md](docs/production-media-fallback.md).
|
|
737
339
|
|
|
738
340
|
## DNS hosts, access & editing
|
|
739
341
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
never shown or changed. Moving a site can't take down its email.
|
|
745
|
-
|
|
746
|
-
- **The view.** Press `n` on a site for just that site's records, or `N` on a server
|
|
747
|
-
for every site on it; inside a site-scoped view, `a` expands to the whole server.
|
|
748
|
-
Each **site** is a line, labeled by its own domain (even when it's a subdomain),
|
|
749
|
-
with its hosting record's type, **TTL in seconds**, value, a `◀ here` flag when the
|
|
750
|
-
record points at this server, and a `+www` tag when `www` simply follows the apex.
|
|
751
|
-
A site's additional domains nest beneath it, so a domain portfolio reads as one
|
|
752
|
-
site, not many. TTLs come from the zone's authoritative nameserver (the configured
|
|
753
|
-
value, not a counted-down one), so they show even for hosts you haven't connected;
|
|
754
|
-
`r` refreshes.
|
|
755
|
-
- **Access (`✓ ↗ ○ ·`).** Each record's zone shows whether you can edit it: `✓`
|
|
756
|
-
editable, `↗` web-only handoff, `○` the provider has an API but you haven't
|
|
757
|
-
connected an account that holds the zone, `·` unknown. A zone is `✓` only when a
|
|
758
|
-
connected account of the provider that actually serves it (its live nameservers)
|
|
759
|
-
holds it — so a stale or duplicate zone elsewhere never shows a false green. With
|
|
760
|
-
two or more accounts connected, an **ACCOUNT** column names the owning one.
|
|
761
|
-
- **Edit a TTL (`⏎`).** On an editable record, `⏎` opens a focused editor — pick a
|
|
762
|
-
preset or a custom value, confirm, and it's written to **AWS Route 53** or
|
|
763
|
-
**Cloudflare** through your connected account. This is the prep step for a low-risk
|
|
764
|
-
migration: lower the TTL, cut over, then restore it. Before writing, an edit-time
|
|
765
|
-
check re-reads the live nameservers and **blocks** the change if the connected
|
|
766
|
-
account's zone isn't the one actually serving the domain. Route 53 changes are
|
|
767
|
-
followed to completion; the record shows an "updating" status that keeps ticking
|
|
768
|
-
even if you leave the view. Cloudflare **proxied** records keep their TTL fixed
|
|
769
|
-
to automatic, so it's not editable here — repointing still works (next).
|
|
770
|
-
- **Repoint a record (`p`).** The same focused editor, on the record's **value** —
|
|
771
|
-
where it points. The picker leads with **your SpinupWP servers** (name + IP, the
|
|
772
|
-
record's current home tagged), because pointing a record at one of your own boxes
|
|
773
|
-
is what this is for; a custom IP is the fallback. The same confirm and NS
|
|
774
|
-
pre-flight gate apply, the write is followed to completion in the background, and
|
|
775
|
-
the inventory row shows `→ new IP` while it applies. A Cloudflare **proxied**
|
|
776
|
-
record repoints its **origin** behind the proxy (visitors keep resolving
|
|
777
|
-
Cloudflare's IPs). CNAMEs aren't repointed — they follow their target, which is
|
|
778
|
-
the record to edit. This is the standalone version of the clone wizard's DNS
|
|
779
|
-
cutover: migrate a single site, finish a cutover by hand, or fix a stale record.
|
|
780
|
-
- **Connect a provider (`c`).** Manage credentials for the selected zone's
|
|
781
|
-
provider — **AWS Route 53** (an IAM access key), **Cloudflare** (a scoped token),
|
|
782
|
-
or **GoDaddy** (a Production API key). Multiple accounts per provider are
|
|
783
|
-
supported, with a drill-down into each account's zones. Credentials are verified
|
|
784
|
-
before they're stored, kept in `config.json` (chmod 600), and the matching
|
|
785
|
-
environment variables are honored (`CLOUDFLARE_API_TOKEN`, `AWS_ACCESS_KEY_ID` /
|
|
786
|
-
`AWS_SECRET_ACCESS_KEY`, `GODADDY_API_KEY` / `GODADDY_API_SECRET`). Secrets are
|
|
787
|
-
masked as you type. Listing hosts needs only read access (Cloudflare `Zone:Read`);
|
|
788
|
-
**editing (a TTL or a repoint)** needs write access — Route 53 record writes, or a
|
|
789
|
-
Cloudflare `Zone.DNS:Edit` token.
|
|
790
|
-
- **Web-only hosts (GoDaddy, Namecheap, Network Solutions, …).** A registrar with
|
|
791
|
-
no usable API shows `↗`; press `w` (in the inventory or the connect overlay) to
|
|
792
|
-
open its web console — for GoDaddy specifically, your Clients hub, with the
|
|
793
|
-
domain copied to your clipboard so you can paste it after logging in as the
|
|
794
|
-
client. Every `↗` zone also shows an **access note** — "Delegate Access" by
|
|
795
|
-
default (the assumed-normal case for a client's registrar), or your own
|
|
796
|
-
per-zone override (e.g. an IT vendor's contact) when that default doesn't hold.
|
|
797
|
-
Press `c` on any such zone to open **Manage Access**: it lists every zone
|
|
798
|
-
already known at that host, `n` edits the selected zone's note, and `r`
|
|
799
|
-
resolves your whole fleet's DNS to fill in any zones it hasn't seen yet.
|
|
800
|
-
|
|
801
|
-
Provider credentials are optional — without them you still get the full host
|
|
802
|
-
inventory and TTLs, just without the editable/account columns or in-place editing.
|
|
342
|
+
`n` (site) / `N` (server) show only a site's own hosting records — never
|
|
343
|
+
MX/TXT/other zone records — with TTLs and edit access. `⏎` edits a TTL, `p`
|
|
344
|
+
repoints a record to another of your servers (AWS Route 53 / Cloudflare). Full
|
|
345
|
+
details: [docs/dns-access-editing.md](docs/dns-access-editing.md).
|
|
803
346
|
|
|
804
347
|
## Development
|
|
805
348
|
|
|
@@ -814,15 +357,17 @@ bun run typecheck # tsc --noEmit
|
|
|
814
357
|
SPINUP_DEV_MODE=1 bun run dev
|
|
815
358
|
```
|
|
816
359
|
|
|
817
|
-
Boots straight into the dashboard against
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
account. A purple **`DEV MODE`** badge in
|
|
821
|
-
write action (PHP upgrade, HTTPS
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
360
|
+
Boots straight into the dashboard against an in-memory example fleet (12
|
|
361
|
+
servers, ~80 sites — a deterministic mix of Standard WP, Bedrock, and non-WP
|
|
362
|
+
sites, EOL Ubuntu/PHP versions, and pending updates) — no API token, no network
|
|
363
|
+
calls, nothing that can touch a real account. A purple **`DEV MODE`** badge in
|
|
364
|
+
the header makes it unmistakable. Every write action (PHP upgrade, HTTPS
|
|
365
|
+
toggle, purge cache, reboot, create a server/site) works against the fake data
|
|
366
|
+
and shows the same in-progress/toast behavior as the real thing, so it's
|
|
367
|
+
useful for screenshots (the ones above were generated this way), walkthroughs,
|
|
368
|
+
and UI work without a live account on hand. The clone wizard's SSH-based
|
|
369
|
+
file/DB pull is out of scope — it always talks to real servers over SSH,
|
|
370
|
+
independent of the API client.
|
|
826
371
|
|
|
827
372
|
The fixture fleet lives in `src/dev/fixtures.ts`; the fake client it's served
|
|
828
373
|
through is `src/dev/mockClient.ts`.
|