docs: add LAN HTTPS setup guide (nginx + certbot + Cloudflare DNS-01)
Documents the trusted-certificate setup used for local staff access, needed because the Screen Capture API (Copy Agent screenshots) requires a secure context.
This commit is contained in:
104
README.md
104
README.md
@@ -508,6 +508,110 @@ Each office runs its own `cloudflared` tunnel on its own PC. Ports never conflic
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## LAN HTTPS Setup (Trusted Certificate for Local Access)
|
||||||
|
|
||||||
|
Some browser features — notably the Screen Capture API used by the Copy Agent's screenshot
|
||||||
|
tool — only work in a "secure context" (`https://`, or the literal hostname `localhost`). Plain
|
||||||
|
`http://<LAN-ip>:3000` doesn't qualify, so staff would see the feature report itself as
|
||||||
|
unsupported.
|
||||||
|
|
||||||
|
The fix used here: a real, publicly-trusted certificate (Let's Encrypt) for a subdomain whose
|
||||||
|
DNS record points at this office's private LAN IP. Since the cert is issued via a DNS-01
|
||||||
|
challenge (not a live connection to the server), staff PCs need zero configuration — no local CA
|
||||||
|
to install, unlike a self-signed/mkcert certificate.
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- DNS: `local-<office>.mydentalofficemanagement.com` → `A` record → this PC's LAN IP, **unproxied**
|
||||||
|
("DNS only" in Cloudflare — proxying doesn't work for private IPs)
|
||||||
|
- nginx terminates TLS using the Let's Encrypt cert and only accepts connections from the office
|
||||||
|
subnet (`allow <subnet>; deny all;`)
|
||||||
|
- Staff browse to `https://local-<office>.mydentalofficemanagement.com` instead of the raw IP
|
||||||
|
|
||||||
|
This is separate from the Cloudflare Tunnel above — the tunnel is for public access (e.g. Twilio
|
||||||
|
webhooks), this is for trusted HTTPS on the local network only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Step 1 — Create the DNS record (in Cloudflare, not the registrar)
|
||||||
|
|
||||||
|
In the Cloudflare dashboard for `mydentalofficemanagement.com` → DNS → Add record:
|
||||||
|
- Type: `A`, Name: `local-<office>` (e.g. `local-summit`), Content: this PC's LAN IP
|
||||||
|
- **Proxy status: DNS only** (grey cloud) — required, Cloudflare can't proxy a private IP
|
||||||
|
|
||||||
|
### Step 2 — Install nginx (if not already done in Step 12 above)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
> If another web server (e.g. Apache) is already bound to port 80/443, nginx will fail to start.
|
||||||
|
> Check with `sudo ss -tlnp | grep -E ':80|:443'` and disable the conflicting service first.
|
||||||
|
|
||||||
|
### Step 3 — Install certbot with the Cloudflare DNS plugin
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y certbot python3-certbot-dns-cloudflare
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4 — Create a scoped Cloudflare API token
|
||||||
|
|
||||||
|
Cloudflare dashboard → profile icon → **My Profile** → **API Tokens** → **Create Token** → use the
|
||||||
|
**"Edit zone DNS"** template, restricted to **Specific zone → mydentalofficemanagement.com**.
|
||||||
|
|
||||||
|
Save it in a credentials file, root-only:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mkdir -p /etc/letsencrypt
|
||||||
|
sudo nano /etc/letsencrypt/cloudflare.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
dns_cloudflare_api_token = <paste token here>
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
> The same token can be reused across offices (Cloudflare tokens are scoped to the whole zone,
|
||||||
|
> not a single subdomain) — but issuing one token per server makes it easy to revoke just one if
|
||||||
|
> a machine is ever decommissioned.
|
||||||
|
|
||||||
|
### Step 5 — Issue the certificate
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo certbot certonly --dns-cloudflare \
|
||||||
|
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
|
||||||
|
-d local-<office>.mydentalofficemanagement.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Certbot adds a temporary DNS TXT record via the API to prove domain ownership, then removes it.
|
||||||
|
The cert lands at `/etc/letsencrypt/live/local-<office>.mydentalofficemanagement.com/` and
|
||||||
|
auto-renews via a systemd timer — no manual renewal steps.
|
||||||
|
|
||||||
|
### Step 6 — Point nginx at the new cert
|
||||||
|
|
||||||
|
Update the LAN server block in `nginx.conf` (`server_name`, `ssl_certificate`,
|
||||||
|
`ssl_certificate_key`, and the `allow <subnet>;` line) for this office, then reinstall and reload:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo cp nginx.conf /etc/nginx/sites-available/dental-app
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7 — Allow the new hostname in Vite and backend CORS
|
||||||
|
|
||||||
|
- `apps/Frontend/vite.config.ts` → add the hostname to `server.allowedHosts`
|
||||||
|
- `apps/Backend/.env` → add `https://local-<office>.mydentalofficemanagement.com` to
|
||||||
|
`FRONTEND_URLS`
|
||||||
|
|
||||||
|
Staff can now use `https://local-<office>.mydentalofficemanagement.com` with a trusted padlock —
|
||||||
|
no certificate warnings, no CA install on any PC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Twilio In-Browser Calling Setup (Dial Pad)
|
## Twilio In-Browser Calling Setup (Dial Pad)
|
||||||
|
|
||||||
The dial pad on the Patient Connection page lets staff make real phone calls directly through the browser (mic + speaker) using Twilio Voice SDK. One-time setup is required in the Twilio Console.
|
The dial pad on the Patient Connection page lets staff make real phone calls directly through the browser (mic + speaker) using Twilio Voice SDK. One-time setup is required in the Twilio Console.
|
||||||
|
|||||||
Reference in New Issue
Block a user