Files
RenewCMS/docs/deployment.md
T

165 lines
6.3 KiB
Markdown

# Deployment
RenewCMS ships as **one self-contained executable**. It serves:
| Path | What |
|------------|-----------------------------------------|
| `/v1/*` | JSON API |
| `/healthz` | Liveness check, returns `200 ok` |
| `/*` | The Vue SPA (embedded), with fallback to `index.html` for client-side routes |
Its only runtime state is the SQLite database (`DB_FILE`) and the uploads directory (`UPLOAD_DIR`). Nothing else needs to be installed on the server.
## 1. Build
On any machine with the [dev prerequisites](development.md#prerequisites):
```sh
task build # this OS/arch → bin/renewcms[.exe]
task release # all targets → bin/renewcms_<version>_<os>_<arch>[.exe]
```
`task release` also signs the release: it writes `bin/checksums.txt` (SHA-256 of the six binaries) and signs it into `bin/checksums.txt.sig` with an Ed25519 key. It refuses to start, before building anything, unless `RELEASE_SIGNING_KEY` points to the private key file and that key matches the public key committed as `internal/infrastructure/update/release.pub`. The one-time setup is below.
The frontend is built once and embedded into every binary. The binaries are static (`CGO_ENABLED=0`), so a Linux build runs on any distribution. The version comes from `git describe`: tag a release (`git tag v1.2.0`) before building, and the binary logs its version at startup.
### Release signing key (maintainers, once)
1. Run `task release:keygen -- <path>` with a path outside the repository (for example `~/.renewcms/release.key`). The private key is written there and never leaves your machine; keygen refuses a path inside the work tree.
2. Commit the printed public key as `internal/infrastructure/update/release.pub`. Until you do, the file holds a placeholder (32 zero bytes) and `task release` fails with a message saying so.
3. Export `RELEASE_SIGNING_KEY=<path to the private key>` before every `task release`.
To check a download by hand: `go run ./cmd/releasesign verify checksums.txt`, with `checksums.txt.sig` next to it, then compare the binary's hash with the matching line of `checksums.txt`.
### Update checks
The owner's Settings page asks the release feed for the newest release (see `UPDATE_FEED_URL` in [configuration.md](configuration.md)) and keeps the last result in `update-status.json`, next to `DB_FILE`. Back it up with the rest of the data directory if you like; it is rebuilt by the next check when missing. The Settings page only reports a newer version for now: the site does not update itself.
## 2. Lay out the server
The following is a suggested layout for Linux:
```
/opt/renewcms/renewcms the binary
/etc/renewcms/renewcms.env configuration (chmod 600, owned by root)
/var/lib/renewcms/ data: renewcms.db, uploads/ (owned by the service user)
```
```sh
sudo useradd --system --home /var/lib/renewcms --shell /usr/sbin/nologin renewcms
sudo install -d -o renewcms -g renewcms /var/lib/renewcms
sudo install -D -m 755 bin/renewcms_v1.2.0_linux_amd64 /opt/renewcms/renewcms
sudo install -D -m 600 /dev/null /etc/renewcms/renewcms.env # then edit it
```
`/etc/renewcms/renewcms.env` looks like this (see [configuration.md](configuration.md) for every variable):
```sh
ENVIRONMENT=production
PUBLIC_URL=https://cms.example.com
PORT=8080
DB_FILE=/var/lib/renewcms/renewcms.db
UPLOAD_DIR=/var/lib/renewcms/uploads
JWT_SECRET=<openssl rand -hex 32>
SMTP_LOGIN=noreply@example.com
SMTP_SENDER=noreply@example.com
SMTP_PASSWORD=...
SMTP_HOST=smtp.example.com
SMTP_PORT=465
```
## 3. Run it as a service (systemd)
`/etc/systemd/system/renewcms.service`:
```ini
[Unit]
Description=RenewCMS
After=network-online.target
Wants=network-online.target
[Service]
User=renewcms
Group=renewcms
WorkingDirectory=/var/lib/renewcms
EnvironmentFile=/etc/renewcms/renewcms.env
ExecStart=/opt/renewcms/renewcms
Restart=on-failure
# RenewCMS shuts down gracefully on SIGTERM (up to 10 s to finish requests and close the DB)
TimeoutStopSec=15
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/renewcms
[Install]
WantedBy=multi-user.target
```
```sh
sudo systemctl daemon-reload
sudo systemctl enable --now renewcms
journalctl -u renewcms -f
curl -fsS http://localhost:8080/healthz
```
On Windows, register the binary as a service with a wrapper such as [NSSM](https://nssm.cc/) or [WinSW](https://github.com/winsw/winsw), and set the same environment variables.
## 4. Put HTTPS in front
With `ENVIRONMENT=production` the auth cookie is `Secure`, so **the site must be served over HTTPS**. Terminate TLS in a reverse proxy and keep RenewCMS on localhost.
[Caddy](https://caddyserver.com) handles certificates automatically:
```caddyfile
cms.example.com {
reverse_proxy localhost:8080 {
header_up X-Real-IP {remote_host}
}
}
```
nginx:
```nginx
server {
listen 443 ssl http2;
server_name cms.example.com;
# ssl_certificate ... (e.g. certbot)
client_max_body_size 20m; # image uploads
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
If RenewCMS runs on the same host as the proxy, consider firewalling port 8080.
Either way, set `TRUSTED_PROXIES` (see [configuration.md](configuration.md)) to the proxy's own address as seen by RenewCMS, typically `127.0.0.1` when the proxy and RenewCMS share a host, so the login/password-reset rate limiter honors the forwarded client IP instead of the proxy's own address.
## Backups
Everything worth backing up is under the data directory. Take a consistent SQLite copy while the service is running:
```sh
sqlite3 /var/lib/renewcms/renewcms.db ".backup '/backups/renewcms-$(date +%F).db'"
rsync -a /var/lib/renewcms/uploads/ /backups/uploads/
```
Alternatively, run `systemctl stop renewcms`, copy the directory, and run `systemctl start renewcms`.
## Upgrading
1. Back up (see above). The schema is migrated automatically at startup (GORM `AutoMigrate`), and there is no down-migration.
2. Replace `/opt/renewcms/renewcms` with the new binary.
3. Run `sudo systemctl restart renewcms`, then check `journalctl -u renewcms` for the new version line.
To roll back, put the previous binary back and restore the database backup if the schema changed.