# Felhom App Catalog Central repository for all Felhom customer application deployments. ## Architecture ``` felhom-app-catalog/ ← This repo (source of truth) ├── templates/ # Docker Compose templates with placeholders │ ├── actualbudget/ │ ├── docmost/ │ ├── filebrowser/ │ ├── homebox/ │ ├── immich/ │ ├── mealie/ │ ├── paperless-ngx/ │ ├── romm/ │ ├── stirling-pdf/ │ └── vaultwarden/ ├── customers/ # Per-customer configuration (YAML) │ ├── demo-felhom.yaml │ └── pi-customer-1.yaml ├── scripts/ │ └── render.sh # Renders output from templates + customer configs └── output/ # Generated monorepo (pushed to Gitea) ├── README.md ├── demo-felhom/ │ ├── actualbudget/docker-compose.yml │ ├── immich/docker-compose.yml │ └── ... └── pi-customer-1/ ├── actualbudget/docker-compose.yml └── ... ``` The `output/` directory is what gets pushed to: **https://gitea.dooplex.hu/admin/customers-felhom.eu** ## How It Works 1. **Templates** contain Docker Compose files with `{{DOMAIN}}` and `{{HDD_PATH}}` placeholders 2. **Customer configs** define which apps each customer gets, their domain, HDD path, and any version overrides 3. **render.sh** substitutes all placeholders and generates the output directory 4. **`--push`** commits and pushes the output to the Gitea monorepo 5. **Portainer GitOps** on each customer node pulls from the same repo, using a different compose path per stack ### Placeholder Reference | Placeholder | Source | Example | |-------------|--------|---------| | `{{DOMAIN}}` | `domain:` in customer YAML | `demo-felhom.eu` | | `{{HDD_PATH}}` | `hdd_path:` in customer YAML | `/mnt/hdd_1` | ### Storage Strategy - **HDD host paths** (`{{HDD_PATH}}/storage/...`): Large user data — photos, documents, ROMs - **Named Docker volumes** (on NVMe): Databases, app config, caches — need fast I/O - Templates that don't use `{{HDD_PATH}}` work without it (e.g. ActualBudget, Mealie) - If a template needs `{{HDD_PATH}}` but the customer config doesn't set `hdd_path:`, the render script refuses that app and tells you what to fix ## Workflow ### Render & push ```bash ./scripts/render.sh # Render all customers locally ./scripts/render.sh --push # Render + commit + push to Gitea ./scripts/render.sh --customer demo-felhom # Render one customer only ./scripts/render.sh --dry-run # Preview what would happen ./scripts/render.sh --debug # Verbose output ``` The default Gitea repo URL is `https://gitea.dooplex.hu/admin/customers-felhom.eu.git`. Override with: `GITEA_REPO_URL=https://... ./scripts/render.sh --push` ### Adding a new app to the catalog 1. Create `templates//docker-compose.yml` using `{{DOMAIN}}` and optionally `{{HDD_PATH}}` 2. Add the app name to relevant customer configs in `customers/` 3. Run `./scripts/render.sh --push` ### Updating an app version 1. Edit the image tag in `templates//docker-compose.yml` 2. Run `./scripts/render.sh --push` 3. Portainer auto-detects git changes and redeploys (if polling enabled) Customers with version overrides keep their pinned version. ### Adding a new customer 1. Create `customers/.yaml` (copy an existing one as template) 2. Run `./scripts/render.sh --push` 3. Set up Portainer GitOps stacks on the customer node (see below) ## Portainer Stack Setup (per app) On the customer's Portainer, for each app: 1. **Stacks → Add Stack → Repository** 2. Repository URL: `https://gitea.dooplex.hu/admin/customers-felhom.eu` 3. Compose path: `//docker-compose.yml` - Example: `demo-felhom/immich/docker-compose.yml` 4. Add environment variables (secrets — DB passwords, API keys, etc.) 5. Enable **GitOps auto-update** (optional, 5-minute polling) 6. Deploy ## Environment Variables Secrets are **never stored in Git**. They live in Portainer's stack environment variables on each customer node. Each template documents required env vars in comments at the top of the compose file. ## Version Pinning In a customer YAML, you can pin specific app versions: ```yaml overrides: immich_version: "v2.4.1" # Don't auto-update Immich for this customer auto_update: false # Skip ALL updates for this customer ``` ## App Catalog | App | DB Type | RAM | Pi | HDD Data | Subdomain | |-----|---------|-----|-----|----------|-----------| | ActualBudget | None (file) | ~50MB | ✅ | — | budget.* | | Docmost | PostgreSQL + Redis | ~200MB | ⚠️ | — | docs.* | | FileBrowser | None (file) | ~30MB | ✅ | `{{HDD_PATH}}/` | files.* | | Homebox | None (SQLite) | ~50MB | ✅ | — | inventory.* | | Immich | PostgreSQL + Redis | ~4GB | ❌ | `{{HDD_PATH}}/storage/immich/` | photos.* | | Mealie | None (SQLite) | ~200MB | ✅ | — | recipes.* | | Paperless-ngx | PostgreSQL + Redis | ~500MB | ✅ | `{{HDD_PATH}}/storage/paperless/` | paperless.* | | ROMM | MariaDB + Redis | ~300MB | ⚠️ | `{{HDD_PATH}}/storage/romm/` | arcade.* | | Stirling-PDF | None | ~200MB | ✅ | — | pdf.* | | Vaultwarden | None (SQLite) | ~50MB | ✅ | — | vault.* |