# 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/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.
