Files
xamxam/docs/deployment.md
T

227 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Deployment
Server setup, deployment, backups, and rollback procedures for XAMXAM.
---
## Overview
- Production host: `xamxam` (over SSH), app root `/var/www/xamxam/`
- Files are pushed with `rsync` — **there is no git repo on the remote**
- Web / FPM user: `www-data`, app group: `xamxam`
- The **DocumentRoot** is `app/public/`, but on the server the code lives flat
under `/var/www/xamxam/` (Composer autoload path adjusted to `src/`)
- SQLite database at `/var/www/xamxam/storage/xamxam.db`
Deployment is orchestrated through the `justfile` (`deploy` group).
---
## One-time server setup
```bash
ssh xamxam
sudo mkdir -p /var/www/xamxam
sudo chown www-data:xamxam /var/www/xamxam
sudo chmod 775 /var/www/xamxam
exit
```
Then from local, deploy once and apply the nginx config + verify permissions:
```bash
just deploy
just deploy-nginx
```
For a full initial rollout including backup + cleanup cron jobs:
```bash
just deploy-all-first # deploy + deploy-backup + deploy-cleanup-cron
```
### First-time `APP_KEY` on a brand-new server
`deploy-env` uploads the **local** `app/.env` only when the remote has none. Do
**not** rely on a committed `.env` — generate a fresh key per environment and put
it on the server before first `deploy`: create `app/.env` locally (see
[development.md](development.md#appkey-and-the-appenv-file)) so `deploy-env`
uploads it, or create `/var/www/xamxam/.env` on the server directly:
```bash
ssh xamxam 'sudo -u www-data bash -c "echo APP_KEY=\$(php -r \"echo base64_encode(random_bytes(32));\") > /var/www/xamxam/.env; chmod 640 /var/www/xamxam/.env; chown www-data:xamxam /var/www/xamxam/.env"'
```
If you ever rotate `APP_KEY`, re-encrypt the SMTP password with
`just reencrypt-password <new_base64_key>` and push the new key via
`just deploy-env`.
---
## Deploying
| Command | Purpose |
|---------|---------|
| `just deploy` | Full deploy: build + code + Composer deps + migrations + env + permissions check |
| `just deploy-code` | rsync app files + nginx config + permissions (no Composer, no migrations) |
| `just deploy-deps` | Sync composer.{json,lock} → server, then `composer install`/`dump-autoload` |
| `just deploy-migrate` | Run pending DB migrations on the server |
| `just deploy-env` | Upload `app/.env` (only if the remote `.env` is absent — never overwrites a key) |
| `just deploy-nginx` | Upload + apply + reload nginx config |
| `just deploy-db` | Push local `xamxam.db` → remote (**refuses** if a remote DB already exists) |
| `just deploy-verify-permissions` | Check ownership / permissions on the server |
> ℹ️ **First deploy?** After `just deploy`, run `just deploy-backup` and
> `just deploy-cleanup-cron` to install the backup/cleanup cron jobs, and
> `just deploy-logrotate` to install log rotation. A single `just deploy-all-first`
> chains all of these together.
> The app log directory `/var/log/xamxam/` is provisioned automatically by
> `deploy-code` on every run (via `deploy-server.sh`), so no separate step is
> needed for it. To migrate logs written by an older build, run
> `just migrate-log-names --apply` once.
### Environment file & re-encryption
`app/.env` holds secrets (e.g. `APP_KEY`). `deploy-env` will **not** overwrite a
remote `.env` that already has an `APP_KEY`. If you rotate `APP_KEY`, re-encrypt
the SMTP password and push the new key:
```bash
just reencrypt-password <new_base64_key> # runs scripts/reencrypt-smtp-password.php on server
just deploy-env
```
---
## Backups
SQLite backups are taken with a WAL-safe hot backup (`sqlite3 .backup`) then
gzipped to `/var/backups/xamxam/` on the server.
| Command | Purpose |
|---------|---------|
| `just deploy-backup` | Install backup script + cron jobs (one-shot) |
| `just deploy-backup-script` | Install `/usr/local/bin/backup-sqlite.sh` |
| `just deploy-backup-cron` | Install `/etc/cron.d/xamxam-backup` (hourly 30d + daily 90d) + dirs/log |
| `just deploy-check-backup-log` | Tail `/var/log/xamxam-backup-YYYY-MM-DD.log` |
| `just deploy-list-backups` | List backups on the server |
| `just trigger-backup` | Run the backup script now |
| `just test-restore <path.gz>` | Fetch + decompress + verify a remote snapshot |
Retention: hourly backups kept 30 days, nightly (02:00) backups kept 90 days.
Backup files: `/var/backups/xamxam/db-<timestamp>.db.gz`.
Off-site copies are synced daily to Nextcloud WebDAV — see
[nextcloud-sync.md](nextcloud-sync.md).
Draft cleanup is handled by a separate cron (`/etc/cron.d/xamxam-cleanup`),
installed via `just deploy-cleanup-cron`, logging to
`/var/log/xamxam-cleanup-YYYY-MM-DD.log`. Verify with `just deploy-check-cleanup-log`.
---
## Permissions model
Managed/simulated by the justfile and verified by `deploy-verify-permissions`:
- Ownership: `www-data:xamxam`
- Directories: **2775** (setgid — new files inherit the `xamxam` group)
- Regular files: **664**
- `storage/xamxam.db` and other `*.db`: **660**
- `app/.env`: **640**
The nginx/`deploy-server.sh` step (`just deploy-nginx`, or
`sudo DEPLOY_USER=$USER bash /tmp/deploy-server.sh` via `just deploy-script`)
fixes permissions and installs the nginx config.
---
## Storage migration
If paths moved (e.g. legacy upload locations), a one-off migration script can
rewrite stored paths on the server:
```bash
just deploy-migrate-storage # apply
just deploy-migrate-storage --dry-run # dry-run only
```
---
## Rollback
Because there is no repo on the remote, rollback means restoring files and/or
restoring the database from a backup.
### Restore the database
```bash
ssh xamxam
sudo systemctl stop nginx
cp /var/backups/xamxam/db-<timestamp>.db.gz /tmp/restore.db.gz
gunzip -c /tmp/restore.db.gz > /var/www/xamxam/storage/xamxam.db
chown www-data:xamxam /var/www/xamxam/storage/xamxam.db
chmod 660 /var/www/xamxam/storage/xamxam.db
sudo systemctl start nginx
```
### Restore application files
Re-run `just deploy` from a good local state. In jj you can jump back to a
previous commit and redeploy:
```bash
jj log # find a known-good change
jj edit <previous-change-id> # work from that revision
just deploy
```
---
## Verify after deploy
```bash
curl -I https://xamxam.erg.be/ # expect 200
curl -I https://xamxam.erg.be/admin/ # expect 200
curl -I https://xamxam.erg.be/storage/ # expect 404 (blocked)
just deploy-verify-permissions # expect "All permissions OK"
```
---
## Troubleshooting
- **502 Bad Gateway**
```bash
sudo systemctl status php8.4-fpm
sudo systemctl restart php8.4-fpm
```
- **Nginx config error**
```bash
sudo nginx -t
```
- **`deploy-db` refuses to run**
The remote DB already exists. Remove it manually only if you intend to
overwrite production data.
- **Composer did not pick up new classes**
`deploy-deps` runs `composer dump-autoload` when the lock checksum is
unchanged; if that still fails, force a reinstall.
- **Backup/schedule not running**
Confirm the cron files are installed (`/etc/cron.d/xamxam-backup`,
`/etc/cron.d/xamxam-cleanup`) and the log files are writable by `www-data`.
---
## Selected commands reference
| Command | Purpose |
|---------|---------|
| `just deploy` | Full deploy (build + code + deps + migrate + env + perms) |
| `just deploy-nginx` | Apply nginx config |
| `just deploy-backup` | Install backup script + cron |
| `just deploy-cleanup-cron` | Install orphaned-draft cleanup cron |
| `just deploy-list-backups` | List server backups |
| `just trigger-backup` | Run backup now |
| `just reencrypt-password <key>` | Re-encrypt SMTP password after key rotation |
| `just test-restore <path.gz>` | Verify a snapshot |