admin 21e8df1e3a backup classification: backup: blocks for the 13 bind-bearing apps
Adds the referential-coupling backup: block to every app that binds
${HDD_PATH}/${USERDATA_PATH} (immich, paperless-ngx, nextcloud, calibre-web,
audiobookshelf, komga, navidrome, radarr, sonarr, emby, jellyfin, plex, romm).
Each lists its userdata:/hdd: binds with class mandatory|optional|excluded
(operator-ruled + spike SQ2).

Requires controller v0.132.0 (deployed) which parses+validates these blocks.
INERT — no backup tier changes behavior yet (Task 3/4 consume it). All 13
verified against the shipped parser: parse-clean, every bind resolves explicit.
audiobookshelf media/audiobooks = optional (PENDING Viktor veto to excluded).

Ref: felhom.eu/documentation/audits/SPIKE-backup-classification-2026-07-14.md
2026-07-14 18:51:36 +02:00
2026-02-11 20:27:53 +01:00

Felhom App Catalog

Central repository for Felhom customer application templates.

Architecture

app-catalog-felhom.eu/          <- This repo (source of truth)
├── templates.json              # Portainer App Templates index (LEGACY — Portainer-only setups)
├── templates/                  # Docker Compose templates with ${VAR} env var syntax
│   ├── actualbudget/
│   │   ├── docker-compose.yml
│   │   └── .felhom.yml         # App metadata for felhom-controller
│   ├── adventurelog/
│   ├── audiobookshelf/
│   ├── bentopdf/
│   ├── bookstack/
│   ├── calcom/
│   ├── calibre-web/
│   ├── claper/
│   ├── code-server/
│   ├── crafty-controller/
│   ├── docmost/
│   ├── emby/
│   ├── filebrowser/
│   ├── ghost/
│   ├── gitea/
│   ├── glance/
│   ├── gokapi/
│   ├── grafana/
│   ├── gramps-web/
│   ├── home-assistant/
│   ├── homebox/
│   ├── homepage/
│   ├── immich/
│   ├── jellyfin/
│   ├── kimai/
│   ├── komga/
│   ├── mealie/
│   ├── n8n/
│   ├── navidrome/
│   ├── nextcloud/
│   ├── onlyoffice/
│   ├── opengist/
│   ├── outline/
│   ├── paperless-ngx/
│   ├── papra/
│   ├── plant-it/
│   ├── plex/
│   ├── privatebin/
│   ├── radarr/
│   ├── rallly/
│   ├── romm/
│   ├── seerr/
│   ├── sonarr/
│   ├── sparkyfitness/
│   ├── tandoor/
│   ├── termix/
│   ├── uptime-kuma/
│   ├── vaultwarden/
│   ├── vikunja/
│   ├── wanderer/
│   ├── wger/
│   ├── wishlist/
│   └── zipline/
└── scripts/
    └── generate-customer.sh    # LEGACY — generates customer-specific templates for Portainer

How It Works (Controller-based deployments)

The felhom-controller syncs directly from this repo:

  1. Controller periodically pulls this repo (configurable interval, default 15m)
  2. Copies docker-compose.yml and .felhom.yml from templates/<app>/ to /opt/docker/stacks/<app>/
  3. Never overwrites app.yaml (deployed config/secrets) or .env files
  4. Only copies files if content has actually changed (SHA-256 comparison)
  5. After sync, triggers a stack rescan so new/updated apps appear on the dashboard
  6. Manual sync available via "Sablonok frissítése" button on the Alkalmazások page

Controller git config (in controller.yaml)

git:
  repo_url: "https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git"
  branch: "main"
  sync_interval: "15m"
  username: ""     # Optional, for private repos
  token: ""        # Optional, for private repos

.felhom.yml Format

Each app template has a .felhom.yml metadata file that the controller uses for:

  • Display info on the dashboard (name, description, category, subdomain)
  • Resource hints (memory request/limit, Pi compatibility, HDD requirement)
  • Deploy fields (what the user fills in during first deployment)

Field types

Type Description
domain Auto-filled from controller config, read-only
secret Auto-generated, hidden from user (user sees "Generated ✓")
password Auto-generated but shown, user can override
path Filesystem path (validated for existence)
text Free text input
select Dropdown with predefined options
boolean Toggle switch

Generator types (for secret/password fields)

Generator Description
password:N N chars alphanumeric
hex:N N bytes hex-encoded
base64key:N base64: + N random bytes base64-encoded (Laravel APP_KEY format)
static:VAL Fixed value

Example .felhom.yml

display_name: "Paperless-ngx"
description: "Dokumentumok digitalizálása és rendszerezése"
category: "productivity"
subdomain: "paperless"
slug: "paperless-ngx"
resources:
  mem_request: "500M"
  mem_limit: "1152M"
  pi_compatible: true
  needs_hdd: true
deploy_fields:
  - env_var: DOMAIN
    label: "Domain"
    type: domain
    locked_after_deploy: true
  - env_var: DB_PASSWORD
    label: "Adatbázis jelszó"
    type: secret
    generate: "password:24"
    locked_after_deploy: true
  - env_var: HDD_PATH
    label: "Adattárolási útvonal"
    type: path
    required: true
    placeholder: "/mnt/felhom-drives/hdd_1"
    locked_after_deploy: true

App-email mapping (smtp_mapping)

Apps that can send outbound email (password resets, invites, confirmations) get it through one managed path: app → in-controller SMTP shim → hub → Resend. The Resend key stays hub-side; nothing app-specific lives on the box. An app opts in by declaring smtp_mapping, which renames the generic relay settings to that app's own env-var names.

When app-email is on (the household's global toggle AND the app's per-app toggle), the controller injects at deploy/redeploy: host = the on-box shim, port = 2525, from = <from_local>@felhom.eu, the security value, the optional display name, and any fixed extra vars. The values are derived from settings on every compose — never written to app.yaml — so a toggle change applies on the next redeploy without touching secrets. The shim accepts no-auth on the Docker network, so leave any SMTP_USERNAME/SMTP_PASSWORD unset.

The compose file must reference the mapped ${VAR} keys (with a harmless default) so the injected values reach the container, e.g. - SMTP_HOST=${SMTP_HOST:-}. An empty SMTP_HOST keeps the app's mail disabled when the toggle is off.

Field Meaning
host_var env key receiving the shim host (required)
port_var env key receiving the port 2525 (required)
security_var env key receiving the TLS mode (optional)
security_value the app's term for the chosen mode — starttls, TLS, NONE, …
from_var env key receiving the From address (required)
from_name_var env key receiving the From display name (optional)
from_local local-part of the From address (defaults to the app slug) → <local>@felhom.eu
extra fixed extra env (e.g. accept-invalid-cert flags)
# Vaultwarden — STARTTLS to the shim (it accepts the self-signed cert):
smtp_mapping:
  host_var: SMTP_HOST
  port_var: SMTP_PORT
  security_var: SMTP_SECURITY
  security_value: starttls
  from_var: SMTP_FROM
  from_name_var: SMTP_FROM_NAME
  from_local: vaultwarden
  extra:
    SMTP_ACCEPT_INVALID_CERTS: "true"
    SMTP_ACCEPT_INVALID_HOSTNAMES: "true"

TLS choice per app. Prefer STARTTLS to the shim for apps that can accept a self-signed cert (an "accept invalid certs" option). For apps that can't (e.g. Mealie), use plaintext (security_value: "NONE") on 2525 — the shim offers it on the Docker network and it was spike-validated. Never publish the shim off-box, so plaintext there is safe.

Currently mapped: Vaultwarden, Mealie (the two apps proven in SPIKE-smtp-app-relay-2026-06-28). Adding email to another app is just its smtp_mapping block + the matching compose ${VAR} lines.

App Catalog

App DB Type RAM (request / limit) Pi HDD Data Subdomain
ActualBudget None (file) 50M / 256M yes -- budget.*
AdventureLog PostgreSQL 100M / 384M yes -- travel.*
Audiobookshelf None (file) 100M / 512M yes ${HDD_PATH}/media/audiobooks/ audiobooks.*
BentoPDF None (file) 100M / 384M yes -- pdf.*
BookStack MariaDB 150M / 512M yes -- wiki.*
Cal.com PostgreSQL 200M / 768M no -- cal.*
Calibre-Web Automated None (file) 200M / 768M no ${HDD_PATH}/media/books/ books.*
Claper PostgreSQL 100M / 384M yes -- present.*
Code-Server None (file) 200M / 1024M no -- code.*
Crafty Controller None (file) 256M / 2048M no -- minecraft.*
Docmost PostgreSQL + Redis 200M / 768M no -- docs.*
Emby None (file) 512M / 2048M no ${HDD_PATH}/media/ emby.*
FileBrowser Quantum None (file) 50M / 256M yes ${HDD_PATH}/storage/filebrowser/ files.*
Ghost SQLite 150M / 512M no -- blog.*
Gitea SQLite 100M / 512M yes -- git.*
Glance None (file) 20M / 128M yes -- dashboard.*
Gokapi None (file) 30M / 128M yes -- share.*
Grafana None (file) 100M / 512M yes -- grafana.*
Gramps Web None (file) 100M / 384M yes -- family.*
Home Assistant None (file) 256M / 1024M yes -- ha.*
Homebox None (SQLite) 50M / 256M yes -- inventory.*
Homepage None (file) 50M / 256M yes -- home.*
Immich PostgreSQL + Redis 2048M / 4096M no ${HDD_PATH}/storage/immich/ photos.*
Jellyfin None (file) 512M / 2048M no ${HDD_PATH}/media/ media.*
Kimai MariaDB 100M / 384M yes -- time.*
Komga None (file) 200M / 512M yes ${HDD_PATH}/media/comics/ comics.*
Mealie None (SQLite) 200M / 1000M yes -- recipes.*
n8n None (file) 150M / 512M no -- auto.*
Navidrome None (file) 50M / 256M yes ${HDD_PATH}/media/music/ music.*
Nextcloud MariaDB + Redis 256M / 1024M no ${HDD_PATH}/storage/nextcloud/ cloud.*
OnlyOffice None (file) 512M / 2048M no -- office.*
OpenGist None (file) 30M / 128M yes -- gist.*
Outline PostgreSQL + Redis 200M / 768M no -- kb.*
Paperless-ngx PostgreSQL + Redis 500M / 1152M yes ${HDD_PATH}/storage/paperless/ paperless.*
Papra None (file) 50M / 256M yes -- papra.*
Plant-it None (file) 50M / 256M yes -- plants.*
Plex None (file) 512M / 2048M no ${HDD_PATH}/media/ plex.*
PrivateBin None (file) 30M / 128M yes -- paste.*
Radarr None (file) 150M / 512M yes ${HDD_PATH}/media/ radarr.*
Rallly PostgreSQL 50M / 256M yes -- poll.*
RomM MariaDB + Redis 300M / 1024M no ${HDD_PATH}/storage/romm/ arcade.*
Jellyseerr None (file) 100M / 384M yes -- requests.*
Sonarr None (file) 150M / 512M yes ${HDD_PATH}/media/ sonarr.*
SparkyFitness PostgreSQL 400M / 1792M no -- sparky.*
Tandoor Recipes PostgreSQL 150M / 512M yes -- recipes.*
Termix None (file) 30M / 128M yes -- terminal.*
Uptime Kuma None (file) 50M / 256M yes -- status.*
Vaultwarden None (SQLite) 50M / 256M yes -- vault.*
Vikunja None (file) 50M / 256M yes -- tasks.*
Wanderer None (file) 100M / 384M yes -- hike.*
wger SQLite 100M / 384M yes -- fitness.*
Wishlist None (file) 30M / 128M yes -- wishes.*
Zipline PostgreSQL 100M / 512M no -- img.*

Variable types per app

App DOMAIN HDD_PATH Secrets
ActualBudget yes -- --
AdventureLog yes -- SECRET_KEY, DB_PASSWORD
Audiobookshelf yes yes --
BentoPDF yes -- --
BookStack yes -- APP_KEY, DB_PASSWORD
Cal.com yes -- NEXTAUTH_SECRET, CALENDSO_ENCRYPTION_KEY, DB_PASSWORD
Calibre-Web Automated yes yes --
Claper yes -- SECRET_KEY_BASE, DB_PASSWORD
Code-Server yes -- PASSWORD
Crafty Controller yes -- --
Docmost yes -- APP_SECRET, DB_PASSWORD
Emby yes yes --
FileBrowser Quantum yes yes --
Ghost yes -- --
Gitea yes -- --
Glance yes -- --
Gokapi yes -- --
Grafana yes -- GF_SECURITY_ADMIN_PASSWORD
Gramps Web yes -- GRAMPSWEB_SECRET_KEY
Home Assistant yes -- --
Homebox yes -- --
Homepage yes -- --
Immich yes yes DB_PASSWORD
Jellyfin yes yes --
Kimai yes -- DB_PASSWORD, ADMIN_EMAIL, ADMIN_PASSWORD
Komga yes yes --
Mealie yes -- --
n8n yes -- N8N_ENCRYPTION_KEY
Navidrome yes yes --
Nextcloud yes yes DB_PASSWORD, MYSQL_ROOT_PASSWORD, NEXTCLOUD_ADMIN_USER, NEXTCLOUD_ADMIN_PASSWORD
OnlyOffice yes -- JWT_SECRET
OpenGist yes -- --
Outline yes -- SECRET_KEY, UTILS_SECRET, DB_PASSWORD
Paperless-ngx yes yes PAPERLESS_SECRET_KEY, DB_PASSWORD, PAPERLESS_ADMIN_USER, PAPERLESS_ADMIN_PASSWORD
Papra yes -- --
Plant-it yes -- JWT_SECRET
Plex yes yes PLEX_CLAIM
PrivateBin yes -- --
Radarr yes yes --
Rallly yes -- SECRET_PASSWORD, DB_PASSWORD
RomM yes yes DB_PASSWORD, MYSQL_ROOT_PASSWORD, ROMM_AUTH_SECRET_KEY
Jellyseerr yes -- --
Sonarr yes yes --
SparkyFitness yes -- DB_PASSWORD, APP_DB_PASSWORD, API_ENCRYPTION_KEY, BETTER_AUTH_SECRET
Tandoor Recipes yes -- SECRET_KEY, DB_PASSWORD
Termix yes -- --
Uptime Kuma yes -- --
Vaultwarden yes -- ADMIN_TOKEN
Vikunja yes -- VIKUNJA_SERVICE_JWTSECRET
Wanderer yes -- MEILI_MASTER_KEY
wger yes -- SECRET_KEY
Wishlist yes -- --
Zipline yes -- CORE_SECRET, DB_PASSWORD

Storage strategy

  • HDD host paths (${HDD_PATH}/storage/...): Large user data — photos, documents, ROMs
  • Named Docker volumes (on internal SSD): Databases, app config, caches — need fast I/O
  • Templates without ${HDD_PATH} work without an external HDD (e.g., ActualBudget, Mealie)

Docker Compose template standards

All templates follow these standards (enforced via audit):

  • ${DOMAIN} variable syntax for all domain references (not hardcoded)
  • deploy.resources.limits.memory on every service container
  • Healthchecks on every service (appropriate for service type)
  • restart: unless-stopped on every service
  • TZ=Europe/Budapest on every service
  • traefik-public external network + internal network for DB services
  • Explicit container_name: on every service
  • Header comment block with: app name, domain, DB type, RAM, Pi compatibility
  • depends_on with condition: service_healthy for DB/Redis dependencies

Legacy: Portainer-based deployments

The generate-customer.sh script and templates.json are kept for Portainer-only setups where the felhom-controller is not used. For controller-based deployments, these are not needed.

Legacy workflow

# Generate customer templates (on your workstation)
./scripts/generate-customer.sh --customer demo-felhom \
    --domain demo-felhom.eu --hdd-path /mnt/felhom-drives/hdd_1 --push

Adding a New App

  1. Create templates/<appname>/docker-compose.yml following the template standards above
  2. Create templates/<appname>/.felhom.yml following the metadata format
  3. Commit and push — the controller will pick it up on next sync
  4. (Legacy) If also needed for Portainer: add entry to templates.json and generate-customer.sh
Repository Purpose
app-catalog-felhom.eu This repo — templates + metadata
felhom-controller felhom-controller + deploy scripts
felhom.eu Website + k3s manifests
S
Description
No description provided
Readme 1.5 MiB
Languages
Python 81.9%
Shell 18.1%