#167 was closed as completed, but operation 2 of the three it described - move a deployed app between servers - has no primitive in the published package. This splits it out so it can be tracked on its own, with the evidence.
What is actually in 0.12.7
Grepped with word boundaries over the whole published dist (the word boundaries matter: my earlier claim that moveSite existed came from a substring match on removeSiteFromCloudConfig and moveSiteInput inside RemoveSiteInput):
$ grep -rohE '\b(moveSite|migrateSite|transferSite|relocateSite|moveApp|consolidateServer|attachSite|detachSite|renameServer|deleteServer|listServers)\b' package/dist/ | sort | uniq -c
23 listServers
10 deleteServer
4 renameServerAgainst #167's three operations:
| #167 operation | 0.12.7 | Status |
|---|---|---|
| 1. Attach an app to an existing server as a site | attachTo, attachToComputeInfrastructure, ensureAttachModeDatabase, allowStateOnly | works, guardrails landed in #168/#169 |
| 2. Move a deployed app between servers | nothing | absent |
| 3. Rename a server in place | HetznerClient.renameServer(id, name) | provider call only, no orchestration |
renameServer is reachable (via the exported HetznerClient), but it is the raw Hetzner API call. Nothing updates the ts-cloud/* labels, the state file, or the inventory record alongside it, so calling it leaves the box named one thing and labelled another. Worth noting rename is the cheap one: install paths key off siteInstallBase(slug, site) and DNS points at the IP, so the server name is only load-bearing for box lookup.
Why a move cannot be composed from what exists
An attach deploy already stands a site up on a target box, so most of "arrive" is solved. What has no primitive is carrying the state and draining the source. From the layout ts-cloud itself defines, a move has to carry:
- the release tree at
siteInstallBase(slug, site)(/var/www/<slug>-<site>), or re-deploy into it - persistent state outside the release tree (
/var/www/<slug>-shared/…, e.g. the SQLite file the deploy symlinks there) - the database role and schema that
ensureAttachModeDatabaseprovisions inside the host's Postgres - the systemd units:
<slug>-<site>@<sha>.service, plus-queue-<i>and-daemon-<name> - the rpx registry fragment
/etc/rpx/sites.d/<slug>.json, and the certificates in/etc/rpx/certs - the owner stamp
<base>/.ts-cloud/ownerand the deploy history log beside it - DNS, last, and reversibly
Every one of those is a concept ts-cloud owns. What is missing is the operation that sequences them, and specifically the data-carrying step, which no redeploy can substitute for.
Where the plan should live
stacksjs/stacks#2342 asks buddy's --dry-run to print "the plan ts-cloud produced". For move that seems right: it is the operation whose plan is genuinely non-obvious (what is copied, what is recreated, what DNS changes, what is deleted and when), and the plan is a property of the operation, not of the CLI that calls it. For attach / destroy / rename, buddy can compose a plan from existing primitives without one upstream.
If that is the wrong split, it is worth saying so on #2342 before the CLI side commits to a shape.
Related
- #167 - the parent, closed with operation 2 outstanding
- #168, #169 - the co-hosting guardrails, both landed
- stacksjs/stacks#2342 - the buddy CLI surface
- stacksjs/stacks#2378 -
buddy cloud:sites, the read side, which is what makes a move reviewable