docs: add runbook for renaming an office's subdomain (local-summit -> local-centrestreetsmiles example)

Covers the scoped sudoers grant, updating the three per-office config files, reissuing the LAN cert, deploying nginx, repointing the Cloudflare Tunnel, the manual LAN DNS step, updating Twilio/Telnyx webhooks, and keeping per-office config out of git with skip-worktree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 23:05:03 -04:00
co-authored by Claude Sonnet 5
parent 6148cfa848
commit 75658aea9c
+102 -14
View File
@@ -666,20 +666,20 @@ In `apps/Backend`, add the subdomain URL to the allowed CORS origins so login an
---
### Example — Adding Summit Dental Care
### Example — Adding Centre Street Smiles
**Office subdomain:** `summitdentalcare.mydentalofficemanagement.com`
**Office subdomain:** `centrestreetsmiles.mydentalofficemanagement.com`
**Step 1:** Already done (domain is on Cloudflare).
**Step 2:** Install `cloudflared` on Summit Dental's PC.
**Step 2:** Install `cloudflared` on Centre Street Smiles' PC.
**Step 3:** Run `cloudflared tunnel login` on Summit Dental's PC.
**Step 3:** Run `cloudflared tunnel login` on Centre Street Smiles' PC.
**Step 4:**
```bash
cloudflared tunnel create summit-dental-app
# Example output: Created tunnel summit-dental-app with id a1b2c3d4-...
cloudflared tunnel create centrestreetsmiles-app
# Example output: Created tunnel centrestreetsmiles-app with id a1b2c3d4-...
```
**Step 5 — `/etc/cloudflared/config.yml`:**
@@ -688,14 +688,14 @@ tunnel: a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /home/ee/.cloudflared/a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json
ingress:
- hostname: summitdentalcare.mydentalofficemanagement.com
- hostname: centrestreetsmiles.mydentalofficemanagement.com
service: http://localhost:3000
- service: http_status:404
```
**Step 6:**
```bash
cloudflared tunnel route dns summit-dental-app summitdentalcare.mydentalofficemanagement.com
cloudflared tunnel route dns centrestreetsmiles-app centrestreetsmiles.mydentalofficemanagement.com
```
**Step 7:**
@@ -705,9 +705,9 @@ sudo systemctl enable cloudflared
sudo systemctl start cloudflared
```
**Step 8:** Add `summitdentalcare.mydentalofficemanagement.com` to `allowedHosts` in `vite.config.js`.
**Step 8:** Add `centrestreetsmiles.mydentalofficemanagement.com` to `allowedHosts` in `vite.config.js`.
**Step 9:** Add `https://summitdentalcare.mydentalofficemanagement.com` to backend CORS allowed origins.
**Step 9:** Add `https://centrestreetsmiles.mydentalofficemanagement.com` to backend CORS allowed origins.
---
@@ -721,7 +721,7 @@ tunnel at nginx's port-80 block instead, which only forwards `/api/twilio/*` and
and returns `403` for everything else (see [nginx.conf](nginx.conf)) — the rest of the app stays
unreachable from the internet even though the tunnel is live.
Example actually used for Summit Dental Care's Twilio/Telnyx integration — paste this into
Example actually used for Centre Street Smiles' Twilio/Telnyx integration — paste this into
`/etc/cloudflared/config.yml` (`sudo nano /etc/cloudflared/config.yml`, this is a YAML file, not
the certbot `.ini` credentials file from the LAN HTTPS section above):
@@ -730,7 +730,7 @@ tunnel: fc423bdb-eaae-4af5-bd6d-961a60b1e624
credentials-file: /home/gg/.cloudflared/fc423bdb-eaae-4af5-bd6d-961a60b1e624.json
ingress:
- hostname: summit.mydentalofficemanagement.com
- hostname: centrestreetsmiles.mydentalofficemanagement.com
service: http://localhost:80
- service: http_status:404
```
@@ -738,7 +738,7 @@ ingress:
Then route DNS and install the service same as Steps 6–7 above:
```bash
cloudflared tunnel route dns summit-dental-twilio summit.mydentalofficemanagement.com
cloudflared tunnel route dns centrestreetsmiles-twilio centrestreetsmiles.mydentalofficemanagement.com
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
@@ -757,7 +757,7 @@ Each office runs its own `cloudflared` tunnel on its own PC. Ports never conflic
| Office | Local access | Public access | Tunnel name |
|---|---|---|---|
| Community Dentists of Lowell | `http://192.168.1.236:3000` | `https://communitydentistsoflowell.mydentalofficemanagement.com` | `dental-app` |
| Summit Dental Care | `http://<its-ip>:3000` | `https://summitdentalcare.mydentalofficemanagement.com` | `summit-dental-app` |
| Centre Street Smiles | `http://<its-ip>:3000` | `https://centrestreetsmiles.mydentalofficemanagement.com` | `centrestreetsmiles-app` |
| Next office | `http://<its-ip>:3000` | `https://<subdomain>.mydentalofficemanagement.com` | `<office>-app` |
---
@@ -947,6 +947,94 @@ since it fixes every device on the network in one place instead of requiring a h
---
## Renaming an Office's Subdomain (e.g. Moving to a New Office)
When a PC moves to a new office and needs a new subdomain (e.g. `local-summit` →
`local-<newoffice>`), both the LAN HTTPS cert (above) and the Cloudflare Tunnel (above) need to be
re-pointed. This repo's `nginx.conf`, `apps/Backend/.env`, and `apps/Frontend/vite.config.ts` are
**per-office files** — see the note at the end of this section before committing/pushing anything.
### Step 1 — Grant narrow passwordless sudo for the migration commands
Several steps below need `sudo` (certbot, nginx, cloudflared). Since this is normally done
through an assistant/automation that can't type a password interactively, create a scoped
sudoers rule limited to exactly these commands (replace `<youruser>` with your actual username):
```bash
sudo tee /etc/sudoers.d/office-migration <<'RULE'
<youruser> ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx, /usr/sbin/nginx -t, /usr/bin/systemctl restart cloudflared, /usr/bin/certbot certonly*, /usr/bin/tee /etc/cloudflared/config.yml, /usr/bin/tee /etc/nginx/sites-available/dental-app, /usr/bin/tee /etc/nginx/nginx.conf
RULE
sudo chmod 440 /etc/sudoers.d/office-migration
sudo visudo -c
```
Remove it when the migration is done: `sudo rm /etc/sudoers.d/office-migration`.
### Step 2 — Update the repo files
In `nginx.conf`, `apps/Backend/.env`, and `apps/Frontend/vite.config.ts`, replace every
`local-<oldoffice>` / `<oldoffice>` occurrence with `local-<newoffice>` / `<newoffice>`. Also
update the hardcoded LAN IP in `vite.config.ts`'s `allowedHosts` if it changed.
### Step 3 — Issue a new cert for the new LAN hostname
```bash
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
--key-type ecdsa -d local-<newoffice>.mydentalofficemanagement.com --non-interactive --agree-tos
```
> If the new hostname is longer and `nginx -t` later fails with `could not build
> server_names_hash, you should increase server_names_hash_bucket_size: 64`, see the fix in
> [Step 6 above](#step-6--point-nginx-at-the-new-cert).
### Step 4 — Deploy the updated nginx config and reload
```bash
sudo cp nginx.conf /etc/nginx/sites-available/dental-app
sudo nginx -t && sudo systemctl reload nginx
```
### Step 5 — Point the Cloudflare Tunnel at the new public hostname
Edit `/etc/cloudflared/config.yml`'s `hostname:` line to the new public subdomain (keep the same
`tunnel:` ID and `credentials-file:` — it's the same tunnel, just a new hostname), then:
```bash
sudo systemctl restart cloudflared
cloudflared tunnel route dns <tunnel-id-or-name> <newoffice>.mydentalofficemanagement.com
```
### Step 6 — Add the new LAN DNS record
In the Cloudflare dashboard, add the `local-<newoffice>` → this PC's LAN IP record exactly as in
[Step 1 of LAN HTTPS Setup](#step-1--create-the-dns-record-in-cloudflare-not-the-registrar) above.
This has to be done manually in the dashboard — it needs the Cloudflare account login, not just
the scoped DNS-01 API token in `cloudflare.ini`.
### Step 7 — Update third-party webhook URLs
Twilio and Telnyx dashboards both have the old public hostname registered as a webhook URL —
update both to the new `<newoffice>.mydentalofficemanagement.com`, or calls/texts stop arriving.
### Note — don't push the renamed config files to Gitea if this repo is shared
If this Gitea repo is **this office's own dedicated repo** (not pulled by any other office's
PC), committing and pushing the renamed `nginx.conf` / `.env` / `vite.config.ts` is safe — it only
affects this office. But if another PC with a different office's hostname (e.g. `local-broadway`)
ever clones or pulls from this *same* repo, pushing would either create a merge conflict or
silently overwrite that PC's config on its next pull. When in doubt, keep these three files out of
git entirely with:
```bash
git update-index --skip-worktree nginx.conf apps/Backend/.env apps/Frontend/vite.config.ts
```
This makes git treat them as permanently unchanged — the new subdomain keeps working locally, but
the files never show up in `git status`/`diff` and can't be committed or pushed, even by accident
via a future `git add -A`. To undo: `git update-index --no-skip-worktree <file>`.
---
## Payment OCR Service Setup (Google Cloud Vision)
The Payment OCR Service (`apps/PaymentOCRService`, port 5003) uses Google Cloud Vision to read