Install
openclaw skills install @holgergruenhagen/service-cutover-checklistMove a running service to another server: inventory, rehearsal, cutover runbook with measured downtime, rollback paths, and observation before the old copy is retired.
openclaw skills install @holgergruenhagen/service-cutover-checklistMove one running service (an app, its workers, its data, its scheduled jobs, its name in DNS) from an old server to a new one with a short, measured outage and a rollback that has been tried. The pattern: inventory both sides, build and rehearse on the new server while the old one keeps serving, switch in a short window, observe, and only then retire the old copy.
The owner performs the switch. You inventory, plan, prepare commands, run read-only checks, and measure. Everything this skill needs is in this one file. Without shell access, work in paste mode: the owner runs the read-only commands below and pastes the output.
mktemp. Use mktemp -d for working folders, never fixed names
in shared folders, and name each scratch folder in the report.Collect each item with its source command. Mark anything you could not read as "not checked".
| Item | Read-only command (Linux, systemd) |
|---|---|
| Processes and units | systemctl list-units --type=service --state=running, systemctl show <unit> -p ActiveState -p NRestarts -p ControlGroup |
| How a unit runs | systemctl show <unit> -p User -p WorkingDirectory -p EnvironmentFiles -p FragmentPath -p MemoryMax (paths and names only) |
| Program it starts (path only) | systemctl show <unit> -p ExecStart --value | cut -d';' -f1; the full command line can carry credentials in its arguments, so ask the owner to read the arguments and describe them without values |
| Environment names only | systemctl show <unit> -p Environment --value | grep -oE '(^|[[:space:]])[A-Za-z_][A-Za-z0-9_]*=' | sed -E 's/^[[:space:]]+//; s/=$//'; for an env file: grep -oE '^[[:space:]]*(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*=' <file> | sed -E 's/^[[:space:]]*(export[[:space:]]+)?//; s/=$//'. Both print only the variable-name part before = and never any text after it; lines without a name (comments, continuation lines) print nothing. Before recording the list, check that every line is a plain variable name; drop anything else and do not paste the raw output |
| Listening ports | ss -ltnp (who listens where; local-only vs public) |
| Reverse proxy and TLS | from the proxy site config for the domain, only the server name and upstream lines (other lines can hold credentials); certificate: openssl s_client -connect <name>:443 -servername <name> </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate -ext subjectAltName |
| Data locations and size | working directory, data folders, database files; du -sh <path>, df -h |
| Databases | engine and version; for SQLite note any -wal and -shm files next to the database |
| Containers (if used) | docker ps --format '{{.Names}} {{.Image}} {{.Ports}}', docker inspect --format '{{json .Mounts}}' <name>, docker compose config --services, docker compose config --volumes |
| Scheduled jobs | systemctl list-timers --all; crontab -l -u <user> for each relevant user; the system-wide cron folder. Read only: record schedule and program path, never create, change, or remove a job, and leave out arguments that could carry credentials |
| Time zone | timedatectl show -p Timezone; the time zone the app itself uses (an env name such as TZ) |
| DNS | dig +noall +answer <name> (record and TTL), dig +short NS <zone>; whether a CDN proxy sits in front |
| Inbound callers | webhooks, API clients, IP allowlists at partners that name the old address |
| Outbound targets | mail relay and port, APIs, proxies; port reachability from the new server: nc -vz -w 5 <host> <port> (opens and closes a connection, sends no data) |
| Users and ownership | service user, uid and gid, file owners and modes of data and credential files |
| Backups | tool, schedule, which paths it covers, last successful run, where it stores |
| Monitoring | health endpoint, external monitors, dead-man checks that ping from the server |
| Neighbours | other services on the same servers that share ports, users, databases, or the proxy |
| Versions | app, runtime, database, OS on both servers |
Also read the last days of the service's own run history (logs, job table) to find the quietest time for the window and how clients behave during an outage (do they retry, and for how long).
One row per item that moves: path or object, size, owner, how it moves, when.
rsync -aHAX --numeric-ids and run it twice; the second
run shows the delta you will copy in the window.Consistent copies, only after the service has stopped writing:
sqlite3 <db> ".backup '<copy>'" as the file's owner, then
sqlite3 <copy> "PRAGMA integrity_check" must print ok. Never copy a live database file
together with its -wal file.tar -C <dir> -cf - . | ssh <new> 'tar -C <target> -xf -' or rsync; record
sha256sum before and check with sha256sum -c on the new server.Every place that names the old server: DNS records, proxy upstreams, webhooks at partners, partner IP allowlists, monitors, backup jobs, SSH configs, scripts, documentation. One row each: old value, new value, who changes it, how to check it.
dig @<authoritative server> <name> and a public
resolver such as dig @1.1.1.1 <name>.A table: step, who (owner or agent), command or action, success check, rollback, measured minutes. Typical order:
-wal, no open handles
(fuser <file>).Write down before the window how you will measure:
journalctl -u <unit> -o short-iso --since -15min).while sleep 2; do printf '%s %s\n' "$(date -u +%T)" "$(curl -s -o /dev/null -m 3 -w '%{http_code}' https://<name>/<health>)"; donemktemp -d), as an approved step.ansible-playbook --check --diff) and confirm that a second real run reports no changes.systemctl is-active <unit>, and systemctl show <unit> -p ControlGroup names the
service's own system slice).These only read.
systemctl list-timers), time zone as on the old server or deliberately changed.These send data or start work. Show each one with target, data, and side effects, and run it only after the owner approves that exact step.
mktemp -d), never
over the live data; the folder is named in the report and moved to the Trash afterwards.Name what you checked and what you could not check.
One line per step in the owner's chosen place:
<UTC time> | <service> | <step> | <who> | <result> | <rollback still possible: A, B, none>