architecture changed to preconfigured customer templates
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
# Felhom App Catalog
|
||||
|
||||
Central repository for all Felhom customer application deployments.
|
||||
Central repository for Felhom customer application templates.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
felhom-app-catalog/ ← This repo (source of truth)
|
||||
├── templates/ # Docker Compose templates with placeholders
|
||||
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/
|
||||
@@ -17,121 +18,186 @@ felhom-app-catalog/ ← This repo (source of truth)
|
||||
│ ├── 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
|
||||
└── ...
|
||||
└── scripts/
|
||||
└── generate-customer.sh # Generates customer-specific templates with baked-in secrets
|
||||
```
|
||||
|
||||
The `output/` directory is what gets pushed to:
|
||||
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** 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
|
||||
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
|
||||
|
||||
### Render & push
|
||||
### 1. Generate customer templates (on your workstation)
|
||||
|
||||
```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
|
||||
# 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
|
||||
```
|
||||
|
||||
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`
|
||||
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
|
||||
|
||||
### Adding a new app to the catalog
|
||||
### 2. Set up customer server
|
||||
|
||||
1. Create `templates/<appname>/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/<appname>/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/<customer-id>.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: `<customer-id>/<appname>/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
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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 | ✅ | — | 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.* |
|
||||
| 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
|
||||
|
||||
```json
|
||||
{
|
||||
"type": 3,
|
||||
"title": "App Name",
|
||||
"description": "Short description.",
|
||||
"categories": ["category"],
|
||||
"platform": "linux",
|
||||
"logo": "https://example.com/logo.png",
|
||||
"note": "<b>Access:</b> https://subdomain.<DOMAIN>",
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Related Repositories
|
||||
|
||||
| Repository | Purpose |
|
||||
|------------|---------|
|
||||
| [app-catalog-felhom.eu](https://gitea.dooplex.hu/admin/app-catalog-felhom.eu) | This repo -- templates + generation script |
|
||||
| [customers-felhom.eu](https://gitea.dooplex.hu/admin/customers-felhom.eu) | Generated per-customer compose files + templates |
|
||||
| [deploy-portainer](https://gitea.dooplex.hu/admin/deploy-portainer) | `docker-setup.sh` -- server provisioning |
|
||||
Reference in New Issue
Block a user