update_server/README.md

256 lines
5.9 KiB
Markdown

# Update Server
Self-hosted server for publishing application updates. An administrator uploads
versioned release artifacts through a web UI, and client applications discover
and download allowed updates through a bearer-authenticated HTTP API.
The project is designed for small private deployments: one Go application, one
SQLite database, local artifact storage, and HTTPS termination by a reverse
proxy such as Caddy or Nginx.
## What It Does
- Admin web UI at `/admin`.
- Project and tag management.
- Release artifact upload per project.
- Client API key management with project or tag scope rules.
- Client API at `/api/v1`.
- Private artifact downloads through the application, not through a public file
directory.
- SQLite migrations applied automatically on startup.
- Docker image and Docker Compose deployment files.
## Current Readiness
This is a usable MVP. It is ready to run locally or on a small server with
Docker, create an admin user, create projects, upload releases, issue API keys,
and let clients check and download updates.
You do not need to prepare a separate database or run a separate migration step.
The container stores SQLite and uploaded artifacts under `/data`, and the app
applies migrations on startup.
You do need:
- Docker with Docker Compose;
- `APP_BASE_URL`, `ADMIN_EMAIL`, and `ADMIN_PASSWORD`;
- a persistent `/data` volume;
- for public HTTPS: a reverse proxy in front of the app.
Post-MVP features such as release channels, signed manifests, delta updates,
S3-compatible storage, and multi-user administration are not part of the current
minimal version.
## Quick Start With Docker Compose
1. Copy the example environment file:
```bash
cp deploy/update-server.env.example .env
```
2. Edit `.env`.
For a local first run:
```env
APP_BASE_URL=http://127.0.0.1:8080
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-this-before-production
```
For production behind HTTPS:
```env
APP_BASE_URL=https://updates.example.com
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=use-a-real-password
```
If `APP_BASE_URL` starts with `https://`, open the admin UI through that
HTTPS URL. Secure cookies are enabled automatically for HTTPS base URLs.
3. Build and start the container:
```bash
docker compose up -d --build
```
4. Check that it is alive:
```bash
curl http://127.0.0.1:8080/healthz
```
5. Open the admin UI:
```text
http://127.0.0.1:8080/admin
```
After login, create a project, upload a release, create an API key with download
permission, and use that key from a client.
## Docker Run Example
Docker Compose is the recommended path, but a direct Docker run works too:
```bash
docker build -t update-server:latest .
docker volume create update-server-data
docker run -d \
--name update-server \
--restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-e APP_ADDR=0.0.0.0:8080 \
-e APP_BASE_URL=http://127.0.0.1:8080 \
-e DATA_DIR=/data \
-e SQLITE_PATH=/data/db.sqlite \
-e ARTIFACTS_DIR=/data/artifacts \
-e MIGRATIONS_DIR=/app/migrations \
-e TEMPLATES_DIR=/app/web/templates \
-e STATIC_DIR=/app/web/static \
-e TRUST_PROXY_HEADERS=false \
-e ADMIN_EMAIL=admin@example.com \
-e ADMIN_PASSWORD=change-this-before-production \
-v update-server-data:/data \
update-server:latest
```
## Proxmox / Caddy Example
A simple home-server layout is:
```text
Internet
-> router port forward 80/443
-> Caddy reverse proxy LXC
-> Docker tools LXC running update-server
```
Example:
```text
Caddy LXC: 192.168.1.50
Docker tools LXC: 192.168.1.130
Update Server: 192.168.1.130:18080
Public URL: https://updates.esenin.site
```
In this layout, publish the container port on the Docker tools LXC address:
```yaml
ports:
- "192.168.1.130:18080:8080"
```
Set production environment values:
```env
APP_BASE_URL=https://updates.esenin.site
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=use-a-real-password
```
Then point Caddy at the internal service:
```caddyfile
updates.esenin.site {
encode gzip zstd
request_body {
max_size 1GB
}
reverse_proxy 192.168.1.130:18080
}
```
The admin UI and API can live on the same domain:
```text
https://updates.esenin.site/admin
https://updates.esenin.site/api/v1
```
## Client API
The API entrypoint is:
```text
GET /api/v1
```
Protected client endpoints require:
```http
Authorization: Bearer <api_key>
```
Common endpoints:
```text
GET /api/v1/projects
GET /api/v1/projects/{projectSlug}/releases/latest
GET /api/v1/releases/{releaseID}
GET /api/v1/releases/{releaseID}/download
```
Example:
```bash
curl \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/projects
```
Downloads are intentionally private: uploaded files should not be served by
Caddy or Nginx as static files. Clients download through the application so API
key permissions and project scopes are enforced.
## Data And Backups
Persist the whole `/data` directory or Docker volume. It contains:
```text
/data/db.sqlite
/data/db.sqlite-wal
/data/db.sqlite-shm
/data/artifacts/...
```
For live backups, prefer SQLite's online backup command for the database and
archive `/data/artifacts` separately. See `docs/DEPLOYMENT.md` for a fuller
backup and restore flow.
## Development
Useful local commands:
```bash
just run
just test
just build
```
Without `just`:
```bash
go run ./cmd/server
go test ./...
go build -o .bin/update-server ./cmd/server
```
Local defaults use `data-dev/` and `http://127.0.0.1:8080` unless overridden by
environment variables.
## More Docs
- `docs/API.md` - current client API contract, examples, responses, and errors.
- `docs/DEPLOYMENT.md` - deployment, reverse proxy, backup, and restore notes.
- `deploy/update-server.env.example` - production environment example.
- `deploy/Caddyfile.example` - Caddy reverse proxy example.
- `deploy/nginx.update-server.conf.example` - Nginx reverse proxy example.
- `example/autoupdate.py` - minimal Python auto-update client helper.