Files
DiunDashboard/.planning/codebase/INTEGRATIONS.md
Jean-Luc Makiola 96c4012e2f chore: add GSD codebase map with 7 analysis documents
Parallel analysis of tech stack, architecture, structure,
conventions, testing patterns, integrations, and concerns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:13:23 +01:00

9.2 KiB

External Integrations

Analysis Date: 2026-03-23

APIs & External Services

DIUN (Docker Image Update Notifier):

  • DIUN sends webhook POST requests when container image updates are detected
  • Endpoint: POST /webhook
  • SDK/Client: None (DIUN pushes to this app; this app is the receiver)
  • Auth: Authorization header must match WEBHOOK_SECRET env var (when set)
  • Source: pkg/diunwebhook/diunwebhook.go lines 163-199

API Contracts

Webhook Ingestion

POST /webhook - Receive a DIUN event

  • Handler: WebhookHandler in pkg/diunwebhook/diunwebhook.go
  • Auth: Authorization header checked via constant-time compare against WEBHOOK_SECRET
  • Request body:
{
  "diun_version": "4.28.0",
  "hostname": "docker-host",
  "status": "new",
  "provider": "docker",
  "image": "registry/org/image:tag",
  "hub_link": "https://hub.docker.com/r/...",
  "mime_type": "application/vnd.docker.distribution.manifest.v2+json",
  "digest": "sha256:abc123...",
  "created": "2026-03-23T10:00:00Z",
  "platform": "linux/amd64",
  "metadata": {
    "ctn_names": "container-name",
    "ctn_id": "abc123",
    "ctn_state": "running",
    "ctn_status": "Up 2 days"
  }
}
  • Response: 200 OK (empty body) on success
  • Errors: 401 Unauthorized, 405 Method Not Allowed, 400 Bad Request (missing image field or invalid JSON), 500 Internal Server Error
  • Behavior: Upserts into updates table keyed by image. Replaces existing entry and resets acknowledged_at to NULL.

Updates API

GET /api/updates - List all tracked image updates

  • Handler: UpdatesHandler in pkg/diunwebhook/diunwebhook.go
  • Response: 200 OK with JSON object keyed by image name:
{
  "registry/org/image:tag": {
    "event": { /* DiunEvent fields */ },
    "received_at": "2026-03-23T10:00:00Z",
    "acknowledged": false,
    "tag": { "id": 1, "name": "production" }  // or null
  }
}

PATCH /api/updates/{image} - Dismiss (acknowledge) an update

  • Handler: DismissHandler in pkg/diunwebhook/diunwebhook.go
  • URL parameter: {image} is the full image name (URL-encoded)
  • Response: 204 No Content on success
  • Errors: 405 Method Not Allowed, 400 Bad Request, 404 Not Found, 500 Internal Server Error
  • Behavior: Sets acknowledged_at = datetime('now') on the matching row

Tags API

GET /api/tags - List all tags

  • Handler: TagsHandler in pkg/diunwebhook/diunwebhook.go
  • Response: 200 OK with JSON array:
[{ "id": 1, "name": "production" }, { "id": 2, "name": "staging" }]

POST /api/tags - Create a new tag

  • Handler: TagsHandler in pkg/diunwebhook/diunwebhook.go
  • Request body: { "name": "production" }
  • Response: 201 Created with { "id": 1, "name": "production" }
  • Errors: 400 Bad Request (empty name), 409 Conflict (duplicate name), 500 Internal Server Error

DELETE /api/tags/{id} - Delete a tag

  • Handler: TagByIDHandler in pkg/diunwebhook/diunwebhook.go
  • URL parameter: {id} is integer tag ID
  • Response: 204 No Content
  • Errors: 405 Method Not Allowed, 400 Bad Request (invalid ID), 404 Not Found, 500 Internal Server Error
  • Behavior: Cascading delete removes all tag_assignments referencing this tag

Tag Assignments API

PUT /api/tag-assignments - Assign an image to a tag

  • Handler: TagAssignmentHandler in pkg/diunwebhook/diunwebhook.go
  • Request body: { "image": "registry/org/image:tag", "tag_id": 1 }
  • Response: 204 No Content
  • Errors: 400 Bad Request, 404 Not Found (tag doesn't exist), 500 Internal Server Error
  • Behavior: INSERT OR REPLACE - reassigns if already assigned

DELETE /api/tag-assignments - Unassign an image from its tag

  • Handler: TagAssignmentHandler in pkg/diunwebhook/diunwebhook.go
  • Request body: { "image": "registry/org/image:tag" }
  • Response: 204 No Content
  • Errors: 400 Bad Request, 500 Internal Server Error

Static File Serving

GET / and all unmatched routes - Serve React SPA

  • Handler: http.FileServer(http.Dir("./frontend/dist")) in cmd/diunwebhook/main.go
  • Serves the production build of the React frontend

Data Storage

Database:

  • SQLite (file-based, single-writer)
  • Connection: DB_PATH env var (default ./diun.db)
  • Driver: modernc.org/sqlite (pure Go, registered as "sqlite" in database/sql)
  • Max open connections: 1 (db.SetMaxOpenConns(1))
  • Write concurrency: sync.Mutex in pkg/diunwebhook/diunwebhook.go

Schema:

-- Table: updates (one row per unique image)
CREATE TABLE IF NOT EXISTS updates (
    image           TEXT PRIMARY KEY,
    diun_version    TEXT NOT NULL DEFAULT '',
    hostname        TEXT NOT NULL DEFAULT '',
    status          TEXT NOT NULL DEFAULT '',
    provider        TEXT NOT NULL DEFAULT '',
    hub_link        TEXT NOT NULL DEFAULT '',
    mime_type       TEXT NOT NULL DEFAULT '',
    digest          TEXT NOT NULL DEFAULT '',
    created         TEXT NOT NULL DEFAULT '',
    platform        TEXT NOT NULL DEFAULT '',
    ctn_name        TEXT NOT NULL DEFAULT '',
    ctn_id          TEXT NOT NULL DEFAULT '',
    ctn_state       TEXT NOT NULL DEFAULT '',
    ctn_status      TEXT NOT NULL DEFAULT '',
    received_at     TEXT NOT NULL,
    acknowledged_at TEXT
);

-- Table: tags (user-defined grouping labels)
CREATE TABLE IF NOT EXISTS tags (
    id   INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE
);

-- Table: tag_assignments (image-to-tag mapping, one tag per image)
CREATE TABLE IF NOT EXISTS tag_assignments (
    image  TEXT PRIMARY KEY,
    tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE
);

Migrations:

  • Schema is created on startup via InitDB() in pkg/diunwebhook/diunwebhook.go
  • Uses CREATE TABLE IF NOT EXISTS for all tables
  • One manual migration: ALTER TABLE updates ADD COLUMN acknowledged_at TEXT (silently ignored if already present)
  • No formal migration framework; migrations are inline Go code

File Storage: Local filesystem only (SQLite database file)

Caching: None

Authentication & Identity

Webhook Authentication:

  • Token-based via WEBHOOK_SECRET env var
  • Checked in WebhookHandler using crypto/subtle.ConstantTimeCompare against the Authorization header
  • When WEBHOOK_SECRET is empty, the webhook endpoint is unprotected (warning logged at startup)
  • Implementation: pkg/diunwebhook/diunwebhook.go lines 54-56, 163-170

User Authentication: None. The dashboard and all API endpoints (except webhook) are open/unauthenticated.

Monitoring & Observability

Error Tracking: None (no Sentry, Datadog, etc.)

Logs:

  • Go stdlib log package writing to stdout
  • Key log points: startup warnings, webhook receipt, errors in handlers
  • No structured logging framework

CI/CD & Deployment

Hosting: Self-hosted via Docker on a Gitea instance at gitea.jeanlucmakiola.de

Container Registry: gitea.jeanlucmakiola.de/makiolaj/diundashboard

CI Pipeline (Gitea Actions):

  • Config: .gitea/workflows/ci.yml
  • Triggers: Push to develop, PRs targeting develop
  • Steps: gofmt check, go vet, tests with coverage (warn below 80%), go build
  • Runner: Custom Docker image with Go + Node/Bun toolchains

Release Pipeline (Gitea Actions):

  • Config: .gitea/workflows/release.yml
  • Trigger: Manual workflow_dispatch with semver bump choice (patch/minor/major)
  • Steps: Run full CI checks, compute new version tag, create git tag, build and push Docker image (versioned + latest), create Gitea release with changelog
  • Secrets required: GITEA_TOKEN, REGISTRY_TOKEN

Docker Build:

  • Multi-stage Dockerfile at project root (Dockerfile)
  • Stage 1: oven/bun:1-alpine - Build frontend (bun install --frozen-lockfile && bun run build)
  • Stage 2: golang:1.26-alpine - Build Go binary (CGO_ENABLED=0 go build)
  • Stage 3: alpine:3.18 - Runtime with binary + static assets, exposes port 8080

Docker Compose:

  • compose.yml - Production deploy (pulls latest from registry, mounts diun-data volume at /data)
  • compose.dev.yml - Local development (builds from Dockerfile)

Documentation Site:

  • Separate docs/Dockerfile and docs/nginx.conf for static site deployment via Nginx
  • Built with VitePress, served as static HTML

Environment Configuration

Required env vars (production):

  • None strictly required (all have defaults)

Recommended env vars:

  • WEBHOOK_SECRET - Protect webhook endpoint from unauthorized access
  • DB_PATH - Set to /data/diun.db in Docker for persistent volume mount
  • PORT - Override default port 8080

Secrets:

  • WEBHOOK_SECRET - Shared secret between DIUN and this app
  • GITEA_TOKEN - CI/CD pipeline (Gitea API access)
  • REGISTRY_TOKEN - CI/CD pipeline (Docker registry push)

Webhooks & Callbacks

Incoming:

  • POST /webhook - Receives DIUN image update notifications

Outgoing:

  • None

Frontend-Backend Communication

Dev Mode:

  • Vite dev server on :5173 proxies /api and /webhook to http://localhost:8080 (frontend/vite.config.ts)

Production:

  • Go server serves frontend/dist/ at / via http.FileServer
  • API and webhook routes are on the same origin (no CORS needed)

Polling:

  • React SPA polls GET /api/updates every 5 seconds (no WebSocket/SSE)

Integration audit: 2026-03-23