No description
Find a file
2026-06-10 21:26:37 +03:00
cmd init 2026-06-10 20:51:17 +03:00
deploy init 2026-06-10 20:51:17 +03:00
docs init 2026-06-10 20:51:17 +03:00
example init 2026-06-10 20:51:17 +03:00
internal init 2026-06-10 20:51:17 +03:00
migrations init 2026-06-10 20:51:17 +03:00
web init 2026-06-10 20:51:17 +03:00
.dockerignore init 2026-06-10 20:51:17 +03:00
.gitignore init 2026-06-10 20:51:17 +03:00
DEVELOPMENT_WORKFLOW.md init 2026-06-10 20:51:17 +03:00
docker-compose.yml init 2026-06-10 20:51:17 +03:00
Dockerfile init 2026-06-10 20:51:17 +03:00
go.mod init 2026-06-10 20:51:17 +03:00
go.sum init 2026-06-10 20:51:17 +03:00
IMPLEMENTATION_PLAN.md init 2026-06-10 20:51:17 +03:00
Justfile init 2026-06-10 20:51:17 +03:00
migrate init 2026-06-10 20:51:17 +03:00
PRODUCT_SPEC.md init 2026-06-10 20:51:17 +03:00
README.md add readme 2026-06-10 21:26:37 +03:00
server init 2026-06-10 20:51:17 +03:00

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:

    cp deploy/update-server.env.example .env
    
  2. Edit .env.

    For a local first run:

    APP_BASE_URL=http://127.0.0.1:8080
    ADMIN_EMAIL=admin@example.com
    ADMIN_PASSWORD=change-this-before-production
    

    For production behind HTTPS:

    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:

    docker compose up -d --build
    
  4. Check that it is alive:

    curl http://127.0.0.1:8080/healthz
    
  5. Open the admin UI:

    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:

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:

Internet
  -> router port forward 80/443
  -> Caddy reverse proxy LXC
  -> Docker tools LXC running update-server

Example:

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:

ports:
  - "192.168.1.130:18080:8080"

Set production environment values:

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:

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:

https://updates.esenin.site/admin
https://updates.esenin.site/api/v1

Client API

The API entrypoint is:

GET /api/v1

Protected client endpoints require:

Authorization: Bearer <api_key>

Common endpoints:

GET /api/v1/projects
GET /api/v1/projects/{projectSlug}/releases/latest
GET /api/v1/releases/{releaseID}
GET /api/v1/releases/{releaseID}/download

Example:

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:

/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:

just run
just test
just build

Without just:

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.