YAML Configuration Loading#
All YAML config files in homepage (services.yaml, settings.yaml, bookmarks.yaml, etc.) are parsed through a single shared utility. Understanding how that utility works — and what can go wrong — is essential for debugging silent config failures.
Entry Points#
| Layer | File | Key Function |
|---|---|---|
| YAML loader | src/utils/config/yaml.js | loadYaml(input, options) |
| Settings consumer | src/utils/config/config.js | getSettings() |
| Config validator | src/utils/config/config.js | checkAndCopyConfig(config) |
All config-related modules (docker.js, kubernetes.js, service-helpers.js, widget-helpers.js, etc.) were migrated to call loadYaml() instead of yaml.load() directly as part of the js-yaml v5 upgrade .
loadYaml() — The Core Wrapper#
loadYaml() wraps yaml.load() with two behaviors:
- Custom schema: Uses
yaml.CORE_SCHEMA.withTags(yaml.timestampTag, yaml.mergeTag, yaml.legacyMapTag)to restore YAML 1.1 features that js-yaml v5 dropped from its default schema . - Empty-document compatibility: js-yaml v5 throws on empty input;
loadYaml()catches the specific"expected a document, but the input is empty"error and returnsundefinedinstead, preserving v4 behavior .
Any other parse error is re-thrown.
getSettings() — Silent Empty-Object Fallback#
getSettings() reads settings.yaml, runs environment-variable substitution, then calls:
const initialSettings = loadYaml(fileContents) ?? {};
If loadYaml() returns undefined (empty or comment-only file), getSettings() silently falls back to {}. There is no logged warning. Callers receive an empty object and continue normally — this is the mechanism behind symptoms like missing background images or tab navigation settings disappearing without any error in the logs.
js-yaml v4 → v5 Migration Regression (v2.1.1)#
When js-yaml was bumped from 4.3.1 to 5.3.0 in PR #7032, two breaking changes affected homepage config files:
| v4 behavior | v5 behavior (strict YAML 1.2) |
|---|---|
Empty input → undefined | Empty input → throws YAMLException |
| Complex/non-string map keys silently coerced to strings | Complex keys throw "object-based map does not support complex keys" |
<< merge keys enabled by default | Merge keys not in CORE_SCHEMA; must opt-in |
Timestamps parsed as Date objects | No timestamp support in CORE_SCHEMA by default |
The immediate user-visible failure was services.yaml throwing YAMLException: object-based map does not support complex keys for standard Homepage-documented YAML like - Docker Services: . This was fixed in PR #7037 by adding legacyMapTag to the schema and adding a regression test for non-string keys.
See the official js-yaml v4 → v5 migration guide for the full list of breaking changes.
Schema Configuration#
The DEFAULT_SCHEMA in yaml.js opts back into three tags that CORE_SCHEMA omits:
timestampTag— parses date strings (e.g.2024-01-01) asDateobjectsmergeTag— enables<<anchor mergeslegacyMapTag— silently coerces complex keys to strings instead of throwing (added in PR #7037 to fix the v2.1.1 regression)
Notably, YAML 1.1 boolean parsing (yes/no → true/false) is intentionally not re-enabled; those values remain strings .
checkAndCopyConfig() — Error Propagation at Validation Time#
checkAndCopyConfig() calls loadYaml() to validate an existing config file when the server starts. If parsing fails, it returns { ...error, config } rather than throwing — meaning a parse error surfaces as a truthy return value, not an exception. Callers that don't inspect the return value will miss the error.
Tests#
Unit tests live in src/utils/config/yaml.test.js and cover:
- Empty/whitespace/comment-only docs →
undefined - Normal document parsing
- Merge key (
<<) round-trip - Timestamp parsing +
yes/noremain strings - Non-string keys (unsubstituted
{{HOMEPAGE_VAR_*}}and date keys) - Invalid YAML still throws