Files
werkator/docs/prs/2026-09-04-PR#22-init-config-resolution.md
2026-09-04 18:02:58 +02:00

5.4 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

init --systemd generates the host integration — the systemd unit's resource limits, the Apache .htaccess and the maintenance page — from the effective configuration. It read that configuration through a helper with two independent defects, both of which fail silently.

It swallowed every error. The load sat in try { … } catch (_: Exception) { ServerConfig() }. Any configuration error at all — a missing required field, a malformed layer, a version floor violation — produced a default ServerConfig with a blank publicBaseUrl. A repository with a broken .werkator.yml then looked exactly like one that simply has no public base URL configured: the .htaccess and the maintenance page were skipped without a word. This surfaced while verifying PR#17 on mih09, where the missing files looked like an unconfigured publicBaseUrl and were in fact an unrelated validation error.

It read from the wrong directory. The helper called configLoader.load(Paths.get(".")) — the process's current directory — while everything else in the command works off the git top level resolved by GitService.getTopLevel. ConfigLoader.loadRaw resolves the layers directly under the directory it is given and does not walk up to the repository root, so the two agree only when init happens to be invoked from the root itself. From a subdirectory the command read another repository's configuration, or none. That also broke --apply: the fragment is installed into the repository root deliberately before the systemd files are written, so that its port and limits reach the generated unit, and a current-directory read does not see it.

init --systemd runs during initial deployment setup, which is exactly when a silent wrong answer is most expensive.

Non-Goals

  • The fallback itself is kept: a configuration that cannot be loaded is not fatal for init, the units are still generated with the defaults. During the very first bootstrap there is legitimately nothing to load yet.
  • No change to ConfigLoader, to the configuration schema, or to any other command.
  • No sweep for catch-all exception handlers elsewhere in the code base; the two other catch blocks in InitCommand already print an Error: and abort, so they were only checked, not changed.

The Scenarios

Feature: init reports what it read and where it read it from

Background

  • The repository root is the git top level as resolved by GitService.getTopLevel, the directory holding .werkator.yml, .git/werkator/.werkator.yml and an applied fragment.
  • The current directory is the process working directory, which is the repository root only when init is invoked there.

Scenario#22.01: A broken configuration is named, not defaulted over

So that a validation error during deployment setup is not mistaken for an unconfigured installation.

  • Given a repository whose effective configuration cannot be loaded
  • When init --systemd runs
  • Then the exception message is printed as a warning
    • and the unit files are still generated with the default settings
    • and the warning appears exactly once, although three settings are read from the configuration
Verified by

Scenario#22.02: The configuration is read from the repository root

So that the generated host integration reflects the repository being initialized, whatever directory init was invoked from.

  • Given a repository whose root configuration sets server.publicBaseUrl and server.port
  • and a current directory that is not that repository root
  • When init --systemd runs
  • Then the .htaccess and the maintenance page are generated
    • and the .htaccess proxies to the port from the root configuration
Verified by

The Solution

The catch-all now prints the exception message before falling back:

Warning: the effective configuration could not be loaded (<message>)
  continuing with default server settings — check the generated unit and host files

The configuration is read three times while the systemd files are written (memoryMax, tasksMax, publicBaseUrl), which would repeat the warning three times. It is therefore loaded once per run and cached in the command, and the cache is reset at the top of run() so a reused instance — the command is a Spring singleton — re-reads.

The repository root is passed down into the two accessors instead of Paths.get("."). This matches every other caller of ConfigLoader.load in the code base, all of which pass an explicit working directory; InitCommand was the only one relying on the process's current directory.

Both fixes are the same failure in two forms — the command answered from a configuration it never actually read — which is why they are in one PR.

Additional Changes

  • None.