From 846004e6d867a43851b9dace3cc2d3ccd2fe5da1 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sat, 22 Aug 2026 16:08:13 +0400 Subject: [PATCH] ci(deploy): make API-domain reconciliation opt-in, document the real host layout 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. --- .github/workflows/deploy.yml | 15 +++++++++++ docs/DEPLOYMENT.md | 51 +++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index dc34d3c..629175d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -13,6 +13,15 @@ on: description: Branch or SHA to deploy required: false default: main + reconcile_api_domains: + description: >- + Also provision api. 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. 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 }} diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index bdb04b8..8b240c0 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -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 ` | + +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.` 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