ci(deploy): make API-domain reconciliation opt-in, document the real host layout
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Deploy Frontend / deploy (push) Has been cancelled

The Reconcile tenant API domains step ran on every push to main. On the
production host that is actively harmful: api.gorbushka.market already has
a hand-written vhost, and configure-api-domain.sh writes its own file per
domain - so the step would hand nginx a second server block for a
server_name that already has one and re-run certbot against a live API,
once per deploy. Shipping frontend files needs none of it. Gate it behind
a workflow_dispatch input, off by default, for standing up a NEW base
domain.

This also shrinks the secrets a normal deploy requires to four
(DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_KNOWN_HOSTS);
STOREFRONT_DOMAINS, CERTBOT_EMAIL and BACKEND_UPSTREAM are now read only
on the opt-in path.

Document the production host as it actually is: provisioned by hand before
server-setup.sh existed, per-domain vhosts rooted at
/var/www/dexarmarket/browser, which is now a symlink to
/srv/marketplaces/current/frontend. Before 2026-08-22 it pointed straight
at a pinned release with no `current` in between, so releases 14d46ce and
98c39f6 uploaded successfully and were never served.
This commit is contained in:
sdarbinyan
2026-08-22 16:08:13 +04:00
parent d4959bd4da
commit 846004e6d8
2 changed files with 63 additions and 3 deletions

View File

@@ -13,6 +13,15 @@ on:
description: Branch or SHA to deploy
required: false
default: main
reconcile_api_domains:
description: >-
Also provision api.<base-domain> nginx vhosts and TLS. Off by default:
existing API domains are configured by hand, and re-running the helper
writes a second server block for a server_name that already has one.
Turn this on only when adding a NEW base domain.
type: boolean
required: false
default: false
concurrency:
group: deploy-frontend
@@ -82,7 +91,13 @@ jobs:
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
# Opt-in only. api.<base-domain> vhosts already exist and are hand-managed;
# the helper writes its own file per domain, so running it unconditionally
# would give nginx two server blocks for one server_name and re-run certbot
# against a live API on every single deploy. Frontend releases do not need
# this step - it is for standing up a NEW base domain.
- name: Reconcile tenant API domains
if: ${{ inputs.reconcile_api_domains }}
env:
HOST: ${{ secrets.DEPLOY_HOST }}
USER: ${{ secrets.DEPLOY_USER }}

View File

@@ -37,6 +37,21 @@ host; tenant subdomains do not create additional API DNS names.
nginx root is `/srv/marketplaces/current/frontend`. Activation is a symlink swap, so no request is ever served from a half-written directory, and a rollback is a symlink change rather than a rebuild.
**On the current production host there is one extra hop.** That server predates
`server-setup.sh` and was provisioned by hand, so instead of the catch-all vhost
it has per-domain configs (`gorbushka.conf`, `dexarmarket.conf`,
`gorbushka-admin.conf`, `gorbushka-landing.conf`) whose `root` is
`/var/www/dexarmarket/browser`. That path is itself a symlink:
```
/var/www/dexarmarket/browser -> /srv/marketplaces/current/frontend
```
so the release/`current` model above still holds and the workflow needs no
per-host special-casing. Until 2026-08-22 `browser` pointed straight at one
pinned release directory with no `current` in between, which is why two
successfully-uploaded releases sat unserved.
---
## 3. First-time setup
@@ -82,18 +97,26 @@ The output is the `DEPLOY_KNOWN_HOSTS` secret. Pinning it means a rebuilt or imp
### 3.4 Add CI secrets
Required for every deploy:
| Secret | Value |
|---|---|
| `DEPLOY_HOST` | server IP or hostname |
| `DEPLOY_USER` | `deploy` |
| `DEPLOY_SSH_KEY` | contents of the **private** key file |
| `DEPLOY_KNOWN_HOSTS` | output of `ssh-keyscan -H <server-ip>` |
Required **only** when running the workflow with `reconcile_api_domains` on
(§4.6) — a normal release deploy never reads these:
| Secret | Value |
|---|---|
| `STOREFRONT_DOMAINS` | space-separated full hosts, e.g. `gorbushka.market store1.example.com` |
| `CERTBOT_EMAIL` | operations email used for Let's Encrypt |
| `BACKEND_UPSTREAM` | optional; defaults to `https://127.0.0.1:445` |
Before deploying, point each base domain's shared API hostname at the server.
For `gorbushka.market` and `store1.gorbushka.market`, only
When that step does run, point each base domain's shared API hostname at the
server first. For `gorbushka.market` and `store1.gorbushka.market`, only
`api.gorbushka.market` is required. The workflow deduplicates
`STOREFRONT_DOMAINS` by base domain and deliberately stops before release
activation if DNS, certificate issuance, nginx validation, or the JSON
@@ -175,7 +198,24 @@ For a single domain, outside the reconciler:
sudo bash add-domain.sh shop.example.com --email ops@example.com --with-www
```
### 4.5 Verify
### 4.5 API domains in CD are opt-in
The deploy workflow's **Reconcile tenant API domains** step is gated behind the
`reconcile_api_domains` input and is **off for push-triggered deploys**.
`configure-api-domain.sh` writes `/etc/nginx/sites-available/api.<domain>` and
enables it. The API vhosts on the current production host were created by hand
under different filenames (`gorbushka-api.conf`), so running the helper there
produces a *second* server block for a `server_name` that already has one, and
re-runs certbot against a live API — on every deploy. Shipping frontend files
needs none of that.
Turn it on from the workflow-dispatch form only when standing up a **new** base
domain. Before the first such run, reconcile the naming: either delete the
hand-made vhost and let the helper own the name, or leave the step off and keep
managing API domains manually.
### 4.6 Verify
```bash
curl -I https://shop.example.com/health
@@ -197,6 +237,11 @@ sudo systemctl reload nginx
Only the last 5 releases are retained. Older ones need a rebuild from the tag.
The production host reaches releases through `/var/www/dexarmarket/browser ->
/srv/marketplaces/current/frontend` (§2), so moving `current` is all a rollback
needs there too — do not repoint `browser` at a release directly, or the next
deploy's swap will silently stop taking effect.
---
## 6. Operational checks