From 75658aea9c0d032ec7a591bfe71c12a0dde567de Mon Sep 17 00:00:00 2001 From: Gitead Date: Mon, 5 Oct 2026 23:05:03 -0400 Subject: [PATCH] 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 --- README.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2f36c55a..312b52fb 100644 --- a/README.md +++ b/README.md @@ -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://:3000` | `https://summitdentalcare.mydentalofficemanagement.com` | `summit-dental-app` | +| Centre Street Smiles | `http://:3000` | `https://centrestreetsmiles.mydentalofficemanagement.com` | `centrestreetsmiles-app` | | Next office | `http://:3000` | `https://.mydentalofficemanagement.com` | `-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-`), 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 `` with your actual username): + +```bash +sudo tee /etc/sudoers.d/office-migration <<'RULE' + 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-` / `` occurrence with `local-` / ``. 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-.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 .mydentalofficemanagement.com +``` + +### Step 6 — Add the new LAN DNS record + +In the Cloudflare dashboard, add the `local-` → 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 `.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 `. + +--- + ## Payment OCR Service Setup (Google Cloud Vision) The Payment OCR Service (`apps/PaymentOCRService`, port 5003) uses Google Cloud Vision to read