2026-02-14 11:43:02 +01:00
2026-02-12 18:54:46 +01:00
2026-02-14 11:43:02 +01:00
2026-02-11 20:27:53 +01:00
2026-02-12 17:44:57 +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 (generic, with env var prompts)
├── templates/                  # Docker Compose templates with ${VAR} env var syntax
│   ├── actualbudget/
│   ├── docmost/
│   ├── filebrowser/
│   ├── homebox/
│   ├── immich/
│   ├── mealie/
│   ├── paperless-ngx/
│   ├── romm/
│   ├── stirling-pdf/
│   └── vaultwarden/
└── scripts/
    └── generate-customer.sh    # Generates customer-specific templates with baked-in secrets

The output is pushed to: https://gitea.dooplex.hu/admin/customers-felhom.eu

customers-felhom.eu/            <- Generated per-customer deployments
├── demo-felhom/
│   ├── templates.json          # Customer Portainer templates (zero env vars, zero-touch deploy)
│   ├── secrets.env             # Reference copy of all generated secrets
│   ├── actualbudget/docker-compose.yml
│   ├── docmost/docker-compose.yml
│   └── ...
└── pi-customer-1/
    ├── templates.json
    ├── secrets.env
    ├── actualbudget/docker-compose.yml
    └── ...

How It Works

  1. Templates in this repo contain Docker Compose files with ${DOMAIN}, ${HDD_PATH}, and ${SECRET} placeholders
  2. generate-customer.sh substitutes all values (domain, HDD path, auto-generated passwords) and produces customer-specific compose files + a Portainer templates.json with zero env vars
  3. docker-setup.sh on the customer node starts Portainer with --templates pointing at the customer's generated templates.json
  4. Customer opens Portainer -> Templates -> picks an app -> clicks Deploy -> done

Workflow

1. Generate customer templates (on your workstation)

# Full setup: all apps, with HDD
./scripts/generate-customer.sh --customer demo-felhom \
    --domain demo-felhom.eu --hdd-path /mnt/hdd_1 --push

# Raspberry Pi: lightweight apps only, no HDD yet
./scripts/generate-customer.sh --customer pi-customer-1 \
    --domain pi-customer-1.local \
    --apps actualbudget,filebrowser,mealie,stirling-pdf,vaultwarden --push

# Preview without creating files
./scripts/generate-customer.sh --customer test --domain test.local --dry-run

Options:

  • --customer ID -- Customer identifier (required)
  • --domain DOMAIN -- Customer domain (required)
  • --hdd-path PATH -- External HDD mount path (optional; apps needing it show a field in Portainer if omitted)
  • --apps LIST -- Comma-separated app list (default: all available)
  • --push -- Git commit and push to customers-felhom.eu repo
  • --dry-run -- Show what would be done
  • --debug -- Verbose output

2. Set up customer server

sudo ./docker-setup.sh --domain demo-felhom.eu --customer demo-felhom \
    --email certs@felhom.eu --cf-token <cloudflare-api-token>

3. Deploy apps

Open https://portainer.<domain> -> Templates -> pick an app -> Deploy the stack.

All fields (domain, passwords, secrets) are pre-filled. If HDD_PATH wasn't set during generation, apps that need it will show one field to fill in.

Re-generating after changes

Re-running generate-customer.sh for an existing customer preserves all previously generated secrets (idempotent). Only new apps or new secrets are generated.

# Add an app to an existing customer
./scripts/generate-customer.sh --customer demo-felhom \
    --domain demo-felhom.eu --hdd-path /mnt/hdd_1 --push

Environment Variables & Secrets

Secrets are auto-generated by generate-customer.sh and baked directly into the compose files and Portainer templates. They are not stored separately on the customer node -- they live in the customers-felhom.eu Git repo (which is private on your Gitea).

A secrets.env reference file is generated alongside the compose files for easy lookup.

Secret definitions

Defined in generate-customer.sh via the APP_SECRET_DEFS array:

APP_SECRET_DEFS=(
    "docmost:APP_SECRET:hex:32"
    "docmost:DB_PASSWORD:password:24"
    "immich:DB_PASSWORD:password:24"
    "paperless-ngx:PAPERLESS_SECRET_KEY:hex:32"
    "paperless-ngx:DB_PASSWORD:password:24"
    "paperless-ngx:PAPERLESS_ADMIN_USER:static:admin"
    "paperless-ngx:PAPERLESS_ADMIN_PASSWORD:password:16"
    "romm:DB_PASSWORD:password:24"
    "romm:MYSQL_ROOT_PASSWORD:password:24"
    "romm:ROMM_AUTH_SECRET_KEY:hex:32"
    "vaultwarden:ADMIN_TOKEN:hex:32"
    "vaultwarden:SIGNUPS_ALLOWED:static:true"
)

Types: password:LENGTH (alphanumeric), hex:LENGTH (crypto), static:VALUE (fixed).

Variable types per app

App DOMAIN HDD_PATH Secrets
ActualBudget yes -- --
Docmost yes -- APP_SECRET, DB_PASSWORD
FileBrowser yes yes --
Homebox yes -- --
Immich yes yes DB_PASSWORD
Mealie yes -- --
Paperless-ngx yes yes PAPERLESS_SECRET_KEY, DB_PASSWORD, PAPERLESS_ADMIN_USER, PAPERLESS_ADMIN_PASSWORD
ROMM yes yes DB_PASSWORD, MYSQL_ROOT_PASSWORD, ROMM_AUTH_SECRET_KEY
Stirling-PDF yes -- --
Vaultwarden yes -- ADMIN_TOKEN

App Catalog

App DB Type RAM Pi HDD Data Subdomain
ActualBudget None (file) ~50MB yes -- budget.*
Docmost PostgreSQL + Redis ~200MB maybe -- docs.*
FileBrowser None (file) ~30MB yes ${HDD_PATH}/storage/filebrowser/ files.*
Homebox None (SQLite) ~50MB yes -- inventory.*
Immich PostgreSQL + Redis ~4GB no ${HDD_PATH}/storage/immich/ photos.*
Mealie None (SQLite) ~200MB yes -- recipes.*
Paperless-ngx PostgreSQL + Redis ~500MB yes ${HDD_PATH}/storage/paperless/ paperless.*
ROMM MariaDB + Redis ~300MB maybe ${HDD_PATH}/storage/romm/ arcade.*
Stirling-PDF None ~200MB yes -- pdf.*
Vaultwarden None (SQLite) ~50MB yes -- vault.*

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)

Adding a New App

  1. Create templates/<appname>/docker-compose.yml using ${DOMAIN} and optionally ${HDD_PATH}
  2. Add a template entry in templates.json with env definitions, description, logo, and notes
  3. If the app needs secrets, add entries to APP_SECRET_DEFS in generate-customer.sh
  4. Re-run generate-customer.sh --push for each customer

templates.json entry format

{
  "type": 3,
  "title": "App Name",
  "description": "Short description.",
  "categories": ["category"],
  "platform": "linux",
  "logo": "https://example.com/logo.png",
  "note": "<b>Access:</b> https://subdomain.&lt;DOMAIN&gt;",
  "repository": {
    "url": "https://gitea.dooplex.hu/admin/app-catalog-felhom.eu",
    "stackfile": "templates/appname/docker-compose.yml"
  },
  "env": [
    {
      "name": "DOMAIN",
      "label": "Domain",
      "description": "Your server domain (e.g., demo-felhom.eu)"
    }
  ]
}
Repository Purpose
app-catalog-felhom.eu This repo -- templates + generation script
customers-felhom.eu Generated per-customer compose files + templates
deploy-portainer docker-setup.sh -- server provisioning
S
Description
No description provided
Readme 282 KiB
Languages
Shell 100%