diff --git a/controller/README.md b/controller/README.md index dc9066c..96cbd8c 100644 --- a/controller/README.md +++ b/controller/README.md @@ -4,7 +4,7 @@ A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware. -**Current version: v0.22.0** +**Current version: v0.22.3** --- @@ -22,6 +22,7 @@ A single, lightweight Go container that replaces Portainer + scattered systemd s - [Central Hub](#8-central-hub-reporting) - [Setup Wizard](#9-first-run-setup-wizard) - [Disaster Recovery](#10-disaster-recovery) + - [Asset Sync](#11-asset-sync) - [Repository Layout](#repository-layout) - [Configuration](#configuration) - [REST API](#rest-api) @@ -58,12 +59,17 @@ A single, lightweight Go container that replaces Portainer + scattered systemd s │ │ │ Notify │ │ REST API + Hub Reporter ││ │ │ │ │ (events) │ │ (JSON push + events) ││ │ │ │ └──────────┘ └─────────────────────────┘│ │ +│ │ ┌──────────┐ │ │ +│ │ │ Assets │ │ │ +│ │ │ (Hub │ │ │ +│ │ │ sync) │ │ │ +│ │ └──────────┘ │ │ │ └────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ - │ events + reports │ git pull - ▼ ▼ - hub.felhom.eu gitea.dooplex.hu - (central dashboard) (stack definitions) + │ events + reports │ git pull │ asset sync + ▼ ▼ ▼ + hub.felhom.eu gitea.dooplex.hu hub.felhom.eu + (central dashboard) (stack definitions) (logos, screenshots) ``` ### Key Architecture Decisions @@ -907,6 +913,60 @@ When a system drive fails and is replaced, the recovery flow uses the setup wiza --- +### 11. Asset Sync + +App assets (logos, screenshots) are managed centrally by the Hub and downloaded to each controller via a daily sync process. This decouples asset updates from controller image rebuilds — new app icons only require a Hub redeploy. + +#### How It Works (`internal/assets/syncer.go`) + +``` +1. Fetch manifest from Hub: GET /api/v1/assets/manifest (Bearer auth) +2. Compare SHA-256 checksums with local cache (/assets/) +3. Download changed/new files: GET /api/v1/assets/file/{filename} +4. Remove local files not in Hub manifest (stale cleanup) +5. Save local manifest copy for next comparison +``` + +#### Asset Resolution (two-tier) + +| Priority | Path | Source | +|----------|------|--------| +| 1 | `/assets/` | Downloaded from Hub (synced cache) | +| 2 | `/usr/share/felhom/assets/` | Baked into Docker image (fallback) | + +The `Resolve(filename)` method checks the synced cache first, then falls back to the baked-in directory. This ensures assets are always available even before the first sync. + +#### Configuration + +```yaml +assets: + sync_enabled: true # Opt-in: download assets from Hub API + sync_schedule: "05:00" # Daily sync time (HH:MM, Budapest timezone) +``` + +Asset sync requires `hub.enabled: true` with valid `hub.url` and `hub.api_key`. The initial sync runs 10 seconds after startup (to let subsystems initialize), then daily at the configured time. + +#### Sync Status + +The syncer tracks status (last sync time, result, file count, total bytes) accessible via `GET /api/assets/status`. On-demand sync can be triggered via `POST /api/assets/sync`. + +#### File Types + +The Hub serves three asset types per app: +- `{slug}-logo.svg` — primary SVG logo +- `{slug}-logo.png` — PNG fallback +- `{slug}-screenshot-{N}.webp` — app screenshots + +#### Key Design Decisions + +- **Opt-in via `sync_enabled`** — backward compatible, baked-in assets still work without Hub +- **SHA-256 change detection** — only downloads files that actually changed (bandwidth efficient) +- **Atomic file writes** — downloads to `.tmp` then `os.Rename` for crash safety +- **Stale file cleanup** — removes local files not in the Hub manifest (e.g., deleted apps) +- **Non-blocking initial sync** — runs in a goroutine with 10s delay, doesn't block startup + +--- + ## Repository Layout ``` @@ -940,6 +1000,7 @@ controller/ │ │ ├── restore_scan.go # DR: scan drives for backup data, build restore plan │ │ ├── restore_app_linux.go # DR: per-app restore (rsync config/data + docker compose up) │ │ └── restore_drives_linux.go # DR: auto-mount drives by UUID from Hub infra backup +│ ├── assets/syncer.go # Hub asset sync (download, SHA-256 compare, resolve) │ ├── api/router.go # REST API endpoints (~30 routes) │ ├── scheduler/scheduler.go # Central job scheduler (Every, Daily) │ ├── system/ @@ -1041,6 +1102,10 @@ hub: url: "https://hub.felhom.eu" api_key: "bearer-token-here" +assets: + sync_enabled: true # Download app assets (logos, screenshots) from Hub API + sync_schedule: "05:00" # Daily sync time (HH:MM, Budapest timezone) + system: reserved_memory_mb: 384 # RAM reserved for OS + controller ``` @@ -1073,6 +1138,7 @@ Auto-generated during deployment. Contains env vars, locked fields list, deploy | metrics-prune | daily | 04:00 | Delete metrics older than 30 days | | selfupdate-check | periodic | 6h | Check registry for new version (cache for UI) | | selfupdate-auto | daily | 04:30 | Auto-update if enabled + backup not running | +| asset-sync | daily | 05:00 | Download changed app assets from Hub | All daily jobs use Europe/Budapest timezone. Skip-if-running prevents concurrent execution. Panic recovery in all jobs. @@ -1167,6 +1233,13 @@ Config endpoints accept session auth OR `Authorization: Bearer ` (s | GET | `/api/metrics/containers/{name}` | Per-container time-series | | GET | `/api/metrics/sysinfo` | Static system info | +### Assets + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/assets/sync` | Trigger on-demand asset sync from Hub (async) | +| GET | `/api/assets/status` | Asset sync status (last sync, file count, total bytes) | + Response format: `{"ok": true/false, "data": ..., "error": "...", "message": "..."}` --- @@ -1247,6 +1320,9 @@ See `docker-compose.yml` for the full volume configuration. - [x] Controller self-update (v0.16.0) — Watchtower-style pull + restart, Settings page UI, API key auth, auto-update scheduling - [x] Hub-managed config (v0.20.0) — Config apply endpoint (`POST /api/config/apply`), config hash in reports for sync comparison - [x] Config content endpoint (v0.21.1) — `GET /api/config` returns raw YAML for Hub live diff and pull operations +- [x] First-run setup wizard (v0.22.0) — Web-based wizard replaces shell scripts, drive scan for local backups, Hub recovery, fresh install flow +- [x] Setup wizard logo fix (v0.22.2) — Use embedded SVG instead of filesystem path +- [x] Hub-managed asset sync (v0.22.3) — Download app logos/screenshots from Hub API with SHA-256 change detection, daily sync schedule ### In Progress / Planned @@ -1262,7 +1338,7 @@ See `docker-compose.yml` for the full volume configuration. | Node | Hardware | Domain | Status | |------|----------|--------|--------| -| demo-felhom | Acemagic GK3PLUS N100, 16G RAM, 512G SSD + 1TB HDD | demo-felhom.eu | Controller v0.20.0 | +| demo-felhom | Acemagic GK3PLUS N100, 16G RAM, 512G SSD + 1TB HDD | demo-felhom.eu | Controller v0.22.3 | | pi-customer-1 | Raspberry Pi 3B+, 1G RAM, 32G SD | pi-customer-1.local | Not yet tested | ## Related Repositories diff --git a/controller/configs/controller.yaml.example b/controller/configs/controller.yaml.example index 69eed41..e808c83 100644 --- a/controller/configs/controller.yaml.example +++ b/controller/configs/controller.yaml.example @@ -128,8 +128,10 @@ logging: # --- Assets --- assets: - # App logos, screenshots, and descriptions are baked into the container - # image at build time (from the felhom.eu website assets). - # Served locally at /static/assets/ — no external dependency. - # The source URL is only used during image build, not at runtime. + # App logos and screenshots are served from two sources: + # 1. Hub sync (if enabled): downloaded to /assets/ with SHA-256 change detection + # 2. Baked-in fallback: /usr/share/felhom/assets/ from the Docker image + # The source_url is only used during image build, not at runtime. source_url: "https://felhom.eu" + sync_enabled: false # Set true to download assets from Hub API + sync_schedule: "05:00" # Daily sync time (HH:MM, Budapest timezone)