Files
EvolioHealth/docs/architecture.md
T

5.8 KiB

Architecture

System context

flowchart LR
  HC["Android Health Connect"] --> C["EvolioHealth Companion"]
  S["Xiaomi scale via BLE"] --> C
  C -->|HTTPS business API| RP["External reverse proxy"]
  B["Browser"] -->|HTTPS| RP
  RP --> G["EvolioHealth Server - Go"]
  G -->|Private Docker network| PB["PocketBase"]
  G --> R["references.db - read mostly"]
  G --> M["Encrypted private media"]
  G --> W["Bundled Flutter Web assets"]

The reverse proxy targets only the Go server. PocketBase has no host port, no public route, and no frontend credentials. The Go service performs authentication, authorization, input validation, encryption, sync semantics, media processing, auditing, and all business logic.

Components

EvolioHealth Server

A Go service in a minimal Alpine runtime image. It:

  • serves /, the Flutter Web bundle, and /api/v1;
  • owns the public authentication protocol and business API;
  • communicates with PocketBase through its private API, never by opening data.db;
  • directly opens references.db with prepared queries and read-only mode during normal operation;
  • encrypts/decrypts sensitive fields and media;
  • validates and re-encodes images as needed;
  • performs backup, restore, reference updates, and voluntary master-key replacement;
  • exposes non-sensitive liveness and readiness checks.

Use idiomatic standard-library Go where practical. Framework and dependency choices must be justified by maintenance and security value.

PocketBase

PocketBase stores instance-owned mutable data: accounts, profiles, devices, sessions, sync metadata, measurements, workouts, encrypted health payloads, settings, and audit records. Its administrative UI and generic collection APIs are internal-only and not part of the supported operator workflow.

Defense in depth requires restrictive PocketBase collection rules and a dedicated service credential even though Docker networking isolates it. The service credential is provisioned without embedding it in client applications.

Reference database

/data/references/references.db contains automatically replaceable, distributable data such as exercise definitions, muscle groups, equipment, foods, nutrients, templates, and dataset metadata. It contains no user data.

Updates use a signed manifest, HTTPS download, signature and digest verification, minimum-server-version check, SQLite integrity and schema validation, and atomic replacement with rollback to the last valid file. Arbitrary downloaded SQL MUST NOT be executed.

Web and Companion

Flutter Web is bundled into and served by the Go image. It includes both user functions and the simplified administration experience. The Android Companion uses Flutter plus narrowly scoped Kotlin bridges where official Android APIs, Health Connect, WorkManager, Keystore, or BLE require them.

Clients depend only on versioned business contracts, never PocketBase record formats.

Storage layout

Internal paths are fixed:

/config/
  master.key
  instance.json
/data/
  pocketbase/
  references/references.db
  media/avatars/
  media/progress-photos/
  backups/
  temporary/

Operators choose Docker named volumes or bind mounts only by editing the volumes section of compose.yaml; storage-source environment variables are not supported.

Networking

The application container joins a public-facing application network and an internal backend network. PocketBase joins only the internal backend network. It uses expose, never ports. The backend network is internal: true.

The server trusts forwarded headers only from explicitly trusted network ranges or proxy peers. In production it validates the effective HTTPS scheme and configured public origin. Caddy, Traefik, Nginx Proxy Manager, Synology reverse proxy, and conventional Nginx must work without requiring vendor-specific labels.

Runtime and portability

Build in a Go builder image; run in minimal Alpine with CA certificates and tzdata. Publish a multi-architecture OCI manifest for linux/amd64 and linux/arm64. Target Docker/OCI-compatible Linux hosts including OpenMediaVault, Synology Container Manager, Unraid, TrueNAS SCALE, Portainer, and standard Docker Compose. TrueNAS CORE/FreeBSD is not a target.

Both containers run as ${PUID:-1000}:${PGID:-1000}, without privileged mode, Docker socket, host networking, host PID namespace, devices, or extra capabilities. Drop all capabilities, set no-new-privileges, prefer a read-only root filesystem, and use tmpfs for /tmp. Only required mounted paths are writable.

Configuration boundary

Keep environment configuration minimal:

APP_ENV=prod
APP_URL=https://example.invalid
TZ=Europe/Paris
PUID=1000
PGID=1000

The internal app port is fixed at 8080 and PocketBase at 8090. Host mapping belongs in Compose. SMTP, session duration, audit retention, photo policy, and functional settings belong in encrypted application settings managed through setup/admin UI.

APP_ENV=dev|prod changes runtime behavior but never exposes PocketBase. Production hides internals, enforces origin and transport rules, and uses conservative logging. Development may enable local CORS and detailed server logs but must preserve authentication and isolation.

Reliability

  • Handle SIGTERM, drain requests, stop new writes, and close SQLite cleanly.
  • Use bounded request bodies, concurrency, queues, and memory.
  • Use coherent SQLite backup APIs, not raw copying of active databases.
  • Make migrations ordered, repeatable where appropriate, tested from supported previous versions, and transactional.
  • Keep media writes atomic: write temporary, validate, encrypt, fsync as appropriate, rename, then commit metadata; compensate on failure.
  • No in-process job may make the API unusable indefinitely. Long operations expose progress and resumable/checkpointed state.