deploy: follow the landing rename and the exorecovery.ru domains

Bring deploy/ in line with the renamed app directories and with the
domains that are actually configured in the per-app .env files:

  fitnes           fitness.exorecovery.ru  3000  fitness-landing
  hotel            hotel.exorecovery.ru    3001  hotel-landing
  medcenterphysio  physio.exorecovery.ru   3002  physio-landing
  medcenterstart   start.exorecovery.ru    3003  start-landing

apps.conf, the README provisioning steps (directories, bare clones,
systemd units, the sudoers whitelist) and the exo-deploy usage line all
used the old medcenter / medcenterpersonal keys. The README table and
app.build.env.example still carried the *_DOMAIN placeholders.

Also refresh the tracked .env.example files: real VITE_SITE_URL values
instead of the old exodevices.ru sub-paths, and the amo lead tags that
match apps.conf.

Two lockfile fixes, both of which broke `npm ci` in exo-deploy:
- medcenterstart had no lockfile at all after package-lock.json and
  pnpm-lock.yaml were removed — restore package-lock.json and keep the
  pnpm one deleted, since the deploy path is npm;
- medcenterphysio/package-lock.json had lost "resolved" and "integrity"
  on 147 of its 236 entries — restore the complete file.

Verified: exo-render-nginx renders all four vhosts with no leftover
placeholders, and every package-lock.json agrees with its package.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Yuriy Panov
2026-09-06 23:36:17 +06:00
co-authored by Claude Opus 5
parent 0732aa2096
commit 51f39c9b88
10 changed files with 36 additions and 2214 deletions
+11 -11
View File
@@ -5,14 +5,14 @@ nginx serves the built SPA off disk; a small Express process per app handles onl
`/api/*` and forwards leads to amoCRM. Releases are built on the server and swapped `/api/*` and forwards leads to amoCRM. Releases are built on the server and swapped
in atomically via a `current` symlink. in atomically via a `current` symlink.
**Fill in `apps.conf` first** — it is the single source of truth for domains and ports. `apps.conf` is the single source of truth for domains and ports — check it before provisioning.
| App | Domain | Port | amoCRM tag | | App | Domain | Port | amoCRM tag |
|---|---|---|---| |---|---|---|---|
| `fitnes` | `FITNES_DOMAIN` | 3000 | `fitness-landing` | | `fitnes` | `fitness.exorecovery.ru` | 3000 | `fitness-landing` |
| `hotel` | `HOTEL_DOMAIN` | 3001 | `hotel-landing` | | `hotel` | `hotel.exorecovery.ru` | 3001 | `hotel-landing` |
| `medcenter` | `MEDCENTER_DOMAIN` | 3002 | `medcenter-landing` | | `medcenterphysio` | `physio.exorecovery.ru` | 3002 | `physio-landing` |
| `medcenterpersonal` | `MCPERSONAL_DOMAIN` | 3003 | `medcenter-person-landing` | | `medcenterstart` | `start.exorecovery.ru` | 3003 | `start-landing` |
## VPS requirements ## VPS requirements
@@ -73,13 +73,13 @@ apt install -y nodejs && node -v # v24.x
```bash ```bash
adduser --system --group --home /srv/exo --shell /bin/bash exo adduser --system --group --home /srv/exo --shell /bin/bash exo
mkdir -p /srv/exo/{fitnes,hotel,medcenter,medcenterpersonal}/releases mkdir -p /srv/exo/{fitnes,hotel,medcenterphysio,medcenterstart}/releases
mkdir -p /etc/exo /var/www/certbot mkdir -p /etc/exo /var/www/certbot
chown -R exo:exo /srv/exo chown -R exo:exo /srv/exo
chmod 755 /srv/exo # www-data must traverse to reach dist/client chmod 755 /srv/exo # www-data must traverse to reach dist/client
chmod 750 /etc/exo chmod 750 /etc/exo
for app in fitnes hotel medcenter medcenterpersonal; do for app in fitnes hotel medcenterphysio medcenterstart; do
sudo -u exo git clone --bare <REPO_URL> /srv/exo/$app/repo sudo -u exo git clone --bare <REPO_URL> /srv/exo/$app/repo
done done
``` ```
@@ -109,7 +109,7 @@ systemd's `EnvironmentFile` (secrets) coexist and the secrets win.
```bash ```bash
install -m 644 systemd/exo@.service /etc/systemd/system/ install -m 644 systemd/exo@.service /etc/systemd/system/
systemctl daemon-reload systemctl daemon-reload
systemctl enable exo@fitnes exo@hotel exo@medcenter exo@medcenterpersonal systemctl enable exo@fitnes exo@hotel exo@medcenterphysio exo@medcenterstart
``` ```
Let the deploy user restart only its own units — `visudo -f /etc/sudoers.d/exo-deploy`: Let the deploy user restart only its own units — `visudo -f /etc/sudoers.d/exo-deploy`:
@@ -117,8 +117,8 @@ Let the deploy user restart only its own units — `visudo -f /etc/sudoers.d/exo
``` ```
exo ALL=(root) NOPASSWD: /usr/bin/systemctl restart exo@fitnes, \ exo ALL=(root) NOPASSWD: /usr/bin/systemctl restart exo@fitnes, \
/usr/bin/systemctl restart exo@hotel, \ /usr/bin/systemctl restart exo@hotel, \
/usr/bin/systemctl restart exo@medcenter, \ /usr/bin/systemctl restart exo@medcenterphysio, \
/usr/bin/systemctl restart exo@medcenterpersonal /usr/bin/systemctl restart exo@medcenterstart
``` ```
### 6. Firewall and log retention ### 6. Firewall and log retention
@@ -180,7 +180,7 @@ Order matters: nginx needs the port-80 vhost live before certbot can validate.
```bash ```bash
install -m 755 bin/exo-deploy /usr/local/bin/ install -m 755 bin/exo-deploy /usr/local/bin/
sudo -u exo exo-deploy medcenterpersonal main sudo -u exo exo-deploy medcenterphysio main
``` ```
## Runbook ## Runbook
+4 -4
View File
@@ -2,7 +2,7 @@
# Consumed by bin/exo-render-nginx and referenced by README.md. # Consumed by bin/exo-render-nginx and referenced by README.md.
# #
# app|domain|port|amo_lead_tag # app|domain|port|amo_lead_tag
fitnes|fitness.exo.panov.it|3000|fitness-landing fitnes|fitness.exorecovery.ru|3000|fitness-landing
hotel|hotel.exo.panov.it|3001|hotel-landing hotel|hotel.exorecovery.ru|3001|hotel-landing
medcenter|medcenter.exo.panov.it|3002|medcenter-landing medcenterphysio|physio.exorecovery.ru|3002|physio-landing
medcenterpersonal|medcenterperson.exo.panov.it|3003|medcenter-person-landing medcenterstart|start.exorecovery.ru|3003|start-landing
+3 -3
View File
@@ -2,11 +2,11 @@
# Build one landing from git into a fresh release directory and swap it in. # Build one landing from git into a fresh release directory and swap it in.
# Install as /usr/local/bin/exo-deploy (mode 755); run as the `exo` user. # Install as /usr/local/bin/exo-deploy (mode 755); run as the `exo` user.
# #
# sudo -u exo exo-deploy medcenter # deploys origin/main # sudo -u exo exo-deploy medcenterphysio # deploys origin/main
# sudo -u exo exo-deploy medcenter my-branch # sudo -u exo exo-deploy medcenterphysio my-branch
set -Eeuo pipefail set -Eeuo pipefail
APP="${1:?usage: exo-deploy <fitnes|hotel|medcenter|medcenterpersonal> [git-ref]}" APP="${1:?usage: exo-deploy <fitnes|hotel|medcenterphysio|medcenterstart> [git-ref]}"
REF="${2:-main}" REF="${2:-main}"
BASE="/srv/exo/$APP" BASE="/srv/exo/$APP"
ENVFILE="/etc/exo/$APP.env" ENVFILE="/etc/exo/$APP.env"
+1 -1
View File
@@ -5,7 +5,7 @@
# Substituted into %VITE_SITE_URL% in index.html: canonical, og:url, og:image # Substituted into %VITE_SITE_URL% in index.html: canonical, og:url, og:image
# and the JSON-LD url. If it is missing, the literal placeholder ships and # and the JSON-LD url. If it is missing, the literal placeholder ships and
# exo-deploy aborts. # exo-deploy aborts.
VITE_SITE_URL=https://FITNES_DOMAIN VITE_SITE_URL=https://fitness.exorecovery.ru
# Each app sits at its own domain root, so this stays "/". # Each app sits at its own domain root, so this stays "/".
VITE_BASE_PATH=/ VITE_BASE_PATH=/
+1 -1
View File
@@ -4,7 +4,7 @@
NODE_ENV=production NODE_ENV=production
HOST=127.0.0.1 HOST=127.0.0.1
# Per deploy/apps.conf: fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003 # Per deploy/apps.conf: fitnes 3000, hotel 3001, medcenterphysio 3002, medcenterstart 3003
PORT=3000 PORT=3000
# amoCRM account subdomain. "exotherapy", "exotherapy.amocrm.ru" and a full URL # amoCRM account subdomain. "exotherapy", "exotherapy.amocrm.ru" and a full URL
+3 -3
View File
@@ -1,17 +1,17 @@
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Public (baked into the client bundle at build time — never put secrets here) # Public (baked into the client bundle at build time — never put secrets here)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
VITE_SITE_URL=https://exodevices.ru/fitness VITE_SITE_URL=https://fitness.exorecovery.ru
# Sub-path the site is served from, if any. Leave as "/" when it sits at the # Sub-path the site is served from, if any. Leave as "/" when it sits at the
# domain root; set to "/fitness/" if it is mounted under exodevices.ru/fitness. # domain root; set to "/fitness/" if it is ever mounted under a sub-path.
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Server # Server
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# One port per landing so all four can run side by side: # One port per landing so all four can run side by side:
# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. # fitnes 3000, hotel 3001, medcenterphysio 3002, medcenterstart 3003.
PORT=3000 PORT=3000
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+3 -3
View File
@@ -1,17 +1,17 @@
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Public (baked into the client bundle at build time — never put secrets here) # Public (baked into the client bundle at build time — never put secrets here)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
VITE_SITE_URL=https://exodevices.ru/hotels VITE_SITE_URL=https://hotel.exorecovery.ru
# Sub-path the site is served from, if any. Leave as "/" when it sits at the # Sub-path the site is served from, if any. Leave as "/" when it sits at the
# domain root; set to "/hotels/" if it is mounted under exodevices.ru/hotels. # domain root; set to "/hotels/" if it is ever mounted under a sub-path.
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Server # Server
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# One port per landing so all four can run side by side: # One port per landing so all four can run side by side:
# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. # fitnes 3000, hotel 3001, medcenterphysio 3002, medcenterstart 3003.
PORT=3001 PORT=3001
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+5 -5
View File
@@ -1,18 +1,18 @@
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Public (baked into the client bundle at build time — never put secrets here) # Public (baked into the client bundle at build time — never put secrets here)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
VITE_SITE_URL=https://exodevices.ru/medical-centers/with-physiotherapy VITE_SITE_URL=https://physio.exorecovery.ru
# Sub-path the site is served from, if any. Leave as "/" when it sits at the # Sub-path the site is served from, if any. Leave as "/" when it sits at the
# domain root; set to "/medical-centers/with-physiotherapy/" when it is mounted # domain root; set to "/medical-centers/with-physiotherapy/" if it is ever
# under that path. # mounted under that path.
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Server # Server
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# One port per landing so all four can run side by side: # One port per landing so all four can run side by side:
# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. # fitnes 3000, hotel 3001, medcenterphysio 3002, medcenterstart 3003.
PORT=3002 PORT=3002
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -33,4 +33,4 @@ AMO_PIPELINE_ID=10980758
AMO_RESPONSIBLE_USER_ID= AMO_RESPONSIBLE_USER_ID=
# Optional: comma-separated tags added to every lead. # Optional: comma-separated tags added to every lead.
AMO_LEAD_TAGS=medcenter-landing AMO_LEAD_TAGS=physio-landing
+5 -5
View File
@@ -1,18 +1,18 @@
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Public (baked into the client bundle at build time — never put secrets here) # Public (baked into the client bundle at build time — never put secrets here)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
VITE_SITE_URL=https://exodevices.ru/medical-centers/no-physiotherapy VITE_SITE_URL=https://start.exorecovery.ru
# Sub-path the site is served from, if any. Leave as "/" when it sits at the # Sub-path the site is served from, if any. Leave as "/" when it sits at the
# domain root; set to "/medical-centers/no-physiotherapy/" when it is mounted # domain root; set to "/medical-centers/no-physiotherapy/" if it is ever
# under that path. # mounted under that path.
VITE_BASE_PATH=/ VITE_BASE_PATH=/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Server # Server
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# One port per landing so all four can run side by side: # One port per landing so all four can run side by side:
# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. # fitnes 3000, hotel 3001, medcenterphysio 3002, medcenterstart 3003.
PORT=3003 PORT=3003
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -33,4 +33,4 @@ AMO_PIPELINE_ID=10980758
AMO_RESPONSIBLE_USER_ID= AMO_RESPONSIBLE_USER_ID=
# Optional: comma-separated tags added to every lead. # Optional: comma-separated tags added to every lead.
AMO_LEAD_TAGS=medcenter-no-physio-landing AMO_LEAD_TAGS=start-landing
-2178
View File
File diff suppressed because it is too large Load Diff