b3eabfd611
wanderer: ghcr.io/flomp/wanderer:0.16.0 is a ghost - upstream split the app into web+db images, moved registry and renamed the org. Restructured to upstream's own v0.20.0 compose (3 services, new /data/plugins volume, second public hostname for PocketBase, meilisearch pinned DOWN to upstream's v1.36.0 per the R-42 ruling). plant-it: retired. The repo name was wrong (plant-it-server) but upstream has DELETED self-hosting; last server image is 2024-12-10 and it needs MySQL+Redis the template never had. Moved to retired/ rather than deleted - reversible. R-41 slice 1: check-image-resolvable.py. Encodes two traps - manifest inspect exits 0 while printing toomanyrequests, and the inverse, where the first sweep called 24 of 65 pins dead because Hub throttled it. Ambiguity is INCONCLUSIVE, never an accusation.
366 lines
15 KiB
Markdown
366 lines
15 KiB
Markdown
# 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/
|
|
│ ├── 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)
|
|
|
|
```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
|
|
|
|
```yaml
|
|
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) |
|
|
|
|
```yaml
|
|
# 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
|
|
|
|
```bash
|
|
# 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`
|
|
|
|
## Related Repositories
|
|
|
|
| Repository | Purpose |
|
|
|------------|---------|
|
|
| [app-catalog-felhom.eu](https://gitea.dooplex.hu/admin/app-catalog-felhom.eu) | This repo — templates + metadata |
|
|
| [felhom-controller](https://gitea.dooplex.hu/admin/felhom-controller) | felhom-controller + deploy scripts |
|
|
| [felhom.eu](https://gitea.dooplex.hu/admin/felhom.eu) | Website + k3s manifests |
|