Merge branch 'B2B'
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Deploy Frontend / deploy (push) Has been cancelled

This commit is contained in:
sdarbinyan
2026-08-18 12:22:04 +04:00
7 changed files with 515 additions and 9 deletions

View File

@@ -83,27 +83,84 @@ Push to `main`, or run the workflow manually with a ref. The workflow refuses to
---
## 4. TLS
## 4. Domains and TLS — dynamic by default
No certificate is issued during provisioning, because certbot cannot validate a domain that does not yet point at the server.
Domains arrive continuously: one today, five tomorrow. Nothing here requires a person per domain.
Per domain, once its A record resolves here:
**HTTP already needs zero configuration.** The nginx catch-all serves *any* `Host`, and the SPA resolves its tenant from that header. Point a domain's A record at the server and it works over port 80 immediately. Only TLS needs a certificate per name — that is the whole problem this section solves.
Two mechanisms, used together:
### 4.1 Wildcard — tenants on our own apex
One certificate covers every `<slug>.<apex>`. A new tenant subdomain is then live over HTTPS the moment DNS resolves, with **no certificate work at all**.
```bash
sudo bash setup-wildcard-tls.sh \
--apex marketplaces.example.com \
--email ops@example.com \
--dns cloudflare --creds /root/cloudflare.ini
```
Wildcards require DNS-01 validation, so certbot must write a `_acme-challenge` TXT record. With a provider plugin (`cloudflare`, `route53`) renewal is unattended. `--dns manual` works but prompts for a TXT record at **every** renewal — fine to prove the setup out, not acceptable as a steady state.
**Hostinger has no certbot plugin.** If DNS lives there: either move DNS to a provider that has one (Cloudflare is free, minutes of work), or drive issuance from the [Phase 9](backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md) domain-automation API once it exists.
### 4.2 Reconciler — tenants on their own domains
A wildcard cannot cover a customer's own domain. `sync-domains.sh` runs on a 10-minute timer and reconciles the live set against a desired list:
- issues certificates for domains that lack one
- skips domains whose certificate has more than 30 days left
- skips subdomains already covered by `WILDCARD_APEX`
- leaves domains alone while their DNS has not propagated yet, and retries next tick
- disables server blocks for domains removed from the source — **without deleting the certificate**, so re-adding one later is instant
- caps issuance per run, so a misconfigured source cannot burn the weekly ACME budget in a single pass
Configure `/etc/marketplaces/domains.env`:
```bash
DOMAINS_SOURCE=file:/etc/marketplaces/domains.txt
CERTBOT_EMAIL=ops@example.com
MAX_ISSUE_PER_RUN=10
```
Then:
```bash
sudo systemctl enable --now marketplaces-domains.timer
sudo /srv/marketplaces/bin/sync-domains.sh --dry-run # see the plan, change nothing
```
Adding a domain becomes: append a line to `/etc/marketplaces/domains.txt` (or add the row in the backend registry), point DNS, wait one tick.
### 4.3 Backend-driven, once Phase 9 ships
Point the reconciler at the registry instead of a file and the loop closes — `MarketplaceDomain` already carries exactly the statuses this needs (`planned → dns_pending → ssl_pending → active → failed`):
```bash
DOMAINS_SOURCE=https://api.example.com/api/admin/v2/domains
DOMAINS_API_TOKEN=...
```
The script accepts a bare JSON array of hostnames, or objects with `domain` + `status`, in which case it acts only on `active` rows. **A fetch failure aborts the run rather than reading as "remove every domain."**
### 4.4 One-off
For a single domain, outside the reconciler:
```bash
sudo bash add-domain.sh shop.example.com --email ops@example.com --with-www
```
This writes a server block for that domain (same root — it exists only to give certbot a concrete `server_name`), issues the certificate, enables the HTTP→HTTPS redirect, and enables `certbot.timer` for renewal.
Verify:
### 4.5 Verify
```bash
curl -I https://shop.example.com/health
sudo certbot certificates
journalctl -u marketplaces-domains.service --since "1 hour ago"
```
**On wildcards:** `add-domain.sh` uses HTTP-01, which cannot issue wildcards. If tenants all live under one apex (`*.marketplaces.example.com`), a DNS-01 wildcard is fewer moving parts — but it requires API credentials for the DNS provider and a different certbot plugin. Not set up here; raise it when the tenant count makes per-domain issuance annoying.
---
## 5. Rollback