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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user