eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– Migrating an instance from one server to another
2
3> πŸ€– **Not yet encoded β€” do not run from this page.** There is no
4> `ops` verb for instance migration yet. When a migration is next needed,
5> the move is to build one from this record (`ops migrate <host> <box>` or
6> similar, per `deploy/README.md Β§ The operator surface`'s no-cookbooks
7> rule), then run the verb β€” and mark this page historical.
8
9Status: **design record** (the shape below was validated once in practice). Moving a running messenger
10instance to a new box is **data movement + a DNS flip**, done so it's reversible:
11copy the DB, keep the old instance running as a fallback, and only cut DNS over
12once the new box is verified. The flip itself is reversible too.
13
14The key property: **keep the hostname the same**. Browser keys (and passkeys, for
15the home server) are per-origin, so an unchanged origin means members' keys,
16sessions, and history are all unaffected β€” only the DNS record moves.
17
18## Before you start
19
20- SSH to both the source and destination boxes.
21- The destination is already bootstrapped (`lchat bootstrap`; router running).
22- Know the instance's **admin token** and any per-instance config (see step 1).
23
24## Steps
25
26### 1. Recover the instance's config from the source
27
28The instance's DB stores only the **hash** of the admin token β€” the plaintext
29lives in the source's deploy env / secret store. Recover it, plus any per-instance
30config the instance reads (`LCHAT_PUSH_RELAY_URL`/`_TOKEN`, `LCHAT_HOME_ORIGIN`,
31`LCHAT_APP_STORE_URL`, APNs vars, …). You'll write these into the destination's
32EnvironmentFile in step 4. **Preserve the admin token** β€” minting a new one
33invalidates every saved admin link.
34
35### 2. Snapshot the DB consistently
36
37Use SQLite's `VACUUM INTO` β€” it produces a single, compacted, transaction-consistent
38file and is **safe on a live DB** (no need to stop the instance):
39
40```sh
41sqlite3 /path/to/lchat.db "VACUUM INTO '/tmp/snapshot.db'"
42sqlite3 /tmp/snapshot.db "PRAGMA integrity_check" # expect: ok
43```
44
45If the source hides the DB behind an opaque store (many identically-named DB files),
46identify the right one by **content** β€” e.g. grep the files for the instance's
47`admin_token_hash` (`hex(sha256(admin token))`), which is unique per instance.
48
49`VACUUM INTO` snapshots the **DB only**. Attachment/photo/video bytes live in a
50**sibling directory** `<db>-blobs/` (e.g. `/data/<host>-blobs/`), not in the DB β€”
51so it must be copied separately (step 3), or migrated messages lose their images
52and videos. The files are immutable and content-addressed by a random id, so a
53live copy is safe. (An older instance that predates disk-backed blobs has no such
54directory β€” its blobs are still inside the DB and need nothing extra.)
55
56### 3. Copy the snapshot to the destination
57
58Copy `/tmp/snapshot.db` to the destination as `/data/<host>.db` and verify it
59landed intact (`integrity_check` + a couple of row counts against the source).
60**Also copy the blob directory** if the source has one:
61
62```sh
63rsync -a /data/<host>-blobs/ <dest>:/data/<host>-blobs/
64```
65
66These are the disk-backed attachment bytes the DB references by id; copy them at
67or after the DB snapshot (blobs uploaded to the source afterward are subject to
68the same gap check as messages β€” see step 5).
69
70**Also copy the VAPID keyfile** (`/data/<host>.vapid`, another DB sibling β€”
71kept out of the DB so S3 replicas never carry a private key). Without it the
72destination re-mints the pair on first boot and every member's web-push
73subscription silently breaks until their next visit re-subscribes them.
74
75### 4. Import on the destination (preserve token + config)
76
77With the migrated DB already at `/data/<host>.db`, run `add-instance` with
78`--admin-token` so it **imports** the existing token instead of minting a new one
79(a fresh token would rewrite `admin_token_hash` on boot and orphan every saved
80admin link). It writes the EnvironmentFile, registers the route, starts the unit
81against the pre-placed DB, health-checks it, and reloads the router:
82
83```sh
84lchat add-instance <host> \
85 --admin-token "<existing token>" \
86 --push-relay-url https://push.example.com --push-relay-token "<existing relay token>"
87```
88
89Pass whatever per-instance config the source had (`--push-relay-url`/`-token`,
90`--home-origin`, `--app-store-url`). Verify **before any DNS change**, over the
91instance's unix socket: `GET /` β†’ 200, an admin-token request β†’ 200, and the row
92counts match the source.
93
94### 5. Gap check
95
96Confirm nothing was written to the source between the snapshot and the cutover
97(compare a row count, e.g. `messages`, source-now vs snapshot). For a busy
98instance, freeze writes or take the snapshot at cutover time β€” writes that land on
99the source after the snapshot are **not** on the destination.
100
101### 6. Cut over DNS
102
103Flip the host's A record to the destination's IP. **Lower the record's TTL ahead of
104time** (to ~60s, ideally a TTL-worth before the cutover) so the flip propagates
105fast; otherwise resolvers that cached the old IP keep hitting the old box until the
106old TTL expires (harmless while the old box is still up as a fallback).
107
108The TLS cert **can't be pre-issued**: autocert's HTTP-01 validates by resolving the
109host over public DNS, so the cert only issues once DNS points at the destination.
110Authoritative DNS updates in seconds (only resolver *caches* lag), so the cert
111issues on the first HTTPS hit right after the flip.
112
113### 7. Verify + keep the fallback
114
115`GET https://<host>/` β†’ 200 with a valid (Let's Encrypt) cert, serving the migrated
116data. **Leave the source instance running** until you're confident β€” the migration
117is a copy, so flipping DNS back is an instant rollback. Retire the source only
118afterward.
119
120## Bots (sidecars)
121
122The instance's **bot agent-users migrate with the DB** (they're rows), but the
123**sidecar** β€” the Node process and its session files (the bots' identities) β€” is
124separate and does *not* come across in the DB copy. Do **not** re-run `add-bot` on
125the destination: that mints a *new* set of agent identities (duplicate bots in the
126Helpers catalog). Instead either copy the sidecar's session files to the
127destination and start `lchat-bot@<host>` there, or point the existing sidecar at
128the destination (it follows the host over DNS and reconnects with its persisted
129token). Plan the bot move as its own step.
130
131## What makes this safe
132
133- **Reversible**: the source is copied, never moved; DNS flip is reversible; the
134 cert issues automatically post-flip.
135- **No key churn**: same origin β‡’ per-origin browser keys/passkeys untouched.
136- **Consistent snapshot**: `VACUUM INTO` captures a clean point-in-time DB; the
137 sibling `<db>-blobs/` dir (immutable, content-addressed files) copies alongside.
138