init
This commit is contained in:
commit
b15b95781c
108 changed files with 14802 additions and 0 deletions
160
docs/DEPLOYMENT.md
Normal file
160
docs/DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# Deployment Notes
|
||||
|
||||
## Runtime shape
|
||||
|
||||
Recommended production layout:
|
||||
|
||||
- run the Go app as a single container;
|
||||
- bind the app container only to `127.0.0.1:8080`;
|
||||
- terminate HTTPS in front of it with Caddy or Nginx;
|
||||
- persist `/data` so both SQLite and artifacts survive restarts;
|
||||
- expose only the reverse proxy to the internet.
|
||||
|
||||
This repository now includes:
|
||||
|
||||
- `Dockerfile`
|
||||
- `docker-compose.yml`
|
||||
- `deploy/Caddyfile.example`
|
||||
- `deploy/nginx.update-server.conf.example`
|
||||
- `deploy/update-server.env.example`
|
||||
|
||||
Important deployment assumptions:
|
||||
|
||||
- set `APP_BASE_URL` to the public `https://...` URL;
|
||||
- leave `TRUST_PROXY_HEADERS=true` only when the app is actually behind a trusted reverse proxy;
|
||||
- do not mount `/data` into a public web root;
|
||||
- do not serve artifacts directly from Caddy or Nginx. Downloads must continue flowing through `/api/v1/releases/{id}/download`.
|
||||
|
||||
## Data layout
|
||||
|
||||
Persist the whole `/data` mount.
|
||||
|
||||
Expected contents:
|
||||
|
||||
- `/data/db.sqlite`
|
||||
- `/data/db.sqlite-wal`
|
||||
- `/data/db.sqlite-shm`
|
||||
- `/data/artifacts/...`
|
||||
|
||||
The app uses SQLite in WAL mode, so cold file copies must include `db.sqlite-wal` and `db.sqlite-shm` unless you use an online SQLite backup.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
1. Copy `deploy/update-server.env.example` to a private env file or export the variables in your shell.
|
||||
2. Set at least:
|
||||
- `APP_BASE_URL`
|
||||
- `ADMIN_EMAIL`
|
||||
- `ADMIN_PASSWORD`
|
||||
3. Start the app:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Security defaults in the provided compose file:
|
||||
|
||||
- non-root container user;
|
||||
- read-only root filesystem;
|
||||
- writable `/data` volume only;
|
||||
- writable `tmpfs` at `/tmp` for multipart form handling;
|
||||
- `no-new-privileges`;
|
||||
- all Linux capabilities dropped;
|
||||
- app port bound only to loopback.
|
||||
|
||||
The current implementation uses database-backed random session tokens, so there is no separate `SESSION_SECRET` environment variable to set.
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
Use one of the example proxy configs and keep the app on loopback.
|
||||
|
||||
Caddy:
|
||||
|
||||
- example file: `deploy/Caddyfile.example`
|
||||
- automatic HTTPS is the simplest option for first rollout.
|
||||
|
||||
Nginx:
|
||||
|
||||
- example file: `deploy/nginx.update-server.conf.example`
|
||||
- remember to provision certificates separately.
|
||||
|
||||
Recommended proxy behavior:
|
||||
|
||||
- pass `Host`, `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto`;
|
||||
- keep `client_max_body_size` or equivalent aligned with `MAX_UPLOAD_BYTES`;
|
||||
- optionally IP-allow-list `/admin` if the admin UI is only for operators;
|
||||
- do not add any direct `/artifacts` static mapping.
|
||||
|
||||
## Proxmox notes
|
||||
|
||||
Safe first production rollout on Proxmox:
|
||||
|
||||
1. Use a VM or an unprivileged LXC dedicated to this service.
|
||||
2. Put persistent app data on a host path such as `/srv/update-server/data`.
|
||||
3. Put backups on a different filesystem or datastore such as `/srv/update-server/backups`.
|
||||
4. Run the app container on the guest and keep it bound to `127.0.0.1:8080`.
|
||||
5. Run Caddy or Nginx on the same guest or on a separate reverse-proxy guest.
|
||||
6. Expose only ports `80` and `443` publicly.
|
||||
7. Keep `APP_BASE_URL` on the final public HTTPS hostname before testing cookies or HSTS.
|
||||
|
||||
Operational advice for Proxmox:
|
||||
|
||||
- snapshots are useful, but they are not a replacement for app-aware backups;
|
||||
- if you use LXC, make sure the mounted `/data` path is writable by the container user;
|
||||
- keep the reverse proxy and app logs outside the repo checkout;
|
||||
- test one admin login, one upload, and one authenticated download after each upgrade.
|
||||
|
||||
## Backup
|
||||
|
||||
Preferred live-backup flow:
|
||||
|
||||
1. Use SQLite's online backup command so you get a consistent `db.sqlite` snapshot without stopping the app.
|
||||
2. Back up `/data/artifacts` separately.
|
||||
|
||||
Example with Docker:
|
||||
|
||||
```bash
|
||||
backup_dir=/srv/update-server/backups/$(date +%Y%m%d-%H%M%S)
|
||||
mkdir -p "${backup_dir}"
|
||||
|
||||
docker exec update-server sqlite3 /data/db.sqlite ".backup '/tmp/db.sqlite'"
|
||||
docker cp update-server:/tmp/db.sqlite "${backup_dir}/db.sqlite"
|
||||
docker exec update-server rm -f /tmp/db.sqlite
|
||||
tar -C /srv/update-server/data -czf "${backup_dir}/artifacts.tar.gz" artifacts
|
||||
```
|
||||
|
||||
If you take a cold backup instead:
|
||||
|
||||
1. stop the container;
|
||||
2. copy `/data/db.sqlite`, `/data/db.sqlite-wal`, `/data/db.sqlite-shm`, and `/data/artifacts`;
|
||||
3. start the container again.
|
||||
|
||||
## Restore
|
||||
|
||||
Restore procedure:
|
||||
|
||||
1. stop the app container;
|
||||
2. restore `db.sqlite`;
|
||||
3. if the restored database came from SQLite `.backup`, remove any stale `db.sqlite-wal` and `db.sqlite-shm` files before starting;
|
||||
4. restore `/data/artifacts`;
|
||||
5. start the container;
|
||||
6. verify `GET /healthz`, admin login, and at least one authenticated client download.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
docker compose stop app
|
||||
cp /srv/update-server/backups/20260415-120000/db.sqlite /srv/update-server/data/db.sqlite
|
||||
rm -f /srv/update-server/data/db.sqlite-wal /srv/update-server/data/db.sqlite-shm
|
||||
tar -C /srv/update-server/data -xzf /srv/update-server/backups/20260415-120000/artifacts.tar.gz
|
||||
docker compose up -d app
|
||||
```
|
||||
|
||||
## Post-deploy checklist
|
||||
|
||||
- `GET /healthz` returns `200`;
|
||||
- admin login works through HTTPS;
|
||||
- `Set-Cookie` on `/admin/login` includes `Secure`, `HttpOnly`, and the `/admin` path;
|
||||
- one release upload succeeds;
|
||||
- one bearer-authenticated `/api/v1/projects` request succeeds;
|
||||
- one `/api/v1/releases/{id}/download` request succeeds through the proxy;
|
||||
- backups complete and can be restored on a staging copy.
|
||||
Loading…
Add table
Add a link
Reference in a new issue