v0.257.0: the app catalog can speak English (R-560, slice 5 Part A)
gates / gates (push) Successful in 25s

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.<copy>` 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-20 14:31:52 +02:00
parent e81ad613ae
commit 60f0a86bd4
60 changed files with 5318 additions and 6 deletions
+6
View File
@@ -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)
+320
View File
@@ -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
}
@@ -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/<app>/.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)
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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."
@@ -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
@@ -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
@@ -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
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.<domain>
# --- 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/"
@@ -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
@@ -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
@@ -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.<domain>/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"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+44 -6
View File
@@ -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) != ""
@@ -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.<copy>`
// 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)
}
}
}