From 60f0a86bd4eb56cde7d6cfac584a61231e4d9c32 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 20 Sep 2026 14:31:52 +0200 Subject: [PATCH] v0.257.0: the app catalog can speak English (R-560, slice 5 Part A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The READ PATH for a second language in `.felhom.yml`. An `i18n: {en: …}` sibling block inside the same file; `Metadata.For(lang)` merges it FIELD BY FIELD over the Hungarian, so a missing or blank English field shows the Hungarian one and a half-translated app is a legal, shippable state. `For("hu")` is the parsed struct with `I18n` cleared and nothing else — measured against all 53 real catalog files, copied into `internal/stacks/testdata/catalog/`. Lists replace whole; every other list is matched by its own key, never by position. `For` never writes through the receiver: the metadata is the stack manager's, shared by concurrent requests, and an in-place merge would leak one household's language into another household's page. Pages reach catalog copy only through `LocalizeStacks`/`LocalizeStackPtr`/`MetaFor`, and `TestNoDirectMetaCopyReadOnPages` keeps a named, reasoned allow-list of every direct `.Meta.` read in `internal/web` so the NEXT page to read one fails the suite instead of quietly rendering Hungarian to an English household. Eight red-proofs. Two of them convicted a hollow TEST rather than the code: a struct copy shares its slices' backing arrays, so the obvious DeepEqual mutation check passed a deliberately broken merge; and a one-entry fixture cannot tell key matching from position matching. Both rewritten, both then seen to fail. MinAgent: 0.131.0 (unchanged). Older controllers are unaffected — `LoadMetadata` uses non-strict `yaml.Unmarshal`, so a pre-0.257.0 box drops the whole block. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CHANGELOG.md | 49 ++ REUSE.md | 1 + controller/internal/stacks/metadata.go | 6 + controller/internal/stacks/metadata_i18n.go | 320 ++++++++++++ .../internal/stacks/metadata_i18n_test.go | 478 ++++++++++++++++++ .../testdata/catalog/actualbudget/.felhom.yml | 65 +++ .../testdata/catalog/adventurelog/.felhom.yml | 81 +++ .../catalog/audiobookshelf/.felhom.yml | 86 ++++ .../testdata/catalog/bentopdf/.felhom.yml | 60 +++ .../testdata/catalog/bookstack/.felhom.yml | 75 +++ .../testdata/catalog/calcom/.felhom.yml | 101 ++++ .../testdata/catalog/calibre-web/.felhom.yml | 94 ++++ .../testdata/catalog/claper/.felhom.yml | 73 +++ .../testdata/catalog/code-server/.felhom.yml | 75 +++ .../catalog/crafty-controller/.felhom.yml | 86 ++++ .../testdata/catalog/docmost/.felhom.yml | 81 +++ .../stacks/testdata/catalog/emby/.felhom.yml | 83 +++ .../stacks/testdata/catalog/ghost/.felhom.yml | 65 +++ .../stacks/testdata/catalog/gitea/.felhom.yml | 81 +++ .../testdata/catalog/glance/.felhom.yml | 61 +++ .../testdata/catalog/gokapi/.felhom.yml | 72 +++ .../testdata/catalog/grafana/.felhom.yml | 74 +++ .../testdata/catalog/gramps-web/.felhom.yml | 67 +++ .../catalog/home-assistant/.felhom.yml | 66 +++ .../testdata/catalog/homebox/.felhom.yml | 78 +++ .../testdata/catalog/homepage/.felhom.yml | 61 +++ .../testdata/catalog/immich/.felhom.yml | 102 ++++ .../testdata/catalog/jellyfin/.felhom.yml | 83 +++ .../stacks/testdata/catalog/kimai/.felhom.yml | 80 +++ .../stacks/testdata/catalog/komga/.felhom.yml | 83 +++ .../testdata/catalog/mealie/.felhom.yml | 82 +++ .../stacks/testdata/catalog/n8n/.felhom.yml | 72 +++ .../testdata/catalog/navidrome/.felhom.yml | 82 +++ .../testdata/catalog/nextcloud/.felhom.yml | 125 +++++ .../testdata/catalog/onlyoffice/.felhom.yml | 84 +++ .../testdata/catalog/opengist/.felhom.yml | 64 +++ .../testdata/catalog/outline/.felhom.yml | 86 ++++ .../catalog/paperless-ngx/.felhom.yml | 167 ++++++ .../stacks/testdata/catalog/papra/.felhom.yml | 70 +++ .../testdata/catalog/plant-it/.felhom.yml | 83 +++ .../stacks/testdata/catalog/plex/.felhom.yml | 91 ++++ .../testdata/catalog/privatebin/.felhom.yml | 60 +++ .../testdata/catalog/radarr/.felhom.yml | 85 ++++ .../testdata/catalog/rallly/.felhom.yml | 88 ++++ .../catalog/recipe-importer/.felhom.yml | 79 +++ .../stacks/testdata/catalog/romm/.felhom.yml | 149 ++++++ .../stacks/testdata/catalog/seerr/.felhom.yml | 68 +++ .../testdata/catalog/sonarr/.felhom.yml | 85 ++++ .../catalog/sparkyfitness/.felhom.yml | 91 ++++ .../testdata/catalog/tandoor/.felhom.yml | 75 +++ .../testdata/catalog/termix/.felhom.yml | 57 +++ .../testdata/catalog/uptime-kuma/.felhom.yml | 62 +++ .../testdata/catalog/vaultwarden/.felhom.yml | 112 ++++ .../testdata/catalog/vikunja/.felhom.yml | 71 +++ .../testdata/catalog/wanderer/.felhom.yml | 81 +++ .../stacks/testdata/catalog/wger/.felhom.yml | 69 +++ .../testdata/catalog/wishlist/.felhom.yml | 61 +++ .../testdata/catalog/zipline/.felhom.yml | 80 +++ controller/internal/web/handlers.go | 50 +- .../internal/web/meta_copy_allowlist_test.go | 108 ++++ 60 files changed, 5318 insertions(+), 6 deletions(-) create mode 100644 controller/internal/stacks/metadata_i18n.go create mode 100644 controller/internal/stacks/metadata_i18n_test.go create mode 100644 controller/internal/stacks/testdata/catalog/actualbudget/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/adventurelog/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/audiobookshelf/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/bentopdf/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/bookstack/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/calcom/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/calibre-web/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/claper/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/code-server/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/crafty-controller/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/docmost/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/emby/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/ghost/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/gitea/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/glance/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/gokapi/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/grafana/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/gramps-web/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/home-assistant/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/homebox/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/homepage/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/immich/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/jellyfin/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/kimai/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/komga/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/mealie/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/n8n/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/navidrome/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/nextcloud/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/onlyoffice/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/opengist/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/outline/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/paperless-ngx/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/papra/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/plant-it/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/plex/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/privatebin/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/radarr/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/rallly/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/recipe-importer/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/romm/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/seerr/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/sonarr/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/sparkyfitness/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/tandoor/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/termix/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/uptime-kuma/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/vaultwarden/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/vikunja/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/wanderer/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/wger/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/wishlist/.felhom.yml create mode 100644 controller/internal/stacks/testdata/catalog/zipline/.felhom.yml create mode 100644 controller/internal/web/meta_copy_allowlist_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f39f5c6..b0beada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +## v0.257.0 — the app catalog can speak English (2026-09-20, R-560 slice 5 Part A) + +**MinAgent: 0.131.0** (unchanged). + +The last Hungarian an English household meets on the dashboard is the text that comes from the app +catalog: each app's one-line description, its tagline, „mire jó" list, „első lépések", the labels and +help lines under every install setting, the prerequisites. 1 032 strings across 53 apps. This release +is the READ PATH for their English twins; the twins themselves arrive in catalog pushes. + +**The format** (`10-localisation.md` §7, now measured rather than proposed). A sibling block inside +the SAME `.felhom.yml`, so a reviewer sees one app whole: + +```yaml +description: "Titkosított jegyzet és szöveg megosztás" +i18n: + en: + description: "Encrypted note and text sharing" +``` + +- **`Metadata.For(lang)`** (`internal/stacks/metadata_i18n.go`) merges FIELD BY FIELD. An English + field that is absent — or blank — shows the Hungarian one, so a half-translated app is a legal, + shippable state. That is what makes the catalog batches safe to push one at a time. +- **`For("hu")` is the parsed struct with `I18n` cleared**, measured against all 53 real catalog + files in `internal/stacks/testdata/catalog/` (`TestMetaForHuIsIdentity`). The Hungarian product + cannot be changed by the presence of a translation. +- **Lists are replaced whole; everything else is matched by ITS OWN KEY** — `deploy_fields` by + `env_var`, options by `value`, `optional_config` groups by `match_group`, integrations by + `target`, data paths by `path`. Position matching would mistranslate silently the first time a + Hungarian field is inserted above another. +- **`For` never writes through.** Metadata lives in the stack manager's cache and is shared by + concurrent requests; an in-place merge would leak one household's language into another's page. +- **Pages reach copy only through `LocalizeStacks` / `LocalizeStackPtr` / `MetaFor`**, and + `TestNoDirectMetaCopyReadOnPages` keeps a named, reasoned allow-list of every direct `.Meta.` + read in `internal/web`, so a NEW page that reads catalog copy off the manager fails the suite + instead of quietly rendering Hungarian. +- Two copy producers sit in packages that have no request and therefore no language — the + integration rows and the initial-credentials note. Both are re-taken from the localised metadata + in the handler; identical bytes for Hungarian. +- **An older controller is unaffected**: `LoadMetadata` uses non-strict `yaml.Unmarshal` (this repo + constructs no `yaml.Decoder` at all, verified), so a pre-0.257.0 box drops the whole `i18n:` block. + That is what lets a catalog push reach the entire fleet ahead of the floor raise. + +**Eight red-proofs**, each seen to fail and then revert: a mutated hu branch; deploy fields matched +by position; a blank English string winning over Hungarian; the defensive copy removed; optional +config, integrations and data paths matched by position; the untranslated half reaching the page. +**Two of them found a hollow test rather than a bug** — a struct copy shares its slices' backing +arrays, so the obvious `DeepEqual` mutation check passed a deliberately broken merge, and a one-entry +fixture cannot tell key matching from position matching. Both tests were rewritten. + ## v0.256.1 — the push log says whether a household sentence was attached (2026-09-18, R-558) **MinAgent: 0.131.0** (unchanged). One log line, no behaviour change. diff --git a/REUSE.md b/REUSE.md index 7c5f174..d9ab9b7 100644 --- a/REUSE.md +++ b/REUSE.md @@ -74,6 +74,7 @@ | `Manager.SetOffboxLatestSnapshotFn` (R-357, v0.226.0) | controller/internal/backup/offbox_restore.go | `(fn func(ctx, stack) (id string, paths []string, err error))` INIT/TEST-ONLY | Overriding the restic snapshot lookup in tests | Exists because R-357's gate could not otherwise be tested at the level that matters: reaching it requires getting past `offboxLatestSnapshot`, which shells to restic. **The assertion the seam enables is `StopStack` call count == 0** — a gate placed after the stop returns the right sentence and still takes the outage. Nil in production | | `CheckPlacement` + `PlacementMismatchMessage` | controller/internal/backup/offbox_placement.go | `(*RecoveryManifest, liveDrive, liveNS) PlacementCheck` / `(stack, PlacementCheck) string` | Comparing where a backup SAYS the data lived against where a restore is about to write | Pure and total — nil/empty/blank manifest all give the same honest "not known, no mismatch". **An UNKNOWN is never a mismatch** (refusing on an absence strands every pre-field unit). Compares the DRIVE only (the namespace root is derived from it), Cleaned, so a trailing slash is not a difference. The message names BOTH values on purpose | | `RecordedUnitForStack` + `RecordedAddress` | controller/internal/backup/offbox_placement.go | `(stack) (RecordedPlacement, RecordedAddress, bool)` | Reading back the address + data folder a backup recorded, for a reinstall prefill | Local file reads over every readable namespace root — **no network, no restic, no restore**; it exists for the NOT-INSTALLED case where `GetStackHDDPath` is `""`. **`RecordedAddress.Known()` requires BOTH halves:** an absent `SUBDOMAIN` makes the live deploy path fall back to the CATALOG default (`stacks/deploy.go:88-90`), and offering that back as "what your backup says" is a fabricated fact | +| **`Metadata.For(lang)` + `LocalizeStacks` / `LocalizeStackPtr` / `Stack.MetaFor` (R-560, v0.257.0)** | controller/internal/stacks/metadata_i18n.go | `(lang string) Metadata`; `([]Stack, lang) []Stack`; `(*Stack, lang) *Stack` | THE only way a page may reach catalog COPY — the app description, tagline, use cases, first steps, prerequisites, deploy-field labels and descriptions, optional-config text, integration labels, data-path labels and the initial-credentials note | Reading `stack.Meta.` off the manager renders HUNGARIAN to an English household, with no error and nothing wrong-looking on the page — `TestNoDirectMetaCopyReadOnPages` keeps the named, reasoned allow-list of every direct read in `internal/web`. **`For("hu")` is the parsed struct with `I18n` cleared** and nothing else (pinned over all 53 real catalog files), so a translation cannot change the Hungarian product. **Fallback is FIELD BY FIELD** — absent or blank English shows Hungarian, which is what makes a half-translated app shippable. **Lists replace WHOLE; every other list is matched by its own key** (`env_var`, option `value`, `match_group`, `target`, `path`) — position matching mistranslates silently the first time a field is inserted. **It never writes through:** the metadata is shared by concurrent requests, so an in-place merge leaks one household's language into another's page | | `Metadata.HasDeployField` | controller/internal/stacks/metadata.go | `(envVar string) bool` | "Does this app have somewhere to PUT a recorded value?" | **13 of 53 templates declare `HDD_PATH`; 40 do not** (measured 2026-08-21). For the 40 a recorded placement is a FACT TO STATE, never a value to write into a field that does not exist | | `redirectTier2` | controller/internal/web/tier2_config_handler.go | `(w, r, name, flash, flashErr)` | Tier2 page flash redirects | Same convention | | `validStackName` | controller/internal/web/validate.go | `(name string) bool` | Any stack name from a request | Single-segment, no `/ \ ..` — blocks path traversal into stacks/userdata | diff --git a/controller/internal/stacks/metadata.go b/controller/internal/stacks/metadata.go index 4305d99..e5e1499 100644 --- a/controller/internal/stacks/metadata.go +++ b/controller/internal/stacks/metadata.go @@ -66,6 +66,12 @@ type Metadata struct { // with the Fork-3 asymmetry — a malformed PATH rejects the whole block, an unknown ROLE drops // just that entry (see ValidateDataPaths). DataPaths []DataPath `yaml:"data_paths,omitempty" json:"data_paths,omitempty"` + // I18n is the catalog's copy in every language OTHER than the Hungarian above (R-560). Keyed by + // language code; only "en" exists today. It is read through `For(lang)` and NEVER rendered + // directly — see metadata_i18n.go for the whole contract. `json:"-"`: this struct is serialised + // into the API answers and the page data, and the untranslated half of a bundle has no reader + // there; the merged view is what travels. + I18n map[string]MetadataOverlay `yaml:"i18n,omitempty" json:"-"` } // SMTPMapping renames the generic relay settings (host / port / security / from / from-name) diff --git a/controller/internal/stacks/metadata_i18n.go b/controller/internal/stacks/metadata_i18n.go new file mode 100644 index 0000000..fd32491 --- /dev/null +++ b/controller/internal/stacks/metadata_i18n.go @@ -0,0 +1,320 @@ +package stacks + +import "strings" + +// CATALOG COPY IN A SECOND LANGUAGE (localisation slice 5, R-560). +// +// A catalog template's customer-facing text is Hungarian in the fields the controller has always +// read (`description`, `app_info`, `deploy_fields[].label` …). English is a SIBLING BLOCK in the +// SAME file: +// +// description: "Titkosított jegyzet és szöveg megosztás" +// app_info: +// first_steps: [...] +// i18n: +// en: +// description: "Encrypted notes and text sharing" +// app_info: +// first_steps: [...] +// +// THREE PROPERTIES, EACH LOAD-BEARING. +// +// 1. **The Hungarian bytes never move.** A translation adds a block; it never edits a Hungarian +// string. `For("hu")` is the parsed struct with `I18n` cleared — pinned by +// TestMetaForHuIsIdentity over all 53 real catalog files. +// +// 2. **An older controller ignores the block.** `LoadMetadata` uses `yaml.Unmarshal`, which is +// NON-STRICT (no `KnownFields`; verified — this repo constructs no yaml.Decoder at all), so a +// controller built before this file silently drops `i18n:` and renders exactly as it did. That +// is what lets the catalog be pushed to the whole fleet ahead of the floor raise. +// +// 3. **Fallback is FIELD BY FIELD, never all-or-nothing.** A missing — or empty — English field +// shows the Hungarian one (10-localisation.md §4, operator ruling 3). A half-translated app is +// therefore a legal, shippable state, which is what makes the batch pushes safe. +// +// LISTS ARE REPLACED WHOLE, NEVER MERGED BY INDEX. `use_cases`, `first_steps` and `prerequisites` +// are prose in a running order; merging item 3 of one language with item 4 of another would produce +// a list nobody wrote. An English list that is present replaces the Hungarian list entirely; an +// absent or empty one leaves the Hungarian list alone. +// +// EVERY OTHER LIST IS MATCHED BY ITS OWN KEY, NEVER BY POSITION: `deploy_fields` by `env_var`, +// `options` by `value`, `optional_config` groups by `match_group` (the Hungarian `group` value they +// translate — a group has no other stable identity), `optional_config[].fields` by `env_var`, +// `integrations` by `target`, `data_paths` by `path`. Position matching would silently mistranslate +// the moment a Hungarian field is inserted above another, and nothing on the page would look wrong. +// +// WHAT AN OVERLAY MAY CARRY IS EXACTLY THE COPY FIELDS — no `env_var`, `type`, `default`, +// `generate`, `required`, `locked_after_deploy`, `data_key`, `path`, `role`, `docs_url`, no image, +// port or healthcheck. The struct shape below IS that rule: a translation cannot change what an app +// does, only what it says. The catalog's `check-copy-i18n.py` gate states the same rule on the file +// before it is ever parsed here. + +// MetadataOverlay is one language's copy for one app. Every scalar is a POINTER so that "the +// translator did not write this field" is distinguishable from "the translator wrote an empty +// string" — both fall back to Hungarian, but only the first is silent. +type MetadataOverlay struct { + Description *string `yaml:"description,omitempty"` + AppInfo *AppInfoOverlay `yaml:"app_info,omitempty"` + DeployFields []DeployFieldOverlay `yaml:"deploy_fields,omitempty"` + OptionalConfig []OptionalConfigGroupOverlay `yaml:"optional_config,omitempty"` + Integrations []IntegrationOverlay `yaml:"integrations,omitempty"` + DataPaths []DataPathOverlay `yaml:"data_paths,omitempty"` + InitialCreds *InitialCredentialsOverlay `yaml:"initial_credentials,omitempty"` +} + +// AppInfoOverlay carries the info page's prose. The three lists replace whole. +type AppInfoOverlay struct { + Tagline *string `yaml:"tagline,omitempty"` + UseCases []string `yaml:"use_cases,omitempty"` + FirstSteps []string `yaml:"first_steps,omitempty"` + Prerequisites []string `yaml:"prerequisites,omitempty"` + DefaultCreds *string `yaml:"default_creds,omitempty"` +} + +// DeployFieldOverlay translates one deploy field, found by EnvVar. +type DeployFieldOverlay struct { + EnvVar string `yaml:"env_var"` + Label *string `yaml:"label,omitempty"` + Description *string `yaml:"description,omitempty"` + Placeholder *string `yaml:"placeholder,omitempty"` + Options []SelectOptionOverlay `yaml:"options,omitempty"` +} + +// SelectOptionOverlay translates one select option's label, found by Value. +type SelectOptionOverlay struct { + Value string `yaml:"value"` + Label *string `yaml:"label,omitempty"` +} + +// OptionalConfigGroupOverlay translates one optional-config group. MatchGroup is the HUNGARIAN +// `group:` value it belongs to — a group carries no id, and its own label is the thing being +// translated, so the match key has to be stated rather than derived. +type OptionalConfigGroupOverlay struct { + MatchGroup string `yaml:"match_group"` + Group *string `yaml:"group,omitempty"` + Description *string `yaml:"description,omitempty"` + Fields []OptionalConfigFieldOverlay `yaml:"fields,omitempty"` +} + +// OptionalConfigFieldOverlay translates one optional-config field, found by EnvVar. +type OptionalConfigFieldOverlay struct { + EnvVar string `yaml:"env_var"` + Label *string `yaml:"label,omitempty"` + HelpText *string `yaml:"help_text,omitempty"` +} + +// IntegrationOverlay translates one integration offer, found by Target. +type IntegrationOverlay struct { + Target string `yaml:"target"` + Label *string `yaml:"label,omitempty"` + Description *string `yaml:"description,omitempty"` +} + +// DataPathOverlay translates one data-folder label, found by Path. +type DataPathOverlay struct { + Path string `yaml:"path"` + Label *string `yaml:"label,omitempty"` +} + +// InitialCredentialsOverlay translates the note shown beside a first-login credential. Only the +// note: the file path, the format and the keys are configuration, and the credential VALUE never +// passes through here at all. +type InitialCredentialsOverlay struct { + Note *string `yaml:"note,omitempty"` +} + +// BaseLanguage is the language the unmarshalled fields themselves are written in. It is not a +// choice a template can make — the catalog's Hungarian is the base by construction (the freeze +// gate in the catalog repo pins it byte for byte). +const BaseLanguage = "hu" + +// overlayStr returns the translated string, or the base one when the translation is absent or +// blank. A blank translation is treated as ABSENT and never renders an empty box — operator +// ruling 3, 10-localisation.md §4. +func overlayStr(p *string, base string) string { + if p == nil || strings.TrimSpace(*p) == "" { + return base + } + return *p +} + +// overlayList returns the translated list, or the base one when the translation is absent or +// empty. Whole-list replacement, never a per-index merge — see the header. +func overlayList(in []string, base []string) []string { + if len(in) == 0 { + return base + } + out := make([]string, len(in)) + copy(out, in) + return out +} + +// For returns this app's metadata as the given language renders it. +// +// For the base language — and for any language this template has no block for — it is the parsed +// struct with `I18n` cleared, so a Hungarian page cannot be changed by the presence of a +// translation. For a language with a block, copy fields are replaced one by one and EVERYTHING +// ELSE is carried through untouched. +// +// It NEVER mutates the receiver. Every slice it changes is copied first: `Metadata` values live +// inside the stack manager's cache and are handed to many requests at once, so an in-place edit +// would leak one household's language into another's page. +// +// VALUE receiver, like CanInstall and CatalogSinceAge above it — see the comment there: a pointer +// receiver on a type that reaches html/template inside an interface{} is a render-time 500. +func (m Metadata) For(lang string) Metadata { + out := m + out.I18n = nil + if lang == "" || lang == BaseLanguage { + return out + } + ov, ok := m.I18n[lang] + if !ok { + return out + } + + out.Description = overlayStr(ov.Description, out.Description) + + if ov.AppInfo != nil { + out.AppInfo.Tagline = overlayStr(ov.AppInfo.Tagline, out.AppInfo.Tagline) + out.AppInfo.DefaultCreds = overlayStr(ov.AppInfo.DefaultCreds, out.AppInfo.DefaultCreds) + out.AppInfo.UseCases = overlayList(ov.AppInfo.UseCases, out.AppInfo.UseCases) + out.AppInfo.FirstSteps = overlayList(ov.AppInfo.FirstSteps, out.AppInfo.FirstSteps) + out.AppInfo.Prerequisites = overlayList(ov.AppInfo.Prerequisites, out.AppInfo.Prerequisites) + } + + if len(ov.DeployFields) > 0 && len(out.DeployFields) > 0 { + fields := make([]DeployField, len(out.DeployFields)) + copy(fields, out.DeployFields) + for _, fo := range ov.DeployFields { + for i := range fields { + if fields[i].EnvVar != fo.EnvVar || fo.EnvVar == "" { + continue + } + fields[i].Label = overlayStr(fo.Label, fields[i].Label) + fields[i].Description = overlayStr(fo.Description, fields[i].Description) + fields[i].Placeholder = overlayStr(fo.Placeholder, fields[i].Placeholder) + if len(fo.Options) > 0 && len(fields[i].Options) > 0 { + opts := make([]SelectOption, len(fields[i].Options)) + copy(opts, fields[i].Options) + for _, oo := range fo.Options { + for j := range opts { + if opts[j].Value == oo.Value && oo.Value != "" { + opts[j].Label = overlayStr(oo.Label, opts[j].Label) + } + } + } + fields[i].Options = opts + } + break + } + } + out.DeployFields = fields + } + + if len(ov.OptionalConfig) > 0 && len(out.OptionalConfig) > 0 { + groups := make([]OptionalConfigGroup, len(out.OptionalConfig)) + copy(groups, out.OptionalConfig) + for _, gov := range ov.OptionalConfig { + for i := range groups { + if groups[i].Group != gov.MatchGroup || gov.MatchGroup == "" { + continue + } + groups[i].Description = overlayStr(gov.Description, groups[i].Description) + if len(gov.Fields) > 0 && len(groups[i].Fields) > 0 { + flds := make([]OptionalConfigField, len(groups[i].Fields)) + copy(flds, groups[i].Fields) + for _, fo := range gov.Fields { + for j := range flds { + if flds[j].EnvVar == fo.EnvVar && fo.EnvVar != "" { + flds[j].Label = overlayStr(fo.Label, flds[j].Label) + flds[j].HelpText = overlayStr(fo.HelpText, flds[j].HelpText) + } + } + } + groups[i].Fields = flds + } + // Group LAST: it is the match key, so translating it earlier would make the + // remaining overlay entries for this group unmatchable. + groups[i].Group = overlayStr(gov.Group, groups[i].Group) + break + } + } + out.OptionalConfig = groups + } + + if len(ov.Integrations) > 0 && len(out.Integrations) > 0 { + ints := make([]IntegrationDef, len(out.Integrations)) + copy(ints, out.Integrations) + for _, io := range ov.Integrations { + for i := range ints { + if ints[i].Target == io.Target && io.Target != "" { + ints[i].Label = overlayStr(io.Label, ints[i].Label) + ints[i].Description = overlayStr(io.Description, ints[i].Description) + } + } + } + out.Integrations = ints + } + + if len(ov.DataPaths) > 0 && len(out.DataPaths) > 0 { + dps := make([]DataPath, len(out.DataPaths)) + copy(dps, out.DataPaths) + for _, do := range ov.DataPaths { + for i := range dps { + if dps[i].Path == do.Path && do.Path != "" { + dps[i].Label = overlayStr(do.Label, dps[i].Label) + } + } + } + out.DataPaths = dps + } + + if ov.InitialCreds != nil && out.InitialCreds != nil { + ic := *out.InitialCreds + ic.Note = overlayStr(ov.InitialCreds.Note, ic.Note) + out.InitialCreds = &ic + } + + return out +} + +// MetaFor is For on a stack, and it is the ONLY way a page should reach catalog copy: reading +// `stack.Meta.Description` straight out of the manager renders Hungarian to an English household. +// TestNoDirectMetaCopyReadOnPages pins that rule on the handler sources. +func (s Stack) MetaFor(lang string) Metadata { return s.Meta.For(lang) } + +// LocalizeStacks returns the list as the given language renders it. +// +// For the base language it returns the INPUT SLICE ITSELF — not a copy. That is deliberate and it +// is the parity guarantee in code: the Hungarian page cannot differ from the pre-change one, +// because nothing on its path was touched. For another language every element is copied (Stack is +// a value type) and only its Meta replaced. +func LocalizeStacks(in []Stack, lang string) []Stack { + if lang == "" || lang == BaseLanguage { + return in + } + out := make([]Stack, len(in)) + copy(out, in) + for i := range out { + out[i].Meta = out[i].Meta.For(lang) + } + return out +} + +// LocalizeStack is LocalizeStacks for a single stack. +func LocalizeStack(in Stack, lang string) Stack { + in.Meta = in.Meta.For(lang) + return in +} + +// LocalizeStackPtr is LocalizeStack for a stack POINTER, returning a NEW pointer so the value the +// caller holds — which may be the manager's own — is never written through. A nil stack stays nil. +func LocalizeStackPtr(in *Stack, lang string) *Stack { + if in == nil { + return nil + } + out := *in + out.Meta = out.Meta.For(lang) + return &out +} diff --git a/controller/internal/stacks/metadata_i18n_test.go b/controller/internal/stacks/metadata_i18n_test.go new file mode 100644 index 0000000..4c97f21 --- /dev/null +++ b/controller/internal/stacks/metadata_i18n_test.go @@ -0,0 +1,478 @@ +package stacks + +import ( + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "gopkg.in/yaml.v3" +) + +// testdata/catalog is a SNAPSHOT of all 53 `templates//.felhom.yml` from +// app-catalog-felhom.eu at 94bc5febaca2 (2026-09-20), copied verbatim. It is here so the identity +// property below is measured on the REAL catalog rather than on a fixture written to pass. +// +// Refresh it with, from this repo's `controller/` directory: +// +// for d in ../../app-catalog-felhom.eu/templates/*/; do +// a=$(basename "$d"); mkdir -p internal/stacks/testdata/catalog/$a +// cp "$d/.felhom.yml" internal/stacks/testdata/catalog/$a/.felhom.yml +// done +// +// A refresh is only ever a copy — if one of these tests then fails, the catalog has grown a shape +// the overlay does not handle, which is the finding, not a reason to edit the fixture. +const catalogFixtureDir = "testdata/catalog" + +func loadCatalogFixtures(t *testing.T) map[string]Metadata { + t.Helper() + entries, err := os.ReadDir(catalogFixtureDir) + if err != nil { + t.Fatalf("catalog fixtures unreadable: %v", err) + } + out := make(map[string]Metadata, len(entries)) + for _, e := range entries { + if !e.IsDir() { + continue + } + b, err := os.ReadFile(filepath.Join(catalogFixtureDir, e.Name(), ".felhom.yml")) + if err != nil { + t.Fatalf("%s: %v", e.Name(), err) + } + var m Metadata + if err := yaml.Unmarshal(b, &m); err != nil { + t.Fatalf("%s: %v", e.Name(), err) + } + out[e.Name()] = m + } + if len(out) < 50 { + t.Fatalf("only %d catalog fixtures found — the snapshot is not the catalog", len(out)) + } + return out +} + +// TestMetaForHuIsIdentity — S1. The Hungarian product must render byte-for-byte the same before and +// after this slice (10-localisation.md §1). `For("hu")` is therefore required to be the parsed +// struct with nothing but `I18n` cleared, over every real catalog file. +// +// RED-PROOF: mutating any copy field inside For's hu branch — e.g. adding +// `out.Description = out.Description + " "` before the `lang == BaseLanguage` return — fails this +// naming the app and the field. Run 2026-09-20: FAIL on 53 apps, then green after revert. +func TestMetaForHuIsIdentity(t *testing.T) { + for app, m := range loadCatalogFixtures(t) { + want := m + want.I18n = nil + for _, lang := range []string{"hu", ""} { + got := m.For(lang) + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: For(%q) changed the Hungarian metadata\n got: %+v\nwant: %+v", app, lang, got, want) + } + } + } +} + +// TestMetaForUnknownLanguageIsHungarian — a language no template carries a block for must fall all +// the way back, not render blanks. This is the state EVERY app is in until its batch is pushed. +func TestMetaForUnknownLanguageIsHungarian(t *testing.T) { + for app, m := range loadCatalogFixtures(t) { + want := m + want.I18n = nil + if got := m.For("en"); !reflect.DeepEqual(got, want) { + t.Errorf("%s: For(\"en\") on an untranslated app is not the Hungarian view", app) + } + } +} + +// TestCatalogFixturesCarryNoOverlayYet pins the fact the two tests above depend on: the snapshot is +// of an UNTRANSLATED catalog, so "For(en) equals Hungarian" above is a real assertion and not a +// tautology that would keep passing once a block exists. When the pilot lands in the catalog and the +// snapshot is refreshed, this test fails — and the refresher must then split the fixture set into +// translated and untranslated, rather than delete the assertion. +func TestCatalogFixturesCarryNoOverlayYet(t *testing.T) { + for app, m := range loadCatalogFixtures(t) { + if len(m.I18n) != 0 { + t.Errorf("%s: the snapshot now carries an i18n block — see this test's comment", app) + } + } +} + +// overlayFixture is one hand-written app carrying a PARTIAL English block: `description`, the +// tagline, first_steps and ONE of two deploy fields, with the English deploy_fields list in the +// OPPOSITE order to the Hungarian one. +const overlayFixture = ` +display_name: "Fixture" +description: "Magyar leírás" +slug: fixture +app_info: + tagline: "Magyar tagline" + use_cases: + - "Magyar eset egy" + - "Magyar eset kettő" + first_steps: + - "Magyar lépés egy" + - "Magyar lépés kettő" + prerequisites: + - "Magyar előfeltétel" + default_creds: "admin / admin" +deploy_fields: + - env_var: FIRST + label: "Első mező" + description: "Az első mező leírása" + placeholder: "elso" + type: text + default: "alap" + required: true + - env_var: SECOND + label: "Második mező" + description: "A második mező leírása" + type: select + options: + - value: "yes" + label: "Igen" + - value: "no" + label: "Nem" +i18n: + en: + description: "English description" + app_info: + tagline: "English tagline" + first_steps: + - "English step one" + - "English step two" + deploy_fields: + - env_var: SECOND + label: "Second field" + options: + - value: "no" + label: "No" + - env_var: FIRST + description: "The first field explained" +` + +func parseFixture(t *testing.T, src string) Metadata { + t.Helper() + var m Metadata + if err := yaml.Unmarshal([]byte(src), &m); err != nil { + t.Fatalf("fixture: %v", err) + } + return m +} + +// TestMetaForEnFieldByField — S2. Every English field present wins; every absent one shows the +// Hungarian; nothing renders blank. +func TestMetaForEnFieldByField(t *testing.T) { + en := parseFixture(t, overlayFixture).For("en") + + checks := []struct{ name, got, want string }{ + {"description", en.Description, "English description"}, + {"tagline", en.AppInfo.Tagline, "English tagline"}, + // Absent from the English block — the Hungarian must show through, field by field. + {"default_creds", en.AppInfo.DefaultCreds, "admin / admin"}, + {"display_name", en.DisplayName, "Fixture"}, + } + for _, c := range checks { + if c.got != c.want { + t.Errorf("%s = %q, want %q", c.name, c.got, c.want) + } + } + if got := strings.Join(en.AppInfo.FirstSteps, "|"); got != "English step one|English step two" { + t.Errorf("first_steps = %q", got) + } + // use_cases and prerequisites carry no English — whole Hungarian lists, not empty ones. + if got := strings.Join(en.AppInfo.UseCases, "|"); got != "Magyar eset egy|Magyar eset kettő" { + t.Errorf("use_cases fell back wrong: %q", got) + } + if got := strings.Join(en.AppInfo.Prerequisites, "|"); got != "Magyar előfeltétel" { + t.Errorf("prerequisites fell back wrong: %q", got) + } +} + +// TestDeployFieldsMatchedByKey — S2's second half, and the one that would fail silently on a live +// box: the English deploy_fields list is in the OPPOSITE order, so a position match would put +// "Second field" on FIRST. Everything that is not copy must also survive untouched. +func TestDeployFieldsMatchedByKey(t *testing.T) { + en := parseFixture(t, overlayFixture).For("en") + byVar := map[string]DeployField{} + for _, f := range en.DeployFields { + byVar[f.EnvVar] = f + } + + first, second := byVar["FIRST"], byVar["SECOND"] + if first.Label != "Első mező" { + t.Errorf("FIRST.label = %q — the English block does not translate it", first.Label) + } + if first.Description != "The first field explained" { + t.Errorf("FIRST.description = %q", first.Description) + } + if first.Placeholder != "elso" { + t.Errorf("FIRST.placeholder = %q — no English given, Hungarian expected", first.Placeholder) + } + if second.Label != "Second field" { + t.Errorf("SECOND.label = %q", second.Label) + } + if second.Description != "A második mező leírása" { + t.Errorf("SECOND.description = %q", second.Description) + } + + // Non-copy fields are not in MetadataOverlay at all, and this is the assertion that says so. + if first.Default != "alap" || !first.Required || first.Type != "text" { + t.Errorf("FIRST lost configuration: %+v", first) + } + + // Options are matched by VALUE, and only the one the block names changes. + opts := map[string]string{} + for _, o := range second.Options { + opts[o.Value] = o.Label + } + if opts["no"] != "No" { + t.Errorf("option no = %q, want %q", opts["no"], "No") + } + if opts["yes"] != "Igen" { + t.Errorf("option yes = %q — untranslated options must stay Hungarian", opts["yes"]) + } +} + +// TestEmptyEnglishFallsBack — a translator who leaves a field as `""` must not blank the page. +// Operator ruling 3 (10-localisation.md §4): never a key, never an empty box. +func TestEmptyEnglishFallsBack(t *testing.T) { + m := parseFixture(t, ` +description: "Magyar leírás" +app_info: + tagline: "Magyar tagline" + use_cases: ["Magyar eset"] +i18n: + en: + description: "" + app_info: + tagline: " " + use_cases: [] +`) + en := m.For("en") + if en.Description != "Magyar leírás" { + t.Errorf("empty English description did not fall back: %q", en.Description) + } + if en.AppInfo.Tagline != "Magyar tagline" { + t.Errorf("whitespace-only English tagline did not fall back: %q", en.AppInfo.Tagline) + } + if len(en.AppInfo.UseCases) != 1 || en.AppInfo.UseCases[0] != "Magyar eset" { + t.Errorf("empty English list did not fall back: %v", en.AppInfo.UseCases) + } +} + +// TestForDoesNotMutateTheReceiver — the metadata For reads lives in the stack manager's cache and +// is shared by every concurrent request. An in-place write would leak one household's language into +// another household's page, and the page would look perfectly normal. +// NOTE ON HOW THIS IS ASSERTED. The obvious form — copy the struct, call For, DeepEqual the copy +// against the original — IS HOLLOW, and was written that way first: a struct copy shares the slices' +// backing arrays, so a write through `out.DeployFields[i].Label` changes BOTH sides and the compare +// passes. It did pass, on a deliberately broken For, in this file's red-proof run. What follows +// therefore snapshots the concrete STRINGS before the call and compares those. +func TestForDoesNotMutateTheReceiver(t *testing.T) { + m := parseFixture(t, overlayFixture) + snap := func() []string { + out := []string{m.Description, m.AppInfo.Tagline, strings.Join(m.AppInfo.FirstSteps, "|")} + for _, f := range m.DeployFields { + out = append(out, f.EnvVar, f.Label, f.Description, f.Placeholder) + for _, o := range f.Options { + out = append(out, o.Value, o.Label) + } + } + return out + } + before := snap() + _ = m.For("en") + if after := snap(); !reflect.DeepEqual(before, after) { + t.Fatalf("For(\"en\") wrote through to the receiver\nbefore: %q\n after: %q", before, after) + } + // And the hu view taken AFTER an English render is still Hungarian. + if hu := m.For("hu"); hu.Description != "Magyar leírás" || hu.DeployFields[1].Options[1].Label != "Nem" { + t.Fatalf("Hungarian view damaged by an English render: %q / %q", hu.Description, hu.DeployFields[1].Options[1].Label) + } +} + +// TestOverlayMatchesOptionalConfigIntegrationsAndDataPaths covers the three key-matched blocks the +// main fixture leaves out. optional_config groups have no id of their own, so the overlay names the +// Hungarian group it translates in `match_group`. +func TestOverlayMatchesOptionalConfigIntegrationsAndDataPaths(t *testing.T) { + // EACH LIST CARRIES TWO ENTRIES AND THE OVERLAY NAMES ONLY THE SECOND. With a single entry per + // list — how this fixture was first written — position and key agree, and a position-matching + // implementation passes the test. It did, in this file's red-proof run. Two entries, translated + // out of order, is what makes the assertion mean anything. + m := parseFixture(t, ` +optional_config: + - group: "Egyéb" + description: "Nem fordított csoport" + fields: + - env_var: OTHER_KEY + label: "Más kulcs" + help_text: "Más magyar súgó" + - group: "Metaadat-szolgáltatók" + description: "Magyar csoportleírás" + fields: + - env_var: KEY_ONE + label: "Kulcs egy" + help_text: "Magyar súgó" + - env_var: KEY_TWO + label: "Kulcs kettő" + help_text: "Másik magyar súgó" +integrations: + - target: nextcloud + label: "Nem fordított integráció" + description: "Nem fordított leírás" + - target: filebrowser + label: "Magyar integráció" + description: "Magyar integrációs leírás" +data_paths: + - path: "export" + role: export + label: "Nem fordított mappa" + - path: "import" + role: import + label: "Magyar mappa" +initial_credentials: + file: /x + note: "Magyar megjegyzés" +i18n: + en: + optional_config: + - match_group: "Metaadat-szolgáltatók" + group: "Metadata providers" + fields: + - env_var: KEY_TWO + help_text: "English help" + integrations: + - target: filebrowser + label: "English integration" + data_paths: + - path: "import" + label: "English folder" + initial_credentials: + note: "English note" +`) + en := m.For("en") + + // The FIRST entry of each list is the one nobody translated — it must be untouched. + if en.OptionalConfig[0].Group != "Egyéb" || en.OptionalConfig[0].Fields[0].HelpText != "Más magyar súgó" { + t.Errorf("an untranslated group was changed: %+v", en.OptionalConfig[0]) + } + if en.Integrations[0].Label != "Nem fordított integráció" { + t.Errorf("an untranslated integration was changed: %+v", en.Integrations[0]) + } + if en.DataPaths[0].Label != "Nem fordított mappa" { + t.Errorf("an untranslated data_path was changed: %+v", en.DataPaths[0]) + } + + g := en.OptionalConfig[1] + if g.Group != "Metadata providers" { + t.Errorf("group = %q", g.Group) + } + if g.Description != "Magyar csoportleírás" { + t.Errorf("group description should fall back: %q", g.Description) + } + if g.Fields[1].HelpText != "English help" { + t.Errorf("KEY_TWO help_text = %q", g.Fields[1].HelpText) + } + if g.Fields[0].HelpText != "Magyar súgó" || g.Fields[1].Label != "Kulcs kettő" { + t.Errorf("unnamed optional-config fields must stay Hungarian: %+v", g.Fields) + } + if en.Integrations[1].Label != "English integration" || en.Integrations[1].Description != "Magyar integrációs leírás" { + t.Errorf("integration overlay wrong: %+v", en.Integrations[1]) + } + if en.DataPaths[1].Label != "English folder" || en.DataPaths[1].Path != "import" { + t.Errorf("data_path overlay wrong: %+v", en.DataPaths[1]) + } + if en.InitialCreds.Note != "English note" || en.InitialCreds.File != "/x" { + t.Errorf("initial_credentials overlay wrong: %+v", en.InitialCreds) + } + + // The Hungarian view is still Hungarian afterwards — the nested slices were copied, not edited. + hu := m.For("hu") + if hu.OptionalConfig[1].Group != "Metaadat-szolgáltatók" || hu.OptionalConfig[1].Fields[1].HelpText != "Másik magyar súgó" { + t.Errorf("Hungarian optional_config damaged: %+v", hu.OptionalConfig[1]) + } + if hu.InitialCreds.Note != "Magyar megjegyzés" { + t.Errorf("Hungarian initial_credentials note damaged: %q", hu.InitialCreds.Note) + } +} + +// TestUnmatchedOverlayEntriesAreInert — an English entry whose key has no Hungarian twin must +// change nothing. (The catalog gate REFUSES such a file; this is the controller's behaviour if one +// reaches a box anyway, e.g. from a hand-edited template.) +func TestUnmatchedOverlayEntriesAreInert(t *testing.T) { + m := parseFixture(t, ` +deploy_fields: + - env_var: FIRST + label: "Első mező" +i18n: + en: + deploy_fields: + - env_var: NOSUCHFIELD + label: "Ghost" +`) + en := m.For("en") + if len(en.DeployFields) != 1 || en.DeployFields[0].Label != "Első mező" { + t.Fatalf("an unmatched overlay entry changed the fields: %+v", en.DeployFields) + } +} + +// TestLoadMetadataReadsTheI18nBlock — the loader is the seam that actually has to see `i18n:`. +// Without this the whole overlay could be correct and inert (the "seam built but never wired" +// class, on file four times in this project). +func TestLoadMetadataReadsTheI18nBlock(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, ".felhom.yml"), []byte(overlayFixture), 0o644); err != nil { + t.Fatal(err) + } + m := LoadMetadata(dir) + if len(m.I18n) != 1 { + t.Fatalf("LoadMetadata did not parse the i18n block: %+v", m.I18n) + } + if got := m.For("en").Description; got != "English description" { + t.Fatalf("loaded metadata does not localise: %q", got) + } + if got := m.For("hu").Description; got != "Magyar leírás" { + t.Fatalf("loaded metadata changed Hungarian: %q", got) + } +} + +// TestLocalizeStacksLeavesHungarianSliceAlone — LocalizeStacks returns the INPUT SLICE for hu. That +// is the parity guarantee in code, so it is asserted rather than assumed. +func TestLocalizeStacksLeavesHungarianSliceAlone(t *testing.T) { + in := []Stack{{Name: "a", Meta: parseFixture(t, overlayFixture)}} + if got := LocalizeStacks(in, "hu"); &got[0] != &in[0] { + t.Fatalf("LocalizeStacks copied the slice for hu") + } + out := LocalizeStacks(in, "en") + if out[0].Meta.Description != "English description" { + t.Fatalf("LocalizeStacks did not localise: %q", out[0].Meta.Description) + } + if in[0].Meta.Description != "Magyar leírás" { + t.Fatalf("LocalizeStacks wrote through to the input: %q", in[0].Meta.Description) + } + if out[0].Meta.I18n != nil { + t.Fatalf("the untranslated half must not reach a page") + } +} + +// TestLocalizeStackPtrReturnsANewPointer — the manager hands out pointers to its own values; a +// localised page must never be able to write into one. +func TestLocalizeStackPtrReturnsANewPointer(t *testing.T) { + st := &Stack{Name: "a", Meta: parseFixture(t, overlayFixture)} + out := LocalizeStackPtr(st, "en") + if out == st { + t.Fatalf("LocalizeStackPtr returned the caller's pointer") + } + if st.Meta.Description != "Magyar leírás" { + t.Fatalf("the manager's value was localised in place: %q", st.Meta.Description) + } + if LocalizeStackPtr(nil, "en") != nil { + t.Fatalf("nil must stay nil") + } + if got := LocalizeStackPtr(st, "hu"); got.Meta.Description != "Magyar leírás" { + t.Fatalf("hu view wrong: %q", got.Meta.Description) + } + if got := st.MetaFor("en"); got.Description != "English description" { + t.Fatalf("MetaFor wrong: %q", got.Description) + } +} diff --git a/controller/internal/stacks/testdata/catalog/actualbudget/.felhom.yml b/controller/internal/stacks/testdata/catalog/actualbudget/.felhom.yml new file mode 100644 index 0000000..4bc66d4 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/actualbudget/.felhom.yml @@ -0,0 +1,65 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/actualbudget/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "ActualBudget" +description: "Személyes pénzügyek és költségvetés kezelése" +category: "finance" +subdomain: "budget" + +# --- Asset slug --- +slug: "actualbudget" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "budget" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: 'Költségvetés tervező - pénzügyeid kézben tartása egyszerűen' + docs_url: 'https://actualbudget.org/docs/' + + use_cases: + - 'Havi költségvetés tervezése és követése envelope módszerrel' + - 'Bankszámla tranzakciók importálása és kategorizálása' + - 'Riportok és grafikonok a kiadásokról és bevételekről' + - 'Több felhasználó - közös háztartási pénzügyek kezelése' + - 'Offline is működik, szinkronizálás a szerver és eszközök között' + + first_steps: + - 'Nyisd meg a budget.DOMAIN címet a böngészőben' + - 'Hozz létre egy jelszót az első megnyitáskor' + - 'Hozd létre a költségvetési kategóriákat (pl. élelmiszer, közlekedés)' + - 'Adj hozzá számlákat és kezdd el rögzíteni a tranzakciókat' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 5006 diff --git a/controller/internal/stacks/testdata/catalog/adventurelog/.felhom.yml b/controller/internal/stacks/testdata/catalog/adventurelog/.felhom.yml new file mode 100644 index 0000000..ed5bcb6 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/adventurelog/.felhom.yml @@ -0,0 +1,81 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "AdventureLog" +description: "Utazási napló és kalandtervező" +category: "travel" +subdomain: "travel" +slug: "adventurelog" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "travel" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + # Data-encrypting key: the app secures stored data with it, so it must NOT be regenerated on + # restore (that would render restored data unreadable). The recovery unit stays secret-free; at + # restore the controller recovers this key from the guest's own app.yaml (live, or via the PBS + # whole-guest snapshot) and FAILS CLOSED (refuse + warn) if it cannot — never silently restores. + data_key: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Utazási napló - utazások dokumentálása térképpel és fotókkal" + docs_url: "https://adventurelog.app/docs/" + + use_cases: + - 'Utazások dokumentálása képekkel és jegyzetekkel' + - 'Térképes megjelenítés a meglátogatott helyekről' + - 'Úti tervek készítése és szervezése' + - 'Statisztikák - meglátogatott országok, városok' + - 'Kalandok megosztása családdal és barátokkal' + + first_steps: + - 'Nyisd meg a travel.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Add hozzá az első utazásodat' + - 'Jelöld meg a meglátogatott helyeket a térképen' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8000 + path: "/api/" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/audiobookshelf/.felhom.yml b/controller/internal/stacks/testdata/catalog/audiobookshelf/.felhom.yml new file mode 100644 index 0000000..608ac5c --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/audiobookshelf/.felhom.yml @@ -0,0 +1,86 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Audiobookshelf" +description: "Hangoskönyv és podcast kezelő szerver" +category: "media" +subdomain: "audiobooks" +slug: "audiobookshelf" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/audiobooks + class: optional # PENDING-VETO: spike chose excluded; ruled optional for consistency + # with komga/romm (curated, often personally ripped — precious). + # If Viktor rules excluded, flip THIS LINE ONLY before committing. + - path: media/podcasts + class: excluded # re-downloadable by definition + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "audiobooks" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Hangoskönyv tár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Hangoskönyv és podcast kezelő, lejátszó és szinkronizáló" + docs_url: "https://www.audiobookshelf.org/docs" + + use_cases: + - 'Hangoskönyvek és podcastok rendszerezése és lejátszása' + - 'Automatikus metaadat letöltés - borítók, leírások, fejezetek' + - 'Folytatás ott, ahol abbahagytad (szinkron eszközök között)' + - 'Podcast feliratkozás és automatikus letöltés' + - 'Dedikált mobil alkalmazások (Android, iOS)' + + first_steps: + - 'Nyisd meg az audiobooks.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot az első megnyitáskor' + - 'Add hozzá a könyvtárakat (/audiobooks és/vagy /podcasts)' + - 'Várd meg az automatikus szkennelést' + - 'Telepítsd az Audiobookshelf alkalmazást a telefonodra' + + prerequisites: + - 'Külső HDD szükséges a hangoskönyvek tárolásához' + - 'Hangoskönyvek mappákba rendezve (pl. Szerző/Könyv/)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 80 + path: "/healthcheck" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/bentopdf/.felhom.yml b/controller/internal/stacks/testdata/catalog/bentopdf/.felhom.yml new file mode 100644 index 0000000..3bc889a --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/bentopdf/.felhom.yml @@ -0,0 +1,60 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "BentoPDF" +description: "Adatvédelmi fókuszú PDF eszköztár" +category: "tools" +subdomain: "pdf" +slug: "bentopdf" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-12" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "pdf" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Adatvédelmi fókuszú PDF eszköztár - a fájlok soha nem hagyják el a szervered" + docs_url: "https://www.bentopdf.com/docs/" + + use_cases: + - 'PDF összefűzés, szétválasztás és forgatás' + - 'PDF konvertálás képekké és képekből' + - 'Jelszóvédelem és titkosítás' + - 'Vízjel hozzáadása dokumentumokhoz' + - 'Minden feldolgozás a szerveren - a fájlok nem kerülnek külső szolgáltatóhoz' + + first_steps: + - 'Nyisd meg a pdf.DOMAIN címet a böngészőben' + - 'Válaszd ki a kívánt műveletet' + - 'Töltsd fel a PDF fájlt és kattints a feldolgozásra' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 diff --git a/controller/internal/stacks/testdata/catalog/bookstack/.felhom.yml b/controller/internal/stacks/testdata/catalog/bookstack/.felhom.yml new file mode 100644 index 0000000..e0d4439 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/bookstack/.felhom.yml @@ -0,0 +1,75 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "BookStack" +description: "Egyszerű, könyv-szerű wiki és dokumentáció platform" +category: "productivity" +subdomain: "wiki" +slug: "bookstack" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "150M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "wiki" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: APP_KEY + label: "Alkalmazás kulcs" + type: secret + generate: "base64key:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Egyszerű wiki platform - polcok, könyvek, fejezetek és oldalak" + default_creds: "admin@admin.com / password" + docs_url: "https://www.bookstackapp.com/docs/" + + use_cases: + - 'Családi tudásbázis - receptek, kézikönyvek, szabályok' + - 'Projekt dokumentáció strukturált formában' + - 'Könyv > Fejezet > Oldal hierarchia az áttekinthetőségért' + - 'Teljes szöveges keresés és címkék' + - 'WYSIWYG és Markdown szerkesztő' + + first_steps: + - 'Nyisd meg a wiki.DOMAIN címet a böngészőben' + - 'Jelentkezz be: admin@admin.com / password' + - 'Változtasd meg azonnal az admin jelszót és email címet' + - 'Hozd létre az első polcot és könyvet' + - 'Kezdj el írni!' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 80 diff --git a/controller/internal/stacks/testdata/catalog/calcom/.felhom.yml b/controller/internal/stacks/testdata/catalog/calcom/.felhom.yml new file mode 100644 index 0000000..4c56fc8 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/calcom/.felhom.yml @@ -0,0 +1,101 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Cal.com" +description: "Nyílt forráskódú időpontfoglaló (Calendly alternatíva)" +category: "travel" +subdomain: "cal" +slug: "calcom" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "768M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "cal" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: NEXTAUTH_SECRET + label: "NextAuth titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: CALENDSO_ENCRYPTION_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:16" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Időpontfoglaló - Calendly alternatíva saját szerveren" + docs_url: "https://cal.com/docs/" + + use_cases: + - 'Időpontfoglalási oldal létrehozása ügyfeleknek' + - 'Naptár szinkronizáció (Google Calendar, Outlook)' + - 'Személyre szabható foglalási típusok és időtartamok' + - 'Automatikus emlékeztető emailek' + - 'Csapat naptár kezelés' + + first_steps: + - 'Nyisd meg a cal.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot' + - 'Állíts be egy foglalási típust (pl. 30 perces megbeszélés)' + - 'Szinkronizáld a naptáradat' + - 'Oszd meg a foglalási linket' + + prerequisites: + - 'x86 processzor szükséges' + - 'Legalább 1 GB szabad RAM (Cal.com + PostgreSQL)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3000 + # /api/health does not exist in cal.com v4.x (404); /api/auth/providers is a stable 200 once serving. + path: "/api/auth/providers" + expect: + status: 200 + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# Cal.com (Nodemailer) hard-codes TLS rejectUnauthorized=true with no skip flag and opportunistically +# STARTTLS-upgrades whenever the server offers it — so it cannot use the self-signed :2525 listener. +# tls_mode=plaintext points it at the :2526 listener, which does NOT advertise STARTTLS, so Cal.com never +# attempts TLS (plaintext on the single-tenant app bridge — accepted posture). secure is inferred from the +# port (≠465 → plaintext). EMAIL_SERVER_USER/PASSWORD stay unset (the shim accepts no-auth). +smtp_mapping: + tls_mode: plaintext + host_var: EMAIL_SERVER_HOST + port_var: EMAIL_SERVER_PORT + from_var: EMAIL_FROM + from_name_var: EMAIL_FROM_NAME + from_local: calcom diff --git a/controller/internal/stacks/testdata/catalog/calibre-web/.felhom.yml b/controller/internal/stacks/testdata/catalog/calibre-web/.felhom.yml new file mode 100644 index 0000000..3aeac3f --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/calibre-web/.felhom.yml @@ -0,0 +1,94 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Calibre-Web Automated" +description: "Automatizált e-könyv könyvtár - konvertálás, metaadat kezelés és webes olvasó" +category: "media" +subdomain: "books" +slug: "calibre-web" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-12" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "768M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/books + class: mandatory # metadata.db lives INSIDE the tree — DB and books are one unit + import: + - path: calibre + class: excluded # ingest inbox, transient + +# --- Customer-facing folder annotation (R-75) --- +data_paths: + - path: calibre + root: import + role: import + label: "Beolvasandó e-könyvek" + - path: media/books + root: userdata + role: library + label: "E-könyvtár" + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "books" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "E-könyvtár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja, ahol a Calibre könyvtár található" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: 'Automatizált e-könyv könyvtár - könyvfeldolgozás, formátumkonverzió és OPDS feed' + default_creds: "admin / admin123" + docs_url: "https://github.com/crocodilestick/Calibre-Web-Automated/wiki" + + use_cases: + - 'Automatizált e-könyv feldolgozás - csak dobd be az ingest mappába' + - 'Automatikus formátumkonverzió (EPUB, MOBI, PDF, AZW3)' + - 'OPDS feed e-olvasókhoz (Kindle, KOReader, Moon+ Reader)' + - 'Webes olvasó böngészőben' + - 'KOReader szinkronizáció (olvasási pozíció, könyvjelzők)' + + first_steps: + - 'Nyisd meg a books.DOMAIN címet a böngészőben' + - 'Jelentkezz be: admin / admin123' + - 'Változtasd meg azonnal a jelszót' + - 'Dobj egy könyvet a Fájlkezelőben (FileBrowser) az import/calibre mappába — automatikusan feldolgozza és a media/books könyvtárba helyezi' + + prerequisites: + - 'Külső HDD szükséges az e-könyvek tárolásához' + - 'x86 processzor szükséges (a CWA tartalmazza a Calibre binárist)' + - 'Legalább 768 MB szabad RAM ajánlott' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8083 diff --git a/controller/internal/stacks/testdata/catalog/claper/.felhom.yml b/controller/internal/stacks/testdata/catalog/claper/.felhom.yml new file mode 100644 index 0000000..21ab10c --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/claper/.felhom.yml @@ -0,0 +1,73 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Claper" +description: "Interaktív prezentáció és közönség bevonás" +category: "productivity" +subdomain: "present" +slug: "claper" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "present" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY_BASE + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Interaktív prezentáció - szavazás, kérdések és reakciók élőben" + docs_url: "https://docs.claper.co/" + + use_cases: + - 'Interaktív prezentációk élő szavazásokkal' + - 'Közönség kérdéseinek gyűjtése és megjelenítése' + - 'Reakciók és visszajelzések valós időben' + - 'PDF prezentációk feltöltése és megosztása' + - 'QR kód a közönség gyors csatlakozásához' + + first_steps: + - 'Nyisd meg a present.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Tölts fel egy PDF prezentációt' + - 'Oszd meg a kódot a közönséggel' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 4000 diff --git a/controller/internal/stacks/testdata/catalog/code-server/.felhom.yml b/controller/internal/stacks/testdata/catalog/code-server/.felhom.yml new file mode 100644 index 0000000..c2942f6 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/code-server/.felhom.yml @@ -0,0 +1,75 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Code-Server" +description: "VS Code a böngészőben - kódolás bárhonnan" +category: "dev" +subdomain: "code" +slug: "code-server" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "1024M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "code" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: PASSWORD + label: "Hozzáférési jelszó" + type: password + generate: "password:16" + description: "Bejelentkezési jelszó a szerkesztőhöz." + locked_after_deploy: false + +# --- App info (info page content) --- +app_info: + tagline: "VS Code a böngészőben - teljes fejlesztői környezet bárhonnan" + default_creds: "(telepítéskor generált jelszó)" + docs_url: "https://coder.com/docs/code-server/" + + use_cases: + - 'Kódolás bárhonnan, bármilyen eszközről böngészőben' + - 'VS Code teljes funkcionalitás - bővítmények, terminál, debug' + - 'Távoli fejlesztés alacsony sávszélességű kapcsolaton is' + - 'Egységes fejlesztői környezet több eszközről' + - 'Tableten és iPaden is használható' + + first_steps: + - 'Nyisd meg a code.DOMAIN címet a böngészőben' + - 'Add meg a hozzáférési jelszót' + - 'Nyisd meg a terminált (Ctrl+`) és telepíts eszközöket' + - 'Telepíts bővítményeket az Extensions panelben' + + prerequisites: + - 'x86 processzor szükséges a legjobb teljesítményhez' + - 'Legalább 1 GB szabad RAM ajánlott' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8443 + path: "/healthz" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/crafty-controller/.felhom.yml b/controller/internal/stacks/testdata/catalog/crafty-controller/.felhom.yml new file mode 100644 index 0000000..e7583f5 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/crafty-controller/.felhom.yml @@ -0,0 +1,86 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Crafty Controller" +description: "Minecraft szerver kezelő webes felülettel" +category: "games" +subdomain: "minecraft" +slug: "crafty-controller" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-06-26" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "256M" + mem_limit: "2048M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "minecraft" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: CRAFTY_PASSWORD + label: "Admin jelszó" + type: password + generate: password:24 + locked_after_deploy: true + description: "A Crafty admin felhasználó (admin) kezdeti jelszava — ezzel tudsz belépni a kezelőfelületre" + +# --- App info (info page content) --- +app_info: + tagline: "Minecraft szerver kezelő - hozd létre a saját világodat" + docs_url: "https://docs.craftycontrol.com/" + + use_cases: + - 'Minecraft szerver létrehozása és kezelése webes felületen' + - 'Több szerver párhuzamos futtatása (Java és Bedrock)' + - 'Automatikus mentések és ütemezett újraindítás' + - 'Játékos menedzsment és konzol hozzáférés' + - 'Szerver fájlok kezelése böngészőből' + + first_steps: + - 'Nyisd meg a minecraft.DOMAIN címet a böngészőben' + - 'A kezdeti belépési adatokat ezen az oldalon (Kezdeti belépési adatok) találod' + - 'Hozz létre egy új Minecraft szervert a varázslóval' + - 'Állítsd be a szerver beállításokat (játékmód, nehézség, stb.)' + - 'Állítsd be a szerver portját a 25565–25575 tartományon belül (server.properties → server-port); az első szerver a 25565-öt használja, a továbbiak 25566–25575-öt' + - 'Helyi hálózaton csatlakozz a szerver helyi IP-címére és portjára (pl. 192.168.x.x:25565) — a Minecraft kliens „helyi hálózat keresése” nem listázza automatikusan, add meg kézzel a címet' + - 'Interneten keresztüli eléréshez port-továbbítás szükséges a választott port(ok)ra — egyeztess az üzemeltetővel' + + prerequisites: + - 'Legalább 2 GB szabad RAM ajánlott (Minecraft szerver + Crafty)' + - 'x86 processzor szükséges (nem fut Raspberry Pi-n)' + - 'A Minecraft szerverek a 25565–25575 porttartományt használhatják (egyszerre legfeljebb 11 szerver)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: tcp + port: 8443 + +# --- Initial login (felhom-seeded) --- +# The admin password is seeded into default.json by the compose entrypoint (CRAFTY_PASSWORD deploy +# field). The controller reads it back from default.json and shows it on the app page under "Kezdeti +# belépési adatok" — same value the customer set at deploy time. +initial_credentials: + file: /crafty/app/config/default.json + format: json + username_key: username + password_key: password + note: "Kezdeti admin jelszó (a telepítéskor beállított). Az első belépés után változtasd meg a Crafty felületén — ez a kártya továbbra is a kezdeti jelszót mutatja." diff --git a/controller/internal/stacks/testdata/catalog/docmost/.felhom.yml b/controller/internal/stacks/testdata/catalog/docmost/.felhom.yml new file mode 100644 index 0000000..bced530 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/docmost/.felhom.yml @@ -0,0 +1,81 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/docmost/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Docmost" +description: "Modern wiki és dokumentáció platform (Notion-szerű)" +category: "productivity" +subdomain: "docs" + +# --- Asset slug --- +slug: "docmost" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "768M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "docs" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: APP_SECRET + label: "Alkalmazás titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: 'Modern wiki és dokumentáció platform Notion-szerű szerkesztővel' + docs_url: 'https://docmost.com/docs/' + + use_cases: + - 'Csapat tudásbázis és belső dokumentáció' + - 'Notion-szerű blokk szerkesztő valós idejű együttműködéssel' + - 'Terek és oldalak hierarchikus szervezése' + - 'Képek, táblázatok és kód blokkok beágyazása' + - 'Keresés a teljes dokumentációban' + + first_steps: + - 'Nyisd meg a docs.DOMAIN címet a böngészőben' + - 'Az első regisztrált felhasználó automatikusan admin lesz' + - 'Hozd létre az első teret (Space) a dokumentumoknak' + - 'Kezdj el írni a Notion-szerű szerkesztőben' + + prerequisites: + - 'x86 processzor szükséges' + - 'Legalább 768 MB szabad RAM (Docmost + PostgreSQL + Redis)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3000 diff --git a/controller/internal/stacks/testdata/catalog/emby/.felhom.yml b/controller/internal/stacks/testdata/catalog/emby/.felhom.yml new file mode 100644 index 0000000..6342cff --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/emby/.felhom.yml @@ -0,0 +1,83 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Emby" +description: "Személyes média szerver élő TV és DVR támogatással" +category: "media" +subdomain: "emby" +slug: "emby" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "2048M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media + class: excluded # pure reader of the shared tree (:ro); state lives in volumes + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "emby" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Médiatár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Személyes média szerver élő TV és DVR támogatással" + docs_url: "https://emby.media/support/articles/Home.html" + + use_cases: + - 'Filmek, sorozatok, zene és fotók kezelése egy helyen' + - 'Élő TV nézés és felvétel (DVR) TV tunerrel' + - 'Automatikus transzkódolás bármilyen eszközhöz' + - 'Szülői felügyelet és felhasználói profilok' + - 'Szinkronizálás offline megtekintéshez mobilon' + + first_steps: + - 'Nyisd meg az emby.DOMAIN címet a böngészőben' + - 'Kövesd a beállítás varázslót' + - 'Hozd létre az admin fiókot' + - 'Add hozzá a médiatárat a /media mappából' + - 'Telepítsd az Emby alkalmazást eszközeidre' + + prerequisites: + - 'Külső HDD szükséges a médiafájlok tárolásához' + - 'Legalább 2 GB szabad RAM ajánlott' + - 'Emby Premiere előfizetés a prémium funkciókhoz (opcionális)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8096 + path: "/emby/system/ping" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/ghost/.felhom.yml b/controller/internal/stacks/testdata/catalog/ghost/.felhom.yml new file mode 100644 index 0000000..30179df --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/ghost/.felhom.yml @@ -0,0 +1,65 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Ghost" +description: "Professzionális blog és hírlevél platform" +category: "productivity" +subdomain: "blog" +slug: "ghost" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" +# Bare "/" is the public blog; the admin panel / setup wizard lives at /ghost/. +open_path: "/ghost/" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "150M" + mem_limit: "512M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "blog" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Professzionális blog és hírlevél platform" + docs_url: "https://ghost.org/docs/" + + use_cases: + - 'Professzionális blog indítása és üzemeltetése' + - 'Hírlevél küldés beépített email funkcióval' + - 'SEO-optimalizált tartalom publikálás' + - 'Tagság és fizetős tartalom kezelés' + - 'Markdown és vizuális szerkesztő' + + first_steps: + - 'Nyisd meg a blog.DOMAIN/ghost/ címet a böngészőben' + - 'Hozd létre az admin fiókot a beállítás varázslóban' + - 'Írd meg és publikáld az első posztodat' + - 'Személyre szabd a témát és beállításokat' + + prerequisites: + - 'x86 processzor szükséges' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 2368 diff --git a/controller/internal/stacks/testdata/catalog/gitea/.felhom.yml b/controller/internal/stacks/testdata/catalog/gitea/.felhom.yml new file mode 100644 index 0000000..a6cf68f --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/gitea/.felhom.yml @@ -0,0 +1,81 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Gitea" +description: "Könnyű, saját Git szerver webes felülettel" +category: "dev" +subdomain: "git" +slug: "gitea" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "git" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Saját Git szerver - GitHub-szerű felület, privát repók" + docs_url: "https://docs.gitea.com/" + + use_cases: + - 'Privát Git repozitóriók a saját szerveren' + - 'GitHub-szerű felület: pull request, issue, wiki' + - 'CI/CD pipeline-ok (Gitea Actions - GitHub Actions kompatibilis)' + - 'Szervezetek és csapatok kezelése' + - 'Container registry és csomagkezelő' + + first_steps: + - 'Nyisd meg a git.DOMAIN címet a böngészőben' + - 'Kövesd az első beállítás varázslót' + - 'Hozd létre az admin fiókot' + - 'Hozd létre az első repozitóriót' + - 'Klónozd a repót: git clone https://git.DOMAIN/user/repo.git' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3000 + # /api/healthz is always available (200) once serving; /api/v1/version 404s pre-install-lock. + path: "/api/healthz" + expect: + status: 200 + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# Gitea uses STARTTLS to the shim and trusts its self-signed cert via FORCE_TRUST_SERVER_CERT. +# Gitea applies GITEA__* env on every boot, so toggling app-email + redeploy takes effect. The From +# is Gitea's single GITEA__mailer__FROM; USER/PASSWD stay unset (the shim accepts no-auth). +smtp_mapping: + host_var: GITEA__mailer__SMTP_ADDR + port_var: GITEA__mailer__SMTP_PORT + security_var: GITEA__mailer__PROTOCOL + security_value: "smtp+starttls" + from_var: GITEA__mailer__FROM + from_local: gitea + extra: + GITEA__mailer__ENABLED: "true" + GITEA__mailer__FORCE_TRUST_SERVER_CERT: "true" diff --git a/controller/internal/stacks/testdata/catalog/glance/.felhom.yml b/controller/internal/stacks/testdata/catalog/glance/.felhom.yml new file mode 100644 index 0000000..d6b75ff --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/glance/.felhom.yml @@ -0,0 +1,61 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Glance" +description: "Minimalista információs dashboard" +category: "dashboard" +subdomain: "dashboard" +slug: "glance" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "20M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "dashboard" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Minimalista dashboard - RSS, időjárás, könyvjelzők egy oldalon" + docs_url: "https://github.com/glanceapp/glance/blob/main/docs/configuration.md" + + use_cases: + - 'RSS hírek, YouTube, Reddit és egyéb tartalmak egy oldalon' + - 'Időjárás és rendszer monitorozás widgetekkel' + - 'Könyvjelzők és kedvenc oldalak rendszerezése' + - 'Ultragyors, minimális erőforrás igény' + - 'Személyre szabható elrendezés YAML konfigurációval' + + first_steps: + - 'Nyisd meg a dashboard.DOMAIN címet a böngészőben' + - 'Szerkeszd a glance.yml konfigurációs fájlt a kötetben' + - 'Add hozzá az RSS feedeket, widgeteket' + - 'Személyre szabd az elrendezést' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 diff --git a/controller/internal/stacks/testdata/catalog/gokapi/.felhom.yml b/controller/internal/stacks/testdata/catalog/gokapi/.felhom.yml new file mode 100644 index 0000000..5e40d76 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/gokapi/.felhom.yml @@ -0,0 +1,72 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Gokapi" +description: "Ideiglenes fájlmegosztás lejáró linkekkel" +category: "files" +subdomain: "share" +slug: "gokapi" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" +# Gokapi's bare "/" redirects away (file-share index); the admin/login UI is at /admin. +open_path: "/admin" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "share" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: GOKAPI_PASSWORD + label: "Admin jelszó" + type: password + generate: "password:24" + locked_after_deploy: true + description: "A Gokapi admin felhasználó (admin) jelszava — ezzel tudsz belépni a kezelőfelületre" + +# --- App info (info page content) --- +app_info: + tagline: "Ideiglenes fájlmegosztás lejáró linkekkel" + docs_url: "https://gokapi.readthedocs.io/" + default_creds: "Felhasználó: admin · A jelszó a Beállítások oldalon látható (telepítéskor generált kezdeti jelszó)" + + use_cases: + - 'Fájlok biztonságos megosztása lejáró linkekkel' + - 'Beállítható lejárati idő és letöltési limit' + - 'Jelszóvédett letöltési linkek' + - 'API támogatás automatizált feltöltéshez' + - 'Titkosított tárolás és E2E titkosítás opció' + + first_steps: + - 'Nyisd meg a share.DOMAIN címet a böngészőben' + - 'Lépj be: felhasználó "admin", a jelszó a Beállítások oldalon található' + - 'Tölts fel egy fájlt és generálj megosztási linket' + - 'Állíts be lejárati időt és letöltési limitet a linkhez' + - 'Oszd meg a linket - automatikusan lejár a beállított idő után' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 53842 diff --git a/controller/internal/stacks/testdata/catalog/grafana/.felhom.yml b/controller/internal/stacks/testdata/catalog/grafana/.felhom.yml new file mode 100644 index 0000000..3cea507 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/grafana/.felhom.yml @@ -0,0 +1,74 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Grafana" +description: "Professzionális monitoring és vizualizációs platform" +category: "dashboard" +subdomain: "grafana" +slug: "grafana" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "grafana" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: GF_SECURITY_ADMIN_PASSWORD + label: "Admin jelszó" + type: password + generate: "password:16" + description: "Első bejelentkezéshez. Utána a webes felületen módosítható." + locked_after_deploy: false + +# --- App info (info page content) --- +app_info: + tagline: "Vizualizációs platform - dashboardok, grafikonok, alertek" + default_creds: "admin / (telepítéskor megadott jelszó)" + docs_url: "https://grafana.com/docs/grafana/latest/" + + use_cases: + - 'Rendszer metrikák vizualizálása (CPU, RAM, hálózat, lemez)' + - 'Egyedi dashboardok létrehozása különböző adatforrásokból' + - 'Alertek és értesítések küszöbértékek alapján' + - 'Prometheus, InfluxDB és sok más adatforrás támogatás' + - 'Szép, megosztható dashboardok' + + first_steps: + - 'Nyisd meg a grafana.DOMAIN címet a böngészőben' + - 'Jelentkezz be az admin fiókkal' + - 'Add hozzá az adatforrásokat (Connections > Data Sources)' + - 'Importálj egy dashboard sablont vagy hozz létre sajátot' + + prerequisites: + - 'Adatforrás szükséges (pl. Prometheus - külön telepítendő)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3000 + path: "/api/health" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/gramps-web/.felhom.yml b/controller/internal/stacks/testdata/catalog/gramps-web/.felhom.yml new file mode 100644 index 0000000..083e64e --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/gramps-web/.felhom.yml @@ -0,0 +1,67 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Gramps Web" +description: "Családfa készítő és genealógiai szoftver" +category: "home" +subdomain: "family" +slug: "gramps-web" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "384M" + mem_limit: "1024M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "family" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: GRAMPSWEB_SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Családfa készítő - genealógiai adatbázis webes felületen" + docs_url: "https://gramps-project.github.io/web/" + + use_cases: + - 'Családfa építése és vizualizálása' + - 'Személyek, események, helyszínek és kapcsolatok rögzítése' + - 'Fényképek és dokumentumok csatolása a családtagokhoz' + - 'GEDCOM import és export kompatibilitás' + - 'Családtagok közös hozzáférése a családfához' + + first_steps: + - 'Nyisd meg a family.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot' + - 'Importálj egy meglévő GEDCOM fájlt, vagy kezdd az üres családfát' + - 'Add hozzá az első családtagokat' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 5000 diff --git a/controller/internal/stacks/testdata/catalog/home-assistant/.felhom.yml b/controller/internal/stacks/testdata/catalog/home-assistant/.felhom.yml new file mode 100644 index 0000000..6cfda82 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/home-assistant/.felhom.yml @@ -0,0 +1,66 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Home Assistant" +description: "Nyílt forráskódú okos otthon központ" +category: "home" +subdomain: "ha" +slug: "home-assistant" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "256M" + mem_limit: "1024M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "ha" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Okos otthon központ - automatizálás és vezérlés egy helyről" + docs_url: "https://www.home-assistant.io/docs/" + + use_cases: + - 'Okos otthon eszközök központi kezelése (lámpa, termosztát, kamera)' + - 'Automatizálások létrehozása (pl. mozgásérzékelőre lámpa be)' + - '2000+ integráció (Zigbee, Z-Wave, Tuya, Shelly, stb.)' + - 'Dashboard a család számára mobilon és tableten' + - 'Energiafogyasztás monitorozás' + + first_steps: + - 'Nyisd meg a ha.DOMAIN címet a böngészőben' + - 'Hozd létre a tulajdonos fiókot az onboarding során' + - 'Fedezd fel az automatikusan felfedezett eszközöket' + - 'Telepíts integrációkat az okos otthon eszközeidhez' + - 'Hozd létre az első automatizálást' + + prerequisites: + - 'Okos otthon eszközök (nem kötelező, de ajánlott a telepítés előtt)' + - 'Zigbee/Z-Wave koordinátor USB eszközök átpasszolása nem támogatott Docker módban' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8123 + path: "/api/" diff --git a/controller/internal/stacks/testdata/catalog/homebox/.felhom.yml b/controller/internal/stacks/testdata/catalog/homebox/.felhom.yml new file mode 100644 index 0000000..557a8c9 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/homebox/.felhom.yml @@ -0,0 +1,78 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/homebox/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Homebox" +description: "Otthoni leltár és eszköznyilvántartás" +category: "tools" +subdomain: "inventory" + +# --- Asset slug --- +slug: "homebox" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-19" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: HBOX_AUTH_API_KEY_PEPPER + label: "API-kulcs pepper" + type: secret + generate: "base64key:48" + locked_after_deploy: true + # A Homebox 0.26+ pánikol induláskor, ha ez nincs beállítva (min. 32 bájt). + # Ez sózza az API-kulcsokat: ha újragenerálódik, MINDEN kiadott API-kulcs + # érvénytelenné válik, ezért visszaállításkor a régi értéket kell megtartani. + data_key: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "inventory" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: 'Otthoni leltár kezelő - tartsd számon a tárgyaidat' + docs_url: 'https://homebox.software/en/' + + use_cases: + - 'Otthoni tárgyak leltározása és nyilvántartása' + - 'Helyszínek, címkék és kategóriák szerinti rendszerezés' + - 'Garancia lejáratok és beszerzési árak nyilvántartása' + - 'Vonalkód szkennelés a gyors hozzáadáshoz' + - 'Biztosítási célú leltár készítés' + + first_steps: + - 'Nyisd meg az inventory.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat az első megnyitáskor' + - 'Add hozzá a helyszíneket (pl. nappali, konyha, garázs)' + - 'Kezdd el felvenni a tárgyakat fotókkal' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 7745 + path: "/api/v1/status" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/homepage/.felhom.yml b/controller/internal/stacks/testdata/catalog/homepage/.felhom.yml new file mode 100644 index 0000000..5da1dbb --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/homepage/.felhom.yml @@ -0,0 +1,61 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Homepage" +description: "Személyre szabható kezdőlap szolgáltatás widgetekkel" +category: "dashboard" +subdomain: "home" +slug: "homepage" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "home" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Személyes kezdőlap - szolgáltatás státuszok és widgetek egy helyen" + docs_url: "https://gethomepage.dev/" + + use_cases: + - 'Összes szolgáltatás egy áttekinthető oldalon' + - 'Szolgáltatás állapot monitoring widgetek' + - 'Docker integráció - automatikus szolgáltatás felfedezés' + - 'Könyvjelzők és kedvenc oldalak' + - 'Időjárás, rendszer erőforrás és egyéb widgetek' + + first_steps: + - 'Nyisd meg a home.DOMAIN címet a böngészőben' + - 'Szerkeszd a konfigurációs fájlokat a homepage_config kötetben' + - 'Add hozzá a szolgáltatásokat a services.yaml-ban' + - 'Állítsd be a widgeteket a widgets.yaml-ban' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3000 diff --git a/controller/internal/stacks/testdata/catalog/immich/.felhom.yml b/controller/internal/stacks/testdata/catalog/immich/.felhom.yml new file mode 100644 index 0000000..d7b2af2 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/immich/.felhom.yml @@ -0,0 +1,102 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/immich/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Immich" +description: "Fotók és videók kezelése (Google Photos alternatíva)" +category: "media" +subdomain: "photos" + +# --- Asset slug --- +slug: "immich" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "2048M" + mem_limit: "4096M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + hdd: + - path: appdata/immich + class: mandatory # managed upload library — DB-referenced (SQ3: restore-without = broken) + userdata: + - path: media/photos + class: optional # external library, :ro — precious but re-scanned (explicit overrides ro-default) + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "photos" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - 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" + description: "A külső merevlemez elérési útja, ahol a fotók és videók tárolódnak" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: 'Google Photos alternatíva - automatikus fotó mentés és rendszerezés' + docs_url: 'https://immich.app/docs/overview/introduction' + + use_cases: + - 'Fotók és videók automatikus mentése telefonról' + - 'Arc- és tárgyfelismerés gépi tanulással' + - 'Térképes megjelenítés helyszín alapján' + - 'Emlékek és visszatekintések automatikus generálása' + - 'Albumok létrehozása és megosztása családtagokkal' + + first_steps: + - 'Nyisd meg a photos.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot' + - 'Telepítsd az Immich alkalmazást a telefonodra (Android/iOS)' + - 'Állítsd be az automatikus feltöltést az alkalmazásban' + - 'Hívd meg a családtagokat külön fiókokkal' + # MOUNT-READY, REGISTRATION PENDING: a megosztott média/photos mappa be van csatolva + # /external/photos néven (csak olvasható), de az Immich CSAK akkor látja, ha az + # adminfelületen (Administration → External Libraries) regisztrálod a /external/photos + # útvonalat. Compose ezt NEM teszi meg automatikusan — ez egy telepítés utáni lépés. + - 'Külső könyvtár (opcionális): Administration → External Libraries → add /external/photos' + + prerequisites: + - 'Külső HDD szükséges a fotók és videók tárolásához' + - 'Legalább 4 GB szabad RAM ajánlott (gépi tanulás funkciókhoz)' + - 'x86 processzor szükséges (nem fut Raspberry Pi-n)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 2283 + path: "/api/server/ping" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/jellyfin/.felhom.yml b/controller/internal/stacks/testdata/catalog/jellyfin/.felhom.yml new file mode 100644 index 0000000..04170cb --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/jellyfin/.felhom.yml @@ -0,0 +1,83 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Jellyfin" +description: "Ingyenes és nyílt forráskódú média szerver" +category: "media" +subdomain: "media" +slug: "jellyfin" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "2048M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media + class: excluded # pure reader of the shared tree (:ro); state lives in volumes + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "media" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Médiatár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja, ahol a filmek, sorozatok és zenék tárolódnak" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Ingyenes média szerver - filmek, sorozatok és zene streamelése" + docs_url: "https://jellyfin.org/docs/" + + use_cases: + - 'Filmek, sorozatok és zenék streamelése bármilyen eszközre' + - 'Automatikus metaadat letöltés - borítók, leírások, értékelések' + - 'Élő TV és DVR funkció TV tunerrel' + - 'Több felhasználó külön profillal és szülői felügyelettel' + - 'Mobil és TV alkalmazások (Android, iOS, Roku, Fire TV, stb.)' + + first_steps: + - 'Nyisd meg a media.DOMAIN címet a böngészőben' + - 'Kövesd a beállítás varázslót - válaszd a magyar nyelvet' + - 'Hozd létre az admin fiókot' + - 'Add hozzá a médiatárat (filmek: /media/movies, sorozatok: /media/shows)' + - 'Telepítsd a Jellyfin alkalmazást a telefonodra vagy TV-dre' + + prerequisites: + - 'Külső HDD szükséges a médiafájlok tárolásához' + - 'Legalább 2 GB szabad RAM ajánlott' + - 'Filmek és sorozatok mappákba rendezve (pl. /media/movies/, /media/shows/)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8096 + path: "/health" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/kimai/.felhom.yml b/controller/internal/stacks/testdata/catalog/kimai/.felhom.yml new file mode 100644 index 0000000..c6f059f --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/kimai/.felhom.yml @@ -0,0 +1,80 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Kimai" +description: "Időkövetés és projektmenedzsment" +category: "productivity" +subdomain: "time" +slug: "kimai" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "time" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: ADMIN_EMAIL + label: "Admin email" + type: text + default: "admin@example.com" + + - env_var: ADMIN_PASSWORD + label: "Admin jelszó" + type: password + generate: "password:16" + description: "Első bejelentkezéshez. Utána a webes felületen módosítható." + locked_after_deploy: false + +# --- App info (info page content) --- +app_info: + tagline: "Időkövetés - projektek, ügyfelek és munkaidő nyilvántartás" + default_creds: "(telepítéskor megadott admin email és jelszó)" + docs_url: "https://www.kimai.org/documentation/" + + use_cases: + - 'Munkaidő nyilvántartás projektekre és ügyfelekre bontva' + - 'Stopper-szerű időmérés egy kattintással' + - 'Riportok és exportálás (PDF, CSV, Excel)' + - 'Költségek és számlázás nyilvántartás' + - 'Több felhasználó csapat szintű jogosultságokkal' + + first_steps: + - 'Nyisd meg a time.DOMAIN címet a böngészőben' + - 'Jelentkezz be a telepítéskor megadott admin adatokkal' + - 'Hozd létre az első ügyfelet és projektet' + - 'Indítsd el az időmérést' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8001 diff --git a/controller/internal/stacks/testdata/catalog/komga/.felhom.yml b/controller/internal/stacks/testdata/catalog/komga/.felhom.yml new file mode 100644 index 0000000..efa2556 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/komga/.felhom.yml @@ -0,0 +1,83 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Komga" +description: "Képregény és manga szerver OPDS támogatással" +category: "media" +subdomain: "comics" +slug: "komga" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/comics + class: optional # ruled: precious-decoupled (re-scanned, hand-curated) + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "comics" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Képregénytár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Képregény, manga és könyv szerver webes olvasóval" + docs_url: "https://komga.org/docs/" + + use_cases: + - 'Képregények és mangák rendszerezése és olvasása böngészőben' + - 'Automatikus metaadat szkennelés és borítókép generálás' + - 'OPDS feed kompatibilis olvasó alkalmazásokhoz' + - 'Olvasási haladás szinkronizálás eszközök között' + - 'Több felhasználó külön könyvtárral' + + first_steps: + - 'Nyisd meg a comics.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot az első megnyitáskor' + - 'Add hozzá a könyvtárat (/data)' + - 'Várd meg az automatikus szkennelést' + - 'Használj OPDS-kompatibilis alkalmazást mobilon (pl. Tachiyomi)' + + prerequisites: + - 'Külső HDD szükséges a képregények tárolásához' + - 'Támogatott formátumok: CBZ, CBR, PDF, EPUB' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 25600 + # /actuator/health is unauthenticated (200); the /api/v1 prefix is auth-gated (401). + path: "/actuator/health" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/mealie/.felhom.yml b/controller/internal/stacks/testdata/catalog/mealie/.felhom.yml new file mode 100644 index 0000000..fa83d52 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/mealie/.felhom.yml @@ -0,0 +1,82 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/mealie/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Mealie" +description: "Receptkezelő és étkezéstervező" +category: "productivity" +subdomain: "recipes" + +# --- Asset slug --- +slug: "mealie" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "1000M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "recipes" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: 'Receptkezelő és étkezés tervező a családnak' + default_creds: 'changeme@example.com / MyPassword' + docs_url: 'https://docs.mealie.io/' + + use_cases: + - 'Receptek gyűjtése és rendszerezése kategóriák és címkék szerint' + - 'Receptek importálása weboldalakról automatikusan' + - 'Heti étkezés tervezés és bevásárlólista generálás' + - 'Receptek megosztása családtagokkal és barátokkal' + - 'Többnyelvű felület - magyar is elérhető' + + first_steps: + - 'Nyisd meg a mealie.DOMAIN címet a böngészőben' + - 'Jelentkezz be: changeme@example.com / MyPassword' + - 'Változtasd meg azonnal az email címet és jelszót' + - 'Importáld az első receptet egy weboldal URL beillesztésével' + - 'Próbáld ki az étkezés tervezőt' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: tcp + port: 9000 + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# When app-email is on (global toggle + this app's per-app toggle), the controller injects +# host = the on-box shim, port = 2525, From = mealie@felhom.eu. Mealie has NO accept-invalid- +# cert option, so it talks PLAINTEXT (SMTP_AUTH_STRATEGY=NONE) to the shim on 2525 — the +# spike-validated mode (SPIKE-smtp-app-relay-2026-06-28 §7). SMTP_USER/SMTP_PASSWORD stay +# unset (no auth needed; the shim holds no Resend key). +smtp_mapping: + host_var: SMTP_HOST + port_var: SMTP_PORT + security_var: SMTP_AUTH_STRATEGY + security_value: "NONE" + from_var: SMTP_FROM_EMAIL + from_name_var: SMTP_FROM_NAME + from_local: mealie diff --git a/controller/internal/stacks/testdata/catalog/n8n/.felhom.yml b/controller/internal/stacks/testdata/catalog/n8n/.felhom.yml new file mode 100644 index 0000000..6e117f9 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/n8n/.felhom.yml @@ -0,0 +1,72 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "n8n" +description: "Workflow automatizálás vizuális szerkesztővel" +category: "productivity" +subdomain: "auto" +slug: "n8n" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "1536M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "auto" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: N8N_ENCRYPTION_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:16" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Vizuális workflow automatizálás - Zapier/IFTTT alternatíva" + docs_url: "https://docs.n8n.io/" + + use_cases: + - 'Alkalmazások összekapcsolása vizuális workflow szerkesztővel' + - 'Automatizálás: email, Telegram, webhook, fájl feldolgozás' + - '400+ integráció (Google, Slack, Notion, GitHub, stb.)' + - 'Időzített feladatok (cron) és webhook triggerek' + - 'AI munkafolyamatok LLM integrációval' + + first_steps: + - 'Nyisd meg az auto.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot' + - 'Próbálj ki egy sablon workflow-t a Template galériából' + - 'Hozd létre az első saját munkafolyamatot' + + prerequisites: + - 'x86 processzor szükséges' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 5678 + path: "/healthz" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/navidrome/.felhom.yml b/controller/internal/stacks/testdata/catalog/navidrome/.felhom.yml new file mode 100644 index 0000000..627a9cb --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/navidrome/.felhom.yml @@ -0,0 +1,82 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Navidrome" +description: "Könnyű zene szerver Subsonic API támogatással" +category: "media" +subdomain: "music" +slug: "navidrome" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/music + class: excluded # re-rippable bulk, :ro, shared tree + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "music" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Zenegyűjtemény útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja, ahol a zenefájlok tárolódnak" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Könnyű zene szerver - streameld a saját zenegyűjteményedet" + docs_url: "https://www.navidrome.org/docs/" + + use_cases: + - 'Saját zenegyűjtemény streamelése bármilyen eszközről' + - 'Subsonic API kompatibilis mobil alkalmazások használata' + - 'Lejátszási listák, kedvencek és értékelések kezelése' + - 'Automatikus metaadat és borítókép letöltés' + - 'Több felhasználó külön könyvtárral és beállításokkal' + + first_steps: + - 'Nyisd meg a music.DOMAIN címet a böngészőben' + - 'Az első felhasználó automatikusan admin lesz' + - 'Hozd létre a fiókodat és várd meg az első szkennelést' + - 'Telepíts Subsonic-kompatibilis alkalmazást (pl. Subtracks, play:Sub)' + - 'Add meg a szerver címet: https://music.DOMAIN' + + prerequisites: + - 'Külső HDD szükséges a zenefájlok tárolásához' + - 'Zenefájlok mappákba rendezve (pl. Előadó/Album/szám.flac)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 4533 + path: "/ping" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/nextcloud/.felhom.yml b/controller/internal/stacks/testdata/catalog/nextcloud/.felhom.yml new file mode 100644 index 0000000..3462650 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/nextcloud/.felhom.yml @@ -0,0 +1,125 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Nextcloud" +description: "Saját felhő tárhely - Google Drive/Dropbox alternatíva" +category: "files" +subdomain: "cloud" +slug: "nextcloud" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "256M" + mem_limit: "1024M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + hdd: + - path: appdata/nextcloud + class: mandatory # THE user files — oc_filecache/shares reference them + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "cloud" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: MYSQL_ROOT_PASSWORD + label: "MariaDB root jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: NEXTCLOUD_ADMIN_USER + label: "Admin felhasználónév" + type: text + default: "admin" + locked_after_deploy: true + + - env_var: NEXTCLOUD_ADMIN_PASSWORD + label: "Admin jelszó" + type: password + generate: "password:16" + description: "Első bejelentkezéshez. Utána a webes felületen módosítható." + locked_after_deploy: false + + - env_var: HDD_PATH + label: "Adattárolási útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "A saját Google Drive-od - fájlok, naptár, névjegyek egy helyen" + default_creds: "Az admin fiók adatait a telepítéskor adod meg" + docs_url: "https://docs.nextcloud.com/server/latest/user_manual/" + + use_cases: + - 'Fájlok szinkronizálása és megosztása eszközök között' + - 'Naptár és névjegyek szinkronizálás (CalDAV/CardDAV)' + - 'Dokumentumok közös szerkesztése (OnlyOffice integrációval)' + - 'Fotó és videó automatikus feltöltés telefonról' + - 'Alkalmazásbolt - kibővíthető funkciók (Notes, Tasks, Forms, stb.)' + + first_steps: + - 'Nyisd meg a cloud.DOMAIN címet a böngészőben' + - 'Jelentkezz be a telepítéskor megadott admin fiókkal' + - 'Telepítsd a Nextcloud asztali alkalmazást a számítógépedre' + - 'Telepítsd a Nextcloud mobil alkalmazást a telefonodra' + - 'Hívd meg a családtagokat felhasználói fiókok létrehozásával' + + prerequisites: + - 'Külső HDD szükséges a fájlok tárolásához' + - 'Legalább 1 GB szabad RAM (Nextcloud + MariaDB + Redis)' + - 'x86 processzor ajánlott a legjobb teljesítményhez' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 80 + path: "/status.php" + expect: + status: 200 + body_contains: "installed" + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# Nextcloud (Symfony Mailer) has no env to skip TLS cert verification and opportunistically STARTTLS-upgrades, +# so it can't use the self-signed :2525 listener → tls_mode=plaintext points it at :2526 (STARTTLS NOT +# advertised → no upgrade attempted; plaintext on the single-tenant app bridge — accepted posture). +# Nextcloud SPLITS the From into local-part + domain, so from_var=MAIL_FROM_ADDRESS (local) + +# from_domain_var=MAIL_DOMAIN → nextcloud@felhom.eu. No security_var (SMTP_SECURE stays empty = no TLS); +# SMTP_NAME/PASSWORD unset (no auth). The official image reads these via getenv() on every boot. +smtp_mapping: + tls_mode: plaintext + host_var: SMTP_HOST + port_var: SMTP_PORT + from_var: MAIL_FROM_ADDRESS + from_domain_var: MAIL_DOMAIN + from_local: nextcloud diff --git a/controller/internal/stacks/testdata/catalog/onlyoffice/.felhom.yml b/controller/internal/stacks/testdata/catalog/onlyoffice/.felhom.yml new file mode 100644 index 0000000..3e00a4b --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/onlyoffice/.felhom.yml @@ -0,0 +1,84 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "OnlyOffice" +description: "Teljes értékű irodai csomag a böngészőben" +category: "productivity" +subdomain: "office" +slug: "onlyoffice" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "2048M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "office" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: JWT_SECRET + label: "JWT titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Teljes értékű irodai csomag - Word, Excel, PowerPoint a böngészőben" + docs_url: "https://helpcenter.onlyoffice.com/" + + use_cases: + - 'Dokumentumok, táblázatok és prezentációk szerkesztése böngészőben' + - 'Valós idejű közös szerkesztés több felhasználóval' + - 'Microsoft Office formátumok teljes kompatibilitása (docx, xlsx, pptx)' + - 'Nextcloud és egyéb felhő tárhely integrációk' + - 'PDF konvertálás és kitöltés' + + first_steps: + - 'Nyisd meg az office.DOMAIN címet a böngészőben' + - 'Várj amíg az inicializálás befejeződik (1-2 perc)' + - 'A Document Server önmagában API-ként működik' + - 'Integráld Nextcloud-dal vagy más alkalmazással a JWT tokennel' + - 'Nextcloudban: telepítsd az OnlyOffice alkalmazást és add meg az URL-t' + + prerequisites: + - 'Legalább 2 GB szabad RAM szükséges' + - 'x86 processzor szükséges (nem fut Raspberry Pi-n)' + - 'Nextcloud vagy más kompatibilis alkalmazás ajánlott az integrációhoz' + +# --- App-to-app integrations --- +integrations: + - target: filebrowser + label: "FileBrowser integráció" + description: "Dokumentumok szerkesztése a fájlkezelőben" + - target: nextcloud + label: "Nextcloud integráció" + description: "Dokumentumok szerkesztése a Nextcloudban" + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 80 + path: "/healthcheck" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/opengist/.felhom.yml b/controller/internal/stacks/testdata/catalog/opengist/.felhom.yml new file mode 100644 index 0000000..49a66ef --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/opengist/.felhom.yml @@ -0,0 +1,64 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "OpenGist" +description: "Kód snippetek megosztása (GitHub Gist alternatíva)" +category: "dev" +subdomain: "gist" +slug: "opengist" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "gist" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Kód snippetek megosztása - privát GitHub Gist alternatíva" + docs_url: "https://github.com/thomiceli/opengist" + + use_cases: + - 'Kód snippetek és szövegek megosztása' + - 'Szintaxis kiemelés sok programozási nyelvhez' + - 'Privát és nyilvános gistek' + - 'Git-alapú tárolás - verziókezelés automatikusan' + - 'Markdown támogatás és beágyazás' + + first_steps: + - 'Nyisd meg a gist.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat (az első felhasználó admin lesz)' + - 'Hozd létre az első gistet - írd be a kódot és mentsd' + - 'Oszd meg a linket' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 6157 + path: "/healthcheck" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/outline/.felhom.yml b/controller/internal/stacks/testdata/catalog/outline/.felhom.yml new file mode 100644 index 0000000..6f20543 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/outline/.felhom.yml @@ -0,0 +1,86 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Outline" +description: "Modern csapat tudásbázis Markdown támogatással" +category: "productivity" +subdomain: "kb" +slug: "outline" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "200M" + mem_limit: "768M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "kb" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: UTILS_SECRET + label: "Segédprogram kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Modern tudásbázis - Markdown, valós idejű együttműködés, keresés" + docs_url: "https://docs.getoutline.com/" + + use_cases: + - 'Csapat tudásbázis és dokumentáció' + - 'Markdown szerkesztő valós idejű együttműködéssel' + - 'Strukturált dokumentumok fa-szerkezetben' + - 'Teljes szöveges keresés és hivatkozások' + - 'API és integrációk (Slack, webhookok)' + + first_steps: + - 'Nyisd meg a kb.DOMAIN címet a böngészőben' + - 'Hozd létre az első felhasználót' + - 'Hozz létre egy gyűjteményt (Collection) a dokumentumoknak' + - 'Kezdj el írni Markdown-ban' + - 'Hívd meg a csapattagokat' + + prerequisites: + - 'x86 processzor szükséges' + - 'Legalább 1 GB szabad RAM (Outline + PostgreSQL + Redis)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3000 + path: "/_health" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/paperless-ngx/.felhom.yml b/controller/internal/stacks/testdata/catalog/paperless-ngx/.felhom.yml new file mode 100644 index 0000000..7ff8e31 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/paperless-ngx/.felhom.yml @@ -0,0 +1,167 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/paperless-ngx/.felhom.yml +# +# This file defines: +# 1. Display info (name, description, icon) +# 2. Deploy fields (what the user fills in during first deployment) +# 3. Asset references (logos, screenshots loaded from felhom.eu) +# 4. Resource hints (RAM, Pi compatibility) +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Paperless-ngx" +description: "Dokumentumok digitalizálása és rendszerezése" +category: "productivity" # productivity, media, finance, security, tools +subdomain: "paperless" # -> paperless. + +# --- Asset slug --- +# Used to construct URLs for logo and screenshots from felhom.eu: +# Logo: {assets.base_url}/assets/{slug}-logo.webp +# Screenshot: {assets.base_url}/assets/{slug}-screenshot-{n}.webp +# App page: {assets.base_url}/alkalmazasok#{slug} +# Falls back to directory name if not set. +slug: "paperless-ngx" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "500M" # Expected memory usage (display hint) + mem_limit: "1664M" # Total enforced limit across all containers (1280+256+128) — R-514 + pi_compatible: true # Runs on Raspberry Pi 3B+ + needs_hdd: true # Needs external storage for user data + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + hdd: + - path: appdata/paperless/media + class: mandatory # document originals+archive — DB hashes/paths reference them + - path: appdata/paperless/export + class: excluded # transient export target + import: + - path: paperless + class: excluded # consume inbox — deleted after ingest by contract + +# --- Customer-facing folder annotation (R-75) --- +# ANNOTATES paths that already exist as compose binds above; never declares a new one. +# role: import | library | export (unknown role → that entry is simply not shown) +data_paths: + - path: paperless + root: import + role: import + label: "Beolvasandó dokumentumok" + +# --- Deploy fields --- +# Shown to the user during first deployment. +# After deployment, values are saved to app.yaml in the stack directory. +# +# Field types: +# domain - Auto-filled from controller config, read-only +# secret - Auto-generated, hidden (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): +# password:N - N chars alphanumeric +# hex:N - N bytes hex-encoded +# static:VAL - Fixed value + +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "paperless" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: PAPERLESS_SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: PAPERLESS_ADMIN_USER + label: "Admin felhasználónév" + type: text + default: "admin" + locked_after_deploy: false + + - env_var: PAPERLESS_ADMIN_PASSWORD + label: "Admin jelszó" + type: password + generate: "password:16" + description: "Első bejelentkezéshez. Utána a webes felületen módosítható." + locked_after_deploy: false + + - env_var: HDD_PATH + label: "Adattárolási útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja, ahol a dokumentumok tárolódnak" + locked_after_deploy: true + + - env_var: PAPERLESS_OCR_LANGUAGE + label: "OCR nyelv" + type: select + default: "hun+eng" + options: + - value: "hun" + label: "Magyar" + - value: "eng" + label: "Angol" + - value: "hun+eng" + label: "Magyar + Angol" + - value: "deu+eng" + label: "Német + Angol" + description: "Dokumentum felismerés nyelve" + locked_after_deploy: false + +# --- App info (info page content) --- +app_info: + tagline: 'Digitális irattár - szkennelés, OCR és automatikus rendszerezés' + docs_url: 'https://docs.paperless-ngx.com/' + + use_cases: + - 'Papír dokumentumok digitalizálása és rendszerezése' + - 'Automatikus OCR szövegfelismerés szkennelt dokumentumokon' + - 'Intelligens automatikus kategorizálás és címkézés' + - 'Teljes szöveges keresés az összes dokumentumban' + - 'Email-ből érkező dokumentumok automatikus feldolgozása' + + first_steps: + - 'Nyisd meg a paperless.DOMAIN címet a böngészőben' + - 'Lépj be: felhasználó "admin", a jelszó a Beállítások oldalon található (Automatikusan generált értékek)' + - 'Tölts fel egy dokumentumot a webfelületen, VAGY dobd a Fájlkezelőben (FileBrowser) az import/paperless mappába — onnan automatikusan beolvassa és OCR-rel feldolgozza. A Fájlkezelő belépése a Fájlkezelő alkalmazás oldalán található' + - 'Sok dokumentumot egyszerre is feltölthetsz: egyenként dolgozza fel őket, egy nagyobb köteg néhány percig tart' + - 'Hozz létre címkéket és levelezőket az automatikus rendszerezéshez' + + prerequisites: + - 'Külső HDD ajánlott a dokumentumok tárolásához' + - 'Legalább 1 GB szabad RAM (OCR feldolgozáshoz)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8000 diff --git a/controller/internal/stacks/testdata/catalog/papra/.felhom.yml b/controller/internal/stacks/testdata/catalog/papra/.felhom.yml new file mode 100644 index 0000000..efc8000 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/papra/.felhom.yml @@ -0,0 +1,70 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Papra" +description: "Minimalista dokumentumtár és rendszerező" +category: "productivity" +subdomain: "papra" +slug: "papra" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-12" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "papra" + required: true + locked_after_deploy: true + + - env_var: AUTH_SECRET + label: "Munkamenet-aláíró kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + # A Papra éles módban ELUTASÍTJA az indulást, ha ez az alapértelmezett érték + # marad ("the auth secret must not be the default one"), ezért kötelező. + # Ez írja alá a munkameneteket: ha újragenerálódik, minden bejelentkezés + # érvénytelenné válik, ezért visszaállításkor a régi kulcsot kell megtartani. + data_key: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Minimalista dokumentumtár - egyszerű rendszerezés és keresés" + docs_url: "https://docs.papra.app/" + + use_cases: + - 'Dokumentumok egyszerű feltöltése és rendszerezése' + - 'Gyors keresés a dokumentumok között' + - 'Minimalista felület felesleges funkciók nélkül' + - 'Könnyű alternatíva a Paperless-ngx-hez' + + first_steps: + - 'Nyisd meg a papra.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Töltsd fel az első dokumentumot' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 1221 diff --git a/controller/internal/stacks/testdata/catalog/plant-it/.felhom.yml b/controller/internal/stacks/testdata/catalog/plant-it/.felhom.yml new file mode 100644 index 0000000..f321da5 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/plant-it/.felhom.yml @@ -0,0 +1,83 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Plant-it" +description: "Növénynapló és gondozás emlékeztető" +category: "home" +subdomain: "plants" +slug: "plant-it" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-21" + +# --- Lifecycle --- +# abandoned: upstream stopped developing the self-hosted edition. NOT offered for new installs; +# anyone already running it keeps running it, with a permanent notice that no updates or security +# fixes will arrive. Evidence (2026-07-21): the `backend/` and `deployment/` directories are DELETED +# from upstream `main` (the project is now an Android app on F-Droid/Obtainium); the last server +# image `msdeluise/plant-it-server:0.10.0` was pushed 2024-12-10 and is a security-frozen Spring +# Boot 3.4.0; and it requires MySQL 8.0 + Redis, which this template never had. +# +# The compose below is deliberately LEFT AS-IS (it pins `msdeluise/plant-it:0.10.0`, a repository +# that does not exist — the real one is `-server`). It is not worth fixing: the app is not +# installable, and rewriting it would imply it is. +lifecycle: abandoned + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "plants" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: JWT_SECRET + label: "JWT titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Növénynapló - emlékeztetők öntözésre, trágyázásra és fotónapló" + docs_url: "https://docs.plant-it.org/" + + use_cases: + - 'Szobanövények és kerti növények nyilvántartása' + - 'Emlékeztetők öntözésre, trágyázásra, átültetésre' + - 'Fotónapló a növények fejlődéséről' + - 'Statisztikák és gondozási előzmények' + - 'Több felhasználó - a család együtt gondozhat' + + first_steps: + - 'Nyisd meg a plants.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Add hozzá az első növényt fotóval' + - 'Állíts be gondozási emlékeztetőket' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8080 + path: "/api/info" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/plex/.felhom.yml b/controller/internal/stacks/testdata/catalog/plex/.felhom.yml new file mode 100644 index 0000000..31588d6 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/plex/.felhom.yml @@ -0,0 +1,91 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Plex" +description: "Népszerű média szerver csiszolt felülettel" +category: "media" +subdomain: "plex" +slug: "plex" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-02-15" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "2048M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media + class: excluded # pure reader of the shared tree (:ro); state lives in volumes + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "plex" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: PLEX_CLAIM + label: "Plex Claim Token" + type: text + required: true + placeholder: "claim-xxxxxxxxxxxx" + description: "Generáld a https://plex.tv/claim oldalon (4 percig érvényes)" + locked_after_deploy: true + + - env_var: HDD_PATH + label: "Médiatár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Prémium média szerver kiváló alkalmazás támogatással" + docs_url: "https://support.plex.tv/" + + use_cases: + - 'Filmek, sorozatok és zene streamelése professzionális felületen' + - 'Automatikus transzkódolás bármilyen eszközhöz' + - 'Watch Together - közös filmnézés távolról' + - 'Intro/outro skip funkció (Plex Pass)' + - 'Gazdag mobilalkalmazás és Smart TV támogatás' + + first_steps: + - 'Generálj egy claim tokent a https://plex.tv/claim oldalon' + - 'Telepítés után nyisd meg a plex.DOMAIN címet' + - 'Kapcsold össze a Plex fiókoddat a szerverrel' + - 'Add hozzá a médiatárat (filmek: /media/movies, sorozatok: /media/shows)' + - 'Telepítsd a Plex alkalmazást eszközeidre' + + prerequisites: + - 'Plex fiók szükséges (ingyenes regisztráció a plex.tv-n)' + - 'Külső HDD a médiafájlok tárolásához' + - 'Legalább 2 GB szabad RAM (transzkódoláshoz több ajánlott)' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 32400 + path: "/identity" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/privatebin/.felhom.yml b/controller/internal/stacks/testdata/catalog/privatebin/.felhom.yml new file mode 100644 index 0000000..4f220ca --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/privatebin/.felhom.yml @@ -0,0 +1,60 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "PrivateBin" +description: "Titkosított jegyzet és szöveg megosztás" +category: "security" +subdomain: "paste" +slug: "privatebin" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-09-14" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "paste" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Titkosított szöveg megosztás - a szerver nem látja a tartalmat" + docs_url: "https://github.com/PrivateBin/PrivateBin/wiki" + + use_cases: + - 'Érzékeny szövegek biztonságos megosztása' + - 'E2E titkosítás - a szerver nem fér hozzá a tartalomhoz' + - 'Beállítható lejárati idő (5 perc - 1 év, vagy soha)' + - 'Olvasás után automatikus törlés opció' + - 'Jelszóvédelem a még nagyobb biztonságért' + + first_steps: + - 'Nyisd meg a paste.DOMAIN címet a böngészőben' + - 'Írd be a szöveget és kattints a Küldés gombra' + - 'Oszd meg a generált linket - a titkosítási kulcs az URL-ben van' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 diff --git a/controller/internal/stacks/testdata/catalog/radarr/.felhom.yml b/controller/internal/stacks/testdata/catalog/radarr/.felhom.yml new file mode 100644 index 0000000..7e11e25 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/radarr/.felhom.yml @@ -0,0 +1,85 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Radarr" +description: "Automatikus film letöltő és rendszerező" +category: "media-automation" +subdomain: "radarr" +slug: "radarr" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "150M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/movies + class: excluded # re-downloadable by design + - path: downloads + class: excluded # ruled: transient queue (cross-app shared with sonarr) + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "radarr" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Médiatár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Automatikus film letöltő és rendszerező" + docs_url: "https://wiki.servarr.com/radarr" + + use_cases: + - 'Filmek automatikus letöltése és rendszerezése' + - 'Kívánságlista - filmek automatikusan a könyvtáradba kerülnek' + - 'Minőség kezelés és automatikus frissítés jobb minőségre' + - 'Integráció Jellyfin/Plex és letöltő kliensekkel' + - 'Metaadat és borítókép automatikus letöltés' + + first_steps: + - 'Nyisd meg a radarr.DOMAIN címet a böngészőben' + - 'Állíts be egy letöltő klienst (pl. qBittorrent)' + - 'Add hozzá az indexereket (vagy használj Prowlarr-t)' + - 'Állítsd be a gyökérmappát (/media/movies)' + - 'Keress rá egy filmre és add hozzá a listádhoz' + + prerequisites: + - 'Külső HDD szükséges a médiafájlok tárolásához' + - 'Letöltő kliens szükséges (pl. qBittorrent - külön telepítendő)' + - 'Indexer hozzáférés szükséges a kereséshez' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 7878 + path: "/ping" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/rallly/.felhom.yml b/controller/internal/stacks/testdata/catalog/rallly/.felhom.yml new file mode 100644 index 0000000..c819fa8 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/rallly/.felhom.yml @@ -0,0 +1,88 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Rallly" +description: "Időpont szavazás (Doodle alternatíva)" +category: "productivity" +subdomain: "poll" +slug: "rallly" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "384M" + mem_limit: "1024M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "poll" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_PASSWORD + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Időpont szavazás - Doodle alternatíva a saját szerveren" + docs_url: "https://support.rallly.co/" + + use_cases: + - 'Közös időpont egyeztetés szavazással' + - 'Találkozók, események szervezése' + - 'Résztvevők regisztráció nélkül szavazhatnak' + - 'Email értesítések és emlékeztetők' + - 'Egyszerű, tiszta felület' + + first_steps: + - 'Nyisd meg a poll.DOMAIN címet a böngészőben' + - 'Hozz létre egy új szavazást' + - 'Add meg a lehetséges időpontokat' + - 'Oszd meg a linket a résztvevőkkel' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3000 + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# Rallly (Nodemailer) uses STARTTLS to the shim (SMTP_SECURE=false) and accepts the shim's self-signed +# cert (SMTP_REJECT_UNAUTHORIZED=false on v4; v3.x accepts by default). From = Rallly's single +# NOREPLY_EMAIL; SMTP_USER/SMTP_PWD stay unset (the shim accepts no-auth). +smtp_mapping: + host_var: SMTP_HOST + port_var: SMTP_PORT + security_var: SMTP_SECURE + security_value: "false" + from_var: NOREPLY_EMAIL + from_name_var: NOREPLY_EMAIL_NAME + from_local: rallly + extra: + SMTP_REJECT_UNAUTHORIZED: "false" diff --git a/controller/internal/stacks/testdata/catalog/recipe-importer/.felhom.yml b/controller/internal/stacks/testdata/catalog/recipe-importer/.felhom.yml new file mode 100644 index 0000000..ad986a6 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/recipe-importer/.felhom.yml @@ -0,0 +1,79 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/recipe-importer/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Recipe Importer" +description: "Magyar receptoldalak importálása Mealie-be és Tandoor-ba" +category: "tools" +subdomain: "rimport" + +# --- Asset slug --- +slug: "recipe-importer" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-12" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "rimport" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY + label: "Titkos kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + description: "Flask munkamenet titkos kulcs (automatikusan generált)" + +# --- App info (info page content) --- +app_info: + tagline: 'Magyar receptoldalak importálása egyszerűen — egyedi és tömeges import Mealie-be és Tandoor-ba' + docs_url: 'https://gitea.dooplex.hu/admin/recipe-importer' + + use_cases: + - 'Receptek importálása 6 magyar oldalról: mindmegette.hu, streetkitchen.hu, nosalty.hu, sobors.hu, kiskegyed.hu és gastrohobbi.hu — hozzávalók, lépések, képek és címkék automatikus felismerése' + - 'Tömeges importálás: több URL beillesztése egyszerre, recept-áttekintéssel vagy teljesen automatikus módban' + - 'Importálás előtti szerkesztés — strukturált hozzávalók (mennyiség, mértékegység, étel, megjegyzés), lépések módosítása, címkék kezelése' + - 'Mealie és Tandoor Recipes egyidejű támogatás — választhatsz célrendszert, vagy mindkettőbe importálhatsz egyszerre' + - 'Ismeretlen oldalak esetén schema.org JSON-LD alapú automatikus feldolgozás (recipeIngredient, recipeInstructions, keywords)' + + first_steps: + - 'Nyisd meg a rimport.DOMAIN címet a böngészőben' + - 'Menj a Beállítások oldalra és add meg a Mealie és/vagy Tandoor URL-t és API kulcsot (Mealie: Profil → API Tokens; Tandoor: Beállítások → API Browser → Auth Token)' + - 'Állíts be felhasználónevet és jelszót a hozzáférés védelméhez' + - 'Illeszd be egy recept URL-jét az importálás oldalon, kattints a Feldolgozás gombra, majd ellenőrzés után az Importálás gombra' + + prerequisites: + - 'Mealie és/vagy Tandoor Recipes telepítve és elérhető a hálózaton' + - 'API kulcs a célalkalmazásban (Mealie és/vagy Tandoor) létrehozva' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8000 + path: "/health" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/romm/.felhom.yml b/controller/internal/stacks/testdata/catalog/romm/.felhom.yml new file mode 100644 index 0000000..4d7e157 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/romm/.felhom.yml @@ -0,0 +1,149 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "RomM" +description: "Retró játékgyűjtemény kezelő" +category: "media" +subdomain: "arcade" +slug: "romm" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "300M" + mem_limit: "1024M" + pi_compatible: false + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: roms + class: optional # ruled: curated ROM sets often not re-acquirable + hdd: + - path: appdata/romm/resources + class: excluded # scraper cache, re-derivable + +# --- Customer-facing folder annotation (R-75) --- +data_paths: + - path: roms + root: userdata + role: library + label: "ROM gyűjtemény" + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "arcade" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: MYSQL_ROOT_PASSWORD + label: "MariaDB root jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: ROMM_AUTH_SECRET_KEY + label: "Hitelesítési titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: HDD_PATH + label: "Adattárolási útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja, ahol a ROM-ok és borítóképek tárolódnak" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Retró játékgyűjtemény kezelő, böngésző és lejátszó" + default_creds: "admin / admin" + docs_url: "https://github.com/rommapp/romm/wiki" + + use_cases: + - "Retró játékgyűjtemény rendszerezése és böngészése webes felületen" + - "Játékok metaadatainak automatikus letöltése — borítók, leírások, értékelések" + - "Platformonkénti szűrés és keresés a teljes gyűjteményben" + - "ROM fájlok webes feltöltése és letöltése" + - "Többfelhasználós hozzáférés a háztartás tagjai számára" + + first_steps: + - "Nyisd meg az arcade.DOMAIN címet a böngészőben" + - "Jelentkezz be az alapértelmezett admin / admin fiókkal" + - "Változtasd meg azonnal a jelszót a Settings menüben" + - "Töltsd fel a ROM fájlokat a Fájlkezelőben (FileBrowser) a roms/ mappába, platform mappákba rendezve (pl. roms/gba/, roms/snes/)" + - "Indíts egy Scan-t a bal oldali menüben a ROM-ok beolvasásához" + - "Opcionális: állíts be metaadat-szolgáltatókat a borítóképek és leírások automatikus letöltéséhez (lásd lent)" + + prerequisites: + - "Külső HDD szükséges a ROM fájlok és borítóképek tárolásához" + - "Legalább 1 GB szabad RAM ajánlott (MariaDB + Redis + RomM)" + - "ROM fájlok platform mappákba rendezve (pl. roms/gba/, roms/snes/)" + +# --- Optional config (configurable before or after deployment) --- +optional_config: + - group: "Metaadat-szolgáltatók" + description: "Játékok borítóinak, leírásainak és értékeléseinek automatikus letöltéséhez. Legalább az IGDB beállítása ajánlott. Mindegyik ingyenesen használható regisztráció után." + fields: + - env_var: IGDB_CLIENT_ID + label: "IGDB Client ID" + type: text + help_url: "https://api-docs.igdb.com/#getting-started" + help_text: "1) Regisztrálj / jelentkezz be a Twitch fejlesztői portálon (dev.twitch.tv). 2) Hozz létre egy új alkalmazást (bármilyen névvel). 3) Másold be a Client ID-t." + + - env_var: IGDB_CLIENT_SECRET + label: "IGDB Client Secret" + type: secret_input + help_text: "A Twitch alkalmazásod Client Secret-je (a „New Secret\" gombbal generálhatod)." + + - env_var: STEAMGRIDDB_API_KEY + label: "SteamGridDB API Key" + type: text + help_url: "https://www.steamgriddb.com/profile/preferences/api" + help_text: "Regisztrálj a SteamGridDB oldalon, majd a Preferences → API fül alatt kattints a „Generate API key\" gombra." + + - env_var: SCREENSCRAPER_USER + label: "ScreenScraper felhasználónév" + type: text + help_url: "https://www.screenscraper.fr/" + help_text: "Regisztrálj a screenscraper.fr oldalon. A felhasználóneved lesz az API felhasználónév." + + - env_var: SCREENSCRAPER_PASSWORD + label: "ScreenScraper jelszó" + type: secret_input + help_text: "A screenscraper.fr fiókod jelszava." + + - env_var: MOBYGAMES_API_KEY + label: "MobyGames API Key" + type: text + help_url: "https://www.mobygames.com/info/api/" + help_text: "Regisztrálj a MobyGames oldalon, majd az API oldalon igényelj kulcsot. Részletes játékinformációkat és krediteket biztosít." + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 diff --git a/controller/internal/stacks/testdata/catalog/seerr/.felhom.yml b/controller/internal/stacks/testdata/catalog/seerr/.felhom.yml new file mode 100644 index 0000000..6c986a1 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/seerr/.felhom.yml @@ -0,0 +1,68 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Jellyseerr" +description: "Média igénylés kezelő Jellyfin/Plex integrációval" +category: "media-automation" +subdomain: "requests" +slug: "seerr" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "requests" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Film és sorozat igénylő a háztartás tagjai számára" + docs_url: "https://docs.overseerr.dev/" + + use_cases: + - 'Családtagok filmeket és sorozatokat igényelhetnek egyszerűen' + - 'Automatikus letöltés Sonarr/Radarr integrációval' + - 'Elérhető tartalmak böngészése szép felületen' + - 'Igénylések állapotának követése' + - 'Felhasználói kvóták és jogosultságok kezelése' + + first_steps: + - 'Nyisd meg a requests.DOMAIN címet a böngészőben' + - 'Kövesd a beállítás varázslót' + - 'Csatlakoztasd a Jellyfin vagy Plex szerveredet' + - 'Csatlakoztasd a Sonarr és Radarr példányokat' + - 'Hívd meg a családtagokat' + + prerequisites: + - 'Jellyfin vagy Plex szerver szükséges' + - 'Sonarr és/vagy Radarr szükséges az automatikus letöltéshez' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 5055 + path: "/api/v1/status" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/sonarr/.felhom.yml b/controller/internal/stacks/testdata/catalog/sonarr/.felhom.yml new file mode 100644 index 0000000..de75258 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/sonarr/.felhom.yml @@ -0,0 +1,85 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Sonarr" +description: "Automatikus sorozat letöltő és rendszerező" +category: "media-automation" +subdomain: "sonarr" +slug: "sonarr" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "150M" + mem_limit: "512M" + pi_compatible: true + needs_hdd: true + +# --- Backup classification (referential coupling; see felhom.eu documentation/audits/SPIKE-backup-classification-2026-07-14.md) --- +backup: + userdata: + - path: media/tv + class: excluded + - path: downloads + class: excluded + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "sonarr" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: HDD_PATH + label: "Médiatár útvonal" + type: path + required: true + placeholder: "/mnt/felhom-drives/hdd_1" + description: "A külső merevlemez elérési útja" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Automatikus sorozat letöltő és rendszerező" + docs_url: "https://wiki.servarr.com/sonarr" + + use_cases: + - 'Sorozatok automatikus letöltése és rendszerezése' + - 'Kívánságlista kezelés - új epizódok automatikus letöltése' + - 'Minőség kezelés (480p-tól 4K-ig, automatikus frissítés)' + - 'Integráció Jellyfin/Plex és letöltő kliensekkel' + - 'Felirat automatikus letöltés' + + first_steps: + - 'Nyisd meg a sonarr.DOMAIN címet a böngészőben' + - 'Állíts be egy letöltő klienst (pl. qBittorrent)' + - 'Add hozzá az indexereket (vagy használj Prowlarr-t)' + - 'Állítsd be a gyökérmappát (/media/shows)' + - 'Keress rá egy sorozatra és add hozzá a listádhoz' + + prerequisites: + - 'Külső HDD szükséges a médiafájlok tárolásához' + - 'Letöltő kliens szükséges (pl. qBittorrent - külön telepítendő)' + - 'Indexer hozzáférés szükséges a kereséshez' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 8989 + path: "/ping" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/sparkyfitness/.felhom.yml b/controller/internal/stacks/testdata/catalog/sparkyfitness/.felhom.yml new file mode 100644 index 0000000..05bc282 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/sparkyfitness/.felhom.yml @@ -0,0 +1,91 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +display_name: "SparkyFitness" +description: "Táplálkozás- és edzéskövető" +category: "home" +subdomain: "sparky" +slug: "sparkyfitness" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +resources: + mem_request: "400M" + # Sum of compose limits: db 512M + server 1024M + frontend 256M = 1792M + mem_limit: "1792M" + pi_compatible: false + needs_hdd: false + +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "sparky" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: APP_DB_PASSWORD + label: "Alkalmazás adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + + - env_var: API_ENCRYPTION_KEY + label: "Adattitkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + # Encrypts stored data (e.g. external-integration credentials). Must NEVER be + # regenerated after first boot — that would make existing encrypted data + # unrecoverable. At restore the controller RECOVERS (never regenerates) it. + data_key: true + + - env_var: BETTER_AUTH_SECRET + label: "Munkamenet aláíró kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + # Signs sessions and encrypts 2FA/TOTP secrets. Must NEVER change after first + # boot — changing it locks out any user who enabled two-factor auth. + data_key: true + +app_info: + tagline: "Táplálkozási napló, kalóriaszámláló és edzéskövető – önállóan üzemeltetve" + docs_url: "https://github.com/CodeWithCJ/SparkyFitness" + + use_cases: + - 'Napi étkezések és kalóriabevitel naplózása' + - 'Testsúly és testméretek követése grafikonokkal' + - 'Edzések és mozgás rögzítése' + - 'Tápanyag-célok (fehérje, szénhidrát, zsír) beállítása és követése' + - 'Haladás áttekintése idővonalon és statisztikákon' + + first_steps: + - 'Nyisd meg a sparky.DOMAIN címet a böngészőben' + - 'Regisztrálj egy fiókot e-mail címmel és jelszóval' + - 'Állítsd be a profilodat és a napi céljaidat' + - 'Kezdd el naplózni az étkezéseidet és edzéseidet' + + prerequisites: + - 'Az első indításkor a szerver lefuttatja az adatbázis-migrációkat – ez akár 1-2 percig is eltarthat, mire az alkalmazás elérhetővé válik' + +# --- Controller-side health probe (same form as wger) --- +healthcheck: + checks: + - type: http + port: 80 diff --git a/controller/internal/stacks/testdata/catalog/tandoor/.felhom.yml b/controller/internal/stacks/testdata/catalog/tandoor/.felhom.yml new file mode 100644 index 0000000..d838e02 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/tandoor/.felhom.yml @@ -0,0 +1,75 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Tandoor Recipes" +description: "Receptkezelő és étkezés tervező" +category: "home" +subdomain: "recipes" +slug: "tandoor" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "512M" + mem_limit: "1280M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "recipes" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Receptkezelő és étkezés tervező a családnak" + docs_url: "https://docs.tandoor.dev/" + + use_cases: + - 'Receptek gyűjtése és rendszerezése egy helyen' + - 'Receptek importálása weboldalakról egy kattintással' + - 'Heti étkezés tervezés és bevásárlólista generálás' + - 'Több felhasználó - a család együtt tervezhet' + - 'Receptek megosztása linkkel családtagokkal, barátokkal' + + first_steps: + - 'Nyisd meg a recipes.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot' + - 'Importáld az első receptet egy weboldalról (Bookmarklet)' + - 'Próbáld ki az étkezés tervezőt' + - 'Hívd meg a családtagokat' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 + path: "/accounts/login/" diff --git a/controller/internal/stacks/testdata/catalog/termix/.felhom.yml b/controller/internal/stacks/testdata/catalog/termix/.felhom.yml new file mode 100644 index 0000000..08a0864 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/termix/.felhom.yml @@ -0,0 +1,57 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Termix" +description: "Webes SSH és szerver menedzser" +category: "dev" +subdomain: "terminal" +slug: "termix" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-12" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "terminal" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Webes SSH terminál - szerver hozzáférés a böngészőből" + docs_url: "https://github.com/Termix-SSH/Termix" + + use_cases: + - 'SSH hozzáférés a szerveredhez böngészőből' + - 'Nincs szükség SSH kliensre a számítógépen' + - 'Alapvető szerver menedzsment feladatok bárhonnan' + + first_steps: + - 'Nyisd meg a terminal.DOMAIN címet a böngészőben' + - 'Csatlakozz a szerveredhez SSH-n keresztül' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 8080 diff --git a/controller/internal/stacks/testdata/catalog/uptime-kuma/.felhom.yml b/controller/internal/stacks/testdata/catalog/uptime-kuma/.felhom.yml new file mode 100644 index 0000000..0a373d4 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/uptime-kuma/.felhom.yml @@ -0,0 +1,62 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Uptime Kuma" +description: "Szolgáltatás és weboldal monitoring" +category: "dashboard" +subdomain: "status" +slug: "uptime-kuma" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "status" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Szolgáltatás monitoring - értesítés ha valami nem működik" + docs_url: "https://github.com/louislam/uptime-kuma/wiki" + + use_cases: + - 'Weboldalak és szolgáltatások elérhetőségének figyelése' + - 'HTTP, TCP, DNS, ping és egyéb protokollok támogatása' + - 'Értesítések emailben, Telegramon, Discord-on, stb.' + - 'Nyilvános státusz oldal a felhasználóknak' + - 'Szép grafikonok a rendelkezésre állásról' + + first_steps: + - 'Nyisd meg a status.DOMAIN címet a böngészőben' + - 'Hozd létre az admin fiókot az első megnyitáskor' + - 'Add hozzá az első monitort (pl. a saját weboldalad)' + - 'Állíts be értesítéseket (email, Telegram, stb.)' + - 'Opcionálisan: hozz létre egy nyilvános státusz oldalt' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3001 diff --git a/controller/internal/stacks/testdata/catalog/vaultwarden/.felhom.yml b/controller/internal/stacks/testdata/catalog/vaultwarden/.felhom.yml new file mode 100644 index 0000000..1ca4015 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/vaultwarden/.felhom.yml @@ -0,0 +1,112 @@ +# ============================================================================= +# .felhom.yml — App metadata for felhom-controller +# ============================================================================= +# Place alongside docker-compose.yml in each stack directory: +# /opt/docker/stacks/vaultwarden/.felhom.yml +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Vaultwarden" +description: "Jelszókezelő (Bitwarden-kompatibilis)" +category: "security" +subdomain: "vault" + +# --- Asset slug --- +slug: "vaultwarden" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "vault" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: ADMIN_TOKEN + label: "Admin panel token" + type: secret + generate: "hex:32" + description: "Token az admin panel eléréséhez (https://vault./admin)" + locked_after_deploy: true + + - env_var: SIGNUPS_ALLOWED + label: "Regisztráció engedélyezése" + type: select + default: "false" + options: + - value: "false" + label: "Nem – csak meghívással (ajánlott)" + - value: "true" + label: "Igen – bárki regisztrálhat, aki ismeri a címet" + description: "Alapból lezárva: a család tagjait az admin panelen hívod meg (lásd Első lépések). Nyitott regisztrációval bárki fiókot nyithat, aki kitalálja a címet." + locked_after_deploy: false + +# --- App info (info page content) --- +app_info: + tagline: 'Jelszókezelő - Bitwarden kompatibilis, a saját szerveren' + docs_url: 'https://github.com/dani-garcia/vaultwarden/wiki' + + use_cases: + - 'Jelszavak biztonságos tárolása és automatikus kitöltése' + - 'Bitwarden kliensek teljes kompatibilitása (böngésző, mobil, asztali)' + - 'Jelszavak megosztása családtagokkal szervezeten belül' + - 'Kétfaktoros hitelesítés (TOTP) kódok tárolása' + - 'Biztonságos jegyzetek és bankkártya adatok tárolása' + + first_steps: + - 'Nyisd meg a vault.DOMAIN/admin címet, és lépj be az admin panel tokenjével (Beállítások → Automatikusan generált értékek)' + - 'A „Users" fülön az „Invite User" mezőben hívd meg a saját és a családtagok e-mail címét — a regisztráció alapból le van zárva, csak meghívott cím nyithat fiókot' + - 'Nyisd meg a vault.DOMAIN címet, válaszd a „Create account" lehetőséget a meghívott címmel, és adj meg erős mesterjelszót' + - 'Telepítsd a Bitwarden bővítményt a böngésződbe' + - 'Telepítsd a Bitwarden alkalmazást a telefonodra' + - 'Importáld a meglévő jelszavaidat (Chrome, Firefox, LastPass, stb.)' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 80 + path: "/alive" + expect: + status: 200 + +# --- App-email mapping (apps → in-controller shim → hub → Resend) --- +# When app-email is on (global toggle + this app's per-app toggle), the controller injects +# the relay SMTP settings: host = the on-box shim, port = 2525, From = vaultwarden@felhom.eu. +# Vaultwarden uses STARTTLS to the shim and accepts its self-signed cert +# (SMTP_ACCEPT_INVALID_CERTS/HOSTNAMES). SMTP_USERNAME/SMTP_PASSWORD are intentionally left +# unset — the shim accepts no-auth on the Docker network and holds no Resend key. +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" + # Boot-gate for Vaultwarden's strict SMTP validation (campaign finding F1, 2026-07-06): + # the image errors out when SMTP_HOST/SMTP_FROM are defined-but-empty, so the compose + # default is _ENABLE_SMTP=false and this injection flips it on with the rest of the group. + _ENABLE_SMTP: "true" diff --git a/controller/internal/stacks/testdata/catalog/vikunja/.felhom.yml b/controller/internal/stacks/testdata/catalog/vikunja/.felhom.yml new file mode 100644 index 0000000..ed1bf71 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/vikunja/.felhom.yml @@ -0,0 +1,71 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Vikunja" +description: "Feladatkezelő listák és táblák (Todoist/Trello alternatíva)" +category: "productivity" +subdomain: "tasks" +slug: "vikunja" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "50M" + mem_limit: "256M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "tasks" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: VIKUNJA_SERVICE_JWTSECRET + label: "JWT titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Feladatkezelő - listák, Kanban táblák és Gantt diagramok" + docs_url: "https://vikunja.io/docs/" + + use_cases: + - 'Feladatlisták és teendők kezelése' + - 'Kanban tábla nézet (Trello-szerű)' + - 'Gantt diagram projekt ütemezéshez' + - 'Megosztott listák és csapat együttműködés' + - 'Emlékeztetők, lejárati dátumok, címkék és prioritások' + + first_steps: + - 'Nyisd meg a tasks.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Hozd létre az első projektet és feladatlistát' + - 'Próbáld ki a Kanban és Lista nézeteket' + - 'Hívd meg a családtagokat vagy kollégákat' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3456 + path: "/api/v1/info" + expect: + status: 200 diff --git a/controller/internal/stacks/testdata/catalog/wanderer/.felhom.yml b/controller/internal/stacks/testdata/catalog/wanderer/.felhom.yml new file mode 100644 index 0000000..4f399bd --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/wanderer/.felhom.yml @@ -0,0 +1,81 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Wanderer" +description: "Túra tervező és nyomkövetéssel" +category: "travel" +subdomain: "hike" +slug: "wanderer" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-21" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "350M" + mem_limit: "1024M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "hike" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SUBDOMAIN_DB + label: "Adatbázis aldomain" + type: subdomain + default: "hike-db" + required: true + locked_after_deploy: true + description: "A PocketBase adatbázis aldomainje - a böngésző KÖZVETLENÜL ezt hívja, ezért saját nevet kap" + + - env_var: MEILI_MASTER_KEY + label: "Keresőmotor kulcs" + type: secret + generate: "hex:16" + locked_after_deploy: true + + - env_var: POCKETBASE_ENCRYPTION_KEY + label: "Adatbázis titkosítási kulcs" + type: secret + generate: "hex:16" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Túra tervező - útvonalak, GPX nyomok és domborzati térképek" + docs_url: "https://wanderer.to/" + + use_cases: + - 'Túra útvonalak tervezése és mentése' + - 'GPX fájlok importálása és exportálása' + - 'Domborzati profilok és statisztikák' + - 'Fotók és jegyzetek hozzáadása az útvonalakhoz' + - 'Útvonalak megosztása másokkal' + + first_steps: + - 'Nyisd meg a hike.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Importálj egy GPX fájlt vagy tervezz új útvonalat' + - 'Fedezd fel a térképes megjelenítést' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3000 diff --git a/controller/internal/stacks/testdata/catalog/wger/.felhom.yml b/controller/internal/stacks/testdata/catalog/wger/.felhom.yml new file mode 100644 index 0000000..517da5d --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/wger/.felhom.yml @@ -0,0 +1,69 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "wger" +description: "Edzésnapló és fitnesz tervező" +category: "home" +subdomain: "fitness" +slug: "wger" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-19" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "384M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "fitness" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: SECRET_KEY + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Edzésnapló - edzéstervek, haladás követés és testsúly napló" + default_creds: "admin / adminadmin" + docs_url: "https://wger.readthedocs.io/" + + use_cases: + - 'Edzéstervek létrehozása és követése' + - 'Testsúly és testméretek nyilvántartása grafikonokkal' + - 'Gyakorlatok adatbázisa képekkel és leírásokkal' + - 'Kalória és tápanyag követés' + - 'API támogatás fitnesz alkalmazás integrációkhoz' + + first_steps: + - 'Nyisd meg a fitness.DOMAIN címet a böngészőben' + - 'Jelentkezz be: admin / adminadmin' + - 'Változtasd meg azonnal a jelszót' + - 'Hozd létre az edzéstervedet' + - 'Kezdd el naplózni az edzéseidet' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 80 diff --git a/controller/internal/stacks/testdata/catalog/wishlist/.felhom.yml b/controller/internal/stacks/testdata/catalog/wishlist/.felhom.yml new file mode 100644 index 0000000..e41b518 --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/wishlist/.felhom.yml @@ -0,0 +1,61 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Wishlist" +description: "Családi kívánságlista megosztás" +category: "home" +subdomain: "wishes" +slug: "wishlist" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-19" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "30M" + mem_limit: "128M" + pi_compatible: true + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "wishes" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + +# --- App info (info page content) --- +app_info: + tagline: "Családi kívánságlista - karácsonyra, születésnapokra" + docs_url: "https://github.com/cmintey/wishlist" + + use_cases: + - 'Kívánságlisták létrehozása karácsonyra, születésnapokra' + - 'Családtagok meghívása saját listáik kezeléséhez' + - 'Ajándékok lefoglalása meglepetés nélkül' + - 'Árak és linkek hozzáadása az ajándékokhoz' + - 'Csoportos ajándékok szervezése' + + first_steps: + - 'Nyisd meg a wishes.DOMAIN címet a böngészőben' + - 'Hozd létre a fiókodat' + - 'Hozd létre az első kívánságlistádat' + - 'Hívd meg a családtagokat' + + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: http + port: 3000 diff --git a/controller/internal/stacks/testdata/catalog/zipline/.felhom.yml b/controller/internal/stacks/testdata/catalog/zipline/.felhom.yml new file mode 100644 index 0000000..2304e1c --- /dev/null +++ b/controller/internal/stacks/testdata/catalog/zipline/.felhom.yml @@ -0,0 +1,80 @@ +# ============================================================================= +# .felhom.yml - App metadata for felhom-controller +# ============================================================================= + +# --- Display info (shown on dashboard) --- +display_name: "Zipline" +description: "ShareX/Flameshot szerver - screenshot és fájlmegosztás" +category: "files" +subdomain: "img" +slug: "zipline" +# catalog_since: the date THIS repo last changed this app's pinned images. Any commit that +# changes an image: line must set this to the same day (see CLAUDE.md). +catalog_since: "2026-07-18" + +# --- Resource hints (displayed on deploy screen) --- +resources: + mem_request: "100M" + mem_limit: "512M" + pi_compatible: false + needs_hdd: false + +# --- Deploy fields (first deployment only) --- +deploy_fields: + - env_var: DOMAIN + label: "Domain" + type: domain + description: "A szerver domain neve" + locked_after_deploy: true + + - env_var: SUBDOMAIN + label: "Aldomain" + type: subdomain + default: "img" + required: true + locked_after_deploy: true + description: "Az alkalmazás aldomainje" + + - env_var: CORE_SECRET + label: "Titkosítási kulcs" + type: secret + generate: "hex:32" + locked_after_deploy: true + + - env_var: DB_PASSWORD + label: "Adatbázis jelszó" + type: secret + generate: "password:24" + locked_after_deploy: true + +# --- App info (info page content) --- +app_info: + tagline: "Screenshot és fájlmegosztó szerver ShareX/Flameshot integrációval" + default_creds: "admin / zipline" + docs_url: "https://zipline.diced.sh/docs/" + + use_cases: + - 'Screenshotok automatikus feltöltése ShareX/Flameshot-ból' + - 'URL rövidítés és szöveg megosztás (paste)' + - 'Galéria nézet és album kezelés' + - 'API támogatás egyedi integrációkhoz' + - 'Felhasználói fiókok és meghívó rendszer' + + first_steps: + - 'Nyisd meg az img.DOMAIN címet a böngészőben' + - 'Jelentkezz be: admin / zipline' + - 'Változtasd meg azonnal a jelszót' + - 'Konfiguráld a ShareX-et vagy Flameshot-ot az API URL-lel' + - 'Tölts fel egy screenshot-ot a teszteléshez' + + prerequisites: + - 'ShareX (Windows) vagy Flameshot (Linux) ajánlott a screenshot feltöltéshez' + +# --- Controller-side health probe --- +healthcheck: + checks: + - type: api + port: 3000 + path: "/api/health" + expect: + status: 200 diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 56108af..81afeb5 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -182,7 +182,11 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) { data["TitleKey"] = "page.title.dashboard" // i18n: the Hungarian title above is what hu renders s.addRecoveryBanner(data, r) // R-241: the reminder bar, per visit data["SettingsWarning"] = s.settings.LoadWarning // non-empty if settings.json was recovered from corruption - data["Stacks"] = deployedStacks + // R-560: the app rows carry catalog copy (each row's `.Meta.Description`), so the list the + // TEMPLATE sees is the localised view. The lists the warning helpers below see are the + // originals — they key on stack NAMES, and handing them a translated copy would only widen + // what a catalog push can reach. For hu this returns the same slice, untouched. + data["Stacks"] = stacks.LocalizeStacks(deployedStacks, s.langFor(r)) data["MissingStorage"] = s.missingStorageMap(deployedStacks) data["OOMKilled"] = s.stackMgr.OOMKilledStacks() // R-514 nw, ns := s.networkStorageWarnings(deployedStacks) // NAS unreachable (recoverable) / guest-side stub (defect) @@ -362,7 +366,7 @@ func (s *Server) stacksHandler(w http.ResponseWriter, r *http.Request) { data := s.baseData("stacks", "Alkalmazások") data["TitleKey"] = "page.title.stacks" // i18n: the Hungarian title above is what hu renders allStacks := visibleCatalogStacks(s.stackMgr.GetStacks()) - data["Stacks"] = allStacks + data["Stacks"] = stacks.LocalizeStacks(allStacks, s.langFor(r)) // R-560 — see dashboardHandler data["MissingStorage"] = s.missingStorageMap(allStacks) nw, ns := s.networkStorageWarnings(allStacks) // NAS unreachable (recoverable) / guest-side stub (defect) data["NetworkWarnings"] = nw @@ -415,7 +419,7 @@ func (s *Server) logsHandler(w http.ResponseWriter, r *http.Request, name string data := s.baseData("logs", stack.Meta.DisplayName+" — Naplók") data["TitleKey"], data["TitleArgs"] = "page.title.logs", []interface{}{stack.Meta.DisplayName} // i18n: the Hungarian title above is what hu renders - data["Stack"] = stack + data["Stack"] = stacks.LocalizeStackPtr(stack, s.langFor(r)) // R-560 data["Logs"] = logs s.executeTemplate(w, r, "logs", data) } @@ -436,13 +440,23 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri stack, _ := s.stackMgr.GetStack(name) alreadyDeployed := appCfg != nil && appCfg.Deployed + // R-560 — the ONE place this page's catalog copy is chosen, and it is placed here on purpose: + // every `meta` read below it (the field labels, the descriptions, the optional-config groups) + // is a display read, and this handler only ever renders — the deploy POST is a different + // route. The overlay changes copy fields ONLY; `env_var`, `type`, `generate`, `default`, + // `required` and `locked_after_deploy` are not in MetadataOverlay at all, so the auto-field + // map, the HDD_PATH question and the prefill keys below read exactly what they read before. + lang := s.langFor(r) + localized := meta.For(lang) + meta = &localized + pageTitle, pageTitleKey := meta.DisplayName+" — Telepítés", "page.title.deploy" if alreadyDeployed { pageTitle, pageTitleKey = meta.DisplayName+" — Beállítások", "page.title.app_settings" } data := s.baseData("deploy", pageTitle) data["TitleKey"], data["TitleArgs"] = pageTitleKey, []interface{}{meta.DisplayName} // i18n: the Hungarian title above is what hu renders - data["Stack"] = stack + data["Stack"] = stacks.LocalizeStackPtr(stack, lang) // R-560 data["Meta"] = meta data["AppConfig"] = appCfg data["AlreadyDeployed"] = alreadyDeployed @@ -583,7 +597,21 @@ func (s *Server) deployHandler(w http.ResponseWriter, r *http.Request, name stri // App-to-app integrations if im := s.integrationMgr.Load(); meta.HasIntegrations() && im != nil { data["HasIntegrations"] = true - data["Integrations"] = im.ListForProvider(meta.Slug) + // R-560: the integration manager reads the stack's own (Hungarian) Meta to build these + // rows — it has no request and therefore no language. The label and the sentence are + // catalog copy, so they are re-taken from the LOCALISED meta here, matched by target + // the same way the overlay itself matches. Everything else in the row (state, target + // health, last error) is the manager's and is left alone. + rows := im.ListForProvider(meta.Slug) + for i := range rows { + for _, def := range meta.Integrations { + if def.Target == rows[i].Target { + rows[i].Label, rows[i].Description = def.Label, def.Description + break + } + } + } + data["Integrations"] = rows } // Geo-restriction per-app data @@ -707,6 +735,13 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s } } + // R-560 — this page is the one that shows ALL of an app's catalog copy (tagline, use cases, + // first steps, prerequisites, the default-credentials line, the data-folder labels), so the + // localised view is taken ONCE here and `found` is replaced by it. Everything downstream — + // the data-path cards, the initial-credentials note — then reads the household's language + // without a second decision to get wrong. + found = stacks.LocalizeStackPtr(found, s.langFor(r)) + data := s.baseData("stacks", found.Meta.DisplayName) data["Stack"] = found data["Meta"] = found.Meta @@ -743,7 +778,10 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s data["InitialCreds"] = &stacks.ExtractedCreds{ Available: creds.Available, Username: creds.Username, - Note: creds.Note, + // R-560: the reader took the note from the manager's Hungarian metadata (it runs + // without a request and has no language). The note is catalog copy, so it is + // re-taken from the localised spec — the same value for hu, byte for byte. + Note: found.Meta.InitialCreds.Note, // Password deliberately NOT carried — the reveal endpoint is the only path to it. } data["InitialCredsHasPassword"] = strings.TrimSpace(creds.Password) != "" diff --git a/controller/internal/web/meta_copy_allowlist_test.go b/controller/internal/web/meta_copy_allowlist_test.go new file mode 100644 index 0000000..93e199b --- /dev/null +++ b/controller/internal/web/meta_copy_allowlist_test.go @@ -0,0 +1,108 @@ +package web + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "sort" + "strings" + "testing" +) + +// TestNoDirectMetaCopyReadOnPages — the guard for R-560's whole point. +// +// Catalog copy (`Description`, `AppInfo`, the deploy-field labels, `OptionalConfig`, `Integrations`, +// the `DataPaths` labels, the initial-credentials note) is HUNGARIAN in the value the stack manager +// caches. A page reaches the household's language only by going through `Metadata.For(lang)` — +// `stacks.LocalizeStacks`, `LocalizeStack`, `LocalizeStackPtr` or `MetaFor`. Read `x.Meta.` +// straight off a manager value and an English household silently gets Hungarian: no error, no log, +// nothing that looks wrong on the page. That is precisely the failure mode this project has shipped +// nine times under a comment that read as settled. +// +// So every direct read of a copy field off a `.Meta` in this package is LISTED BELOW WITH A REASON. +// A new one fails this test, and the author then has two honest choices: route it through For(lang), +// or add it here saying why it may stay Hungarian. +// +// WHAT THIS TEST CANNOT DO: it reads the source, so it cannot tell a localised receiver from an +// unlocalised one — `found.Meta.AppInfo` looks the same either way. It catches the ARRIVAL of a new +// copy read, which is when a human has to think, and that is the whole claim made for it. +// +// RED-PROOF (2026-09-20): adding `_ = stack.Meta.Description` to handlers.go failed this test +// naming handlers.go and Description; removing it went green. +var metaCopyReadAllowlist = map[string]string{ + // The app page localises `found` in one place at the top of appDetailHandler and every read + // below it — these included — is of that localised value. + "handlers.go:AppInfo": "appDetailHandler reads it off the LocalizeStackPtr'd `found`", + "handlers.go:InitialCreds": "appDetailHandler: the nil check and the note, both off localised `found`", + // buildDataPathCards is called with the same localised `found`; the labels it renders are the + // English ones when the household is on English. + "datapath_card.go:DataPaths": "buildDataPathCards is handed the localised stack by appDetailHandler", +} + +// metaCopyFields are the Metadata fields that carry customer-facing TEXT. Everything else on +// Metadata — Slug, Subdomain, OpenPath, BrandColor, Category, Resources, Lifecycle, CatalogSince, +// HealthCheck, SMTPMapping, Backup — is configuration and reads the same in every language. +// DisplayName is deliberately NOT here: an app's name is not translated (10-localisation.md §11, +// operator ruling 7). +var metaCopyFields = map[string]bool{ + "Description": true, + "AppInfo": true, + "DeployFields": true, + "OptionalConfig": true, + "Integrations": true, + "DataPaths": true, + "InitialCreds": true, +} + +func TestNoDirectMetaCopyReadOnPages(t *testing.T) { + entries, err := os.ReadDir(".") + if err != nil { + t.Fatal(err) + } + seen := map[string]bool{} + var unlisted []string + + for _, e := range entries { + name := e.Name() + if e.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { + continue + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, name, nil, 0) // comments are not walked — only real reads count + if err != nil { + t.Fatalf("%s: %v", name, err) + } + ast.Inspect(f, func(n ast.Node) bool { + outer, ok := n.(*ast.SelectorExpr) + if !ok || !metaCopyFields[outer.Sel.Name] { + return true + } + inner, ok := outer.X.(*ast.SelectorExpr) + if !ok || inner.Sel.Name != "Meta" { + return true + } + key := name + ":" + outer.Sel.Name + seen[key] = true + if _, allowed := metaCopyReadAllowlist[key]; !allowed { + unlisted = append(unlisted, fmt.Sprintf("%s (%s)", key, fset.Position(outer.Pos()))) + } + return true + }) + } + + sort.Strings(unlisted) + for _, u := range unlisted { + t.Errorf("catalog COPY read straight off .Meta: %s\n"+ + " Route it through stacks.LocalizeStack(s)/LocalizeStackPtr/MetaFor(lang), or add it to\n"+ + " metaCopyReadAllowlist in this file with the reason it may stay Hungarian.", u) + } + + // A stale allow-list entry is a lie about what the code does — it must go when its read goes. + for key := range metaCopyReadAllowlist { + if !seen[key] { + t.Errorf("allow-list entry %q no longer matches any read — delete it", key) + } + } +}