Add a zip archive as a second source for exo-deploy
Until now a release could only come from the bare repo on the VPS, so code that never reached this server's git remote could not be deployed at all. exo-deploy now also takes a zip of the landing source directories: exo-deploy --zip /tmp/exo-20260101.zip medcenterphysio exo-deploy --zip /tmp/exo-20260101.zip all Everything after the source lands in the release directory is unchanged and shared by both modes — npm ci, the build, the %VITE_SITE_URL% check, prune, gzip, the atomic swap, the health check with rollback, the KEEP=3 rotation. The archive therefore carries sources, never a build: VITE_SITE_URL keeps coming from /etc/exo/<app>.build.env rather than from whoever packed the zip. Any .env that travelled inside the archive is dropped before the build. The archive is unpacked once, node_modules/dist/__MACOSX/.DS_Store skipped, and every target app is validated up front — a missing directory or a missing package-lock.json is reported for all four at once, before anything on disk moves. The landing directory is located by its package.json, so both fitnes/... and bundle/fitnes/... (what Finder's Compress produces) work. Also new, and shared by both modes: `all` deploys the four in apps.conf order, stopping at the first failure, and each release records where it came from in .deploy-source, so a live release can still be traced once the deploy output is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc6a6dde8b
commit
5dd6bbc217
+66
-4
@@ -52,7 +52,7 @@ No database, no Redis, no Docker, no PM2. See §14 of the plan for why not Docke
|
||||
|
||||
```bash
|
||||
apt update && apt full-upgrade -y
|
||||
apt install -y git curl nginx ufw fail2ban unattended-upgrades gzip
|
||||
apt install -y git curl nginx ufw fail2ban unattended-upgrades gzip unzip
|
||||
timedatectl set-timezone Europe/Moscow
|
||||
dpkg-reconfigure --priority=low unattended-upgrades
|
||||
|
||||
@@ -188,14 +188,75 @@ Order matters: nginx needs the port-80 vhost live before certbot can validate.
|
||||
|
||||
```bash
|
||||
install -m 755 bin/exo-deploy /usr/local/bin/
|
||||
sudo -u exo exo-deploy medcenterphysio main
|
||||
sudo -u exo exo-deploy medcenterphysio main # one app
|
||||
sudo -u exo exo-deploy all # all four, in apps.conf order
|
||||
```
|
||||
|
||||
## Deploying from a zip archive
|
||||
|
||||
`exo-deploy` takes its code from one of two places: the bare repo (above) or a zip
|
||||
archive sitting on the server. The archive path exists for code that is not in git on
|
||||
this box — a handover build, a contractor's snapshot, a hotfix from a laptop.
|
||||
|
||||
```bash
|
||||
exo-deploy <app|all> [git-ref] # from the bare repo, default ref main
|
||||
exo-deploy --zip <archive.zip> <app|all> # from an archive
|
||||
```
|
||||
|
||||
**The archive holds sources, not a build.** Everything after the source lands in the
|
||||
release directory is identical in both modes — `npm ci`, `npm run build`, the
|
||||
`%VITE_SITE_URL%` check, `npm prune`, gzip, the atomic `current` swap, the health check
|
||||
with automatic rollback. That is the point: `VITE_SITE_URL` comes from
|
||||
`/etc/exo/<app>.build.env` on this server and never from whoever packed the archive.
|
||||
|
||||
Expected layout — the landing directories as they sit in the repo, either at the root of
|
||||
the archive or under one wrapping directory (what Finder's "Compress" produces):
|
||||
|
||||
```
|
||||
fitnes/ hotel/ medcenterphysio/ medcenterstart/ # or bundle/fitnes/ ...
|
||||
package.json, package-lock.json, index.html,
|
||||
src/, server/, shared/, scripts/, public/, tsconfig*.json, vite.config.ts
|
||||
```
|
||||
|
||||
`package-lock.json` is required — `npm ci` refuses to run without it, and `exo-deploy`
|
||||
says so up front for every app in the archive rather than failing halfway through.
|
||||
`node_modules/`, `dist/`, `__MACOSX/` and `.DS_Store` are skipped during unpacking, and
|
||||
any `.env` / `.env.local` that travelled inside the archive is deleted before the build.
|
||||
|
||||
Pack it on the dev machine, from the repo root:
|
||||
|
||||
```bash
|
||||
zip -r exo-$(date +%Y%m%d).zip fitnes hotel medcenterphysio medcenterstart \
|
||||
-x '*/node_modules/*' '*/dist/*' '*/.env' '*/.env.local' '*/.DS_Store' '*/legacy/*'
|
||||
```
|
||||
|
||||
Then ship and deploy. `/tmp` works; the file only has to be readable by `exo`:
|
||||
|
||||
```bash
|
||||
scp exo-20260101.zip <server>:/tmp/
|
||||
sudo -u exo exo-deploy --zip /tmp/exo-20260101.zip all
|
||||
```
|
||||
|
||||
`all` runs the four in `apps.conf` order and **stops at the first failure** — landings
|
||||
already swapped in stay on their new release, the one that failed rolls itself back.
|
||||
Rerun for the rest once the cause is fixed.
|
||||
|
||||
Each release records where it came from, so a live one can be traced long after the
|
||||
deploy output has scrolled away:
|
||||
|
||||
```bash
|
||||
cat /srv/exo/<app>/current/.deploy-source
|
||||
# zip exo-20260101.zip sha256=9f86d0...
|
||||
# deployed 2026-01-01T09:12:44Z
|
||||
```
|
||||
|
||||
## Runbook
|
||||
|
||||
| Task | Command |
|
||||
|---|---|
|
||||
| Deploy | `sudo -u exo exo-deploy <app> [ref]` |
|
||||
| Deploy | `sudo -u exo exo-deploy <app\|all> [ref]` |
|
||||
| Deploy from a zip | `sudo -u exo exo-deploy --zip <archive.zip> <app\|all>` |
|
||||
| What is live | `cat /srv/exo/<app>/current/.deploy-source` |
|
||||
| Rollback | `ln -sfn /srv/exo/<app>/releases/<older> /srv/exo/<app>/current && sudo systemctl restart exo@<app>` |
|
||||
| Tail logs | `journalctl -u exo@<app> -f` |
|
||||
| All leads, last hour | `journalctl -u 'exo@*' --since '1 hour ago' \| grep '\[lead\]'` |
|
||||
@@ -239,7 +300,8 @@ Also enable reg.ru VPS snapshots — a full-image restore beats rebuilding under
|
||||
limiter, single-process by design. Horizontal scaling needs a shared store first.
|
||||
Not a concern at landing-page traffic; nginx `limit_req` is the second layer.
|
||||
- **No CI.** Deployment is a manual `exo-deploy`. A GitHub Actions job that SSHes and
|
||||
runs it is a natural follow-up once the flow is proven.
|
||||
runs it is a natural follow-up once the flow is proven. Until then `--zip` is the way
|
||||
to deploy code that never reached this server's git remote.
|
||||
- **One Metrika counter for four domains.** All four landings carry the same
|
||||
Yandex.Metrika counter (`112352796`) in `index.html`, so reports mix the domains and
|
||||
every domain has to be listed in the counter's settings, or its hits get filtered.
|
||||
|
||||
Reference in New Issue
Block a user