From 2cddd76c8871bb96f0872a9cc7337ccbfd6730f5 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Fri, 4 Sep 2026 17:50:58 +0200 Subject: [PATCH] Read init's effective configuration from the repository root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `init --systemd` resolved the configuration through `Paths.get(".")` while every other file it touches — the two generated config layers, an applied fragment, the units, the .htaccess — goes through the git top level. The two agree only when the process happens to run in the repository root; from a subdirectory the command silently read a different repository's configuration or none at all, so `--apply`'s promise that the fragment's port reaches the generated unit did not hold, and the host integration was skipped as if no publicBaseUrl were configured. The root is passed down instead, matching every other caller of `ConfigLoader.load`. Co-Authored-By: Claude Opus 5 --- .../hoennig/werkator/commands/InitCommand.kt | 17 +++++++++----- .../werkator/commands/InitCommandTest.kt | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/de/hoennig/werkator/commands/InitCommand.kt b/src/main/kotlin/de/hoennig/werkator/commands/InitCommand.kt index a824341..f3c86f7 100644 --- a/src/main/kotlin/de/hoennig/werkator/commands/InitCommand.kt +++ b/src/main/kotlin/de/hoennig/werkator/commands/InitCommand.kt @@ -297,20 +297,25 @@ class InitCommand( * already loadable (re-running `init --systemd` on an installed instance); during * the very first bootstrap they stay unset and the defaults (no directives) apply. */ - private fun loadedSystemdConfig(): de.hoennig.werkator.config.SystemdConfig = loadedServerConfig().systemd + private fun loadedSystemdConfig(root: Path): de.hoennig.werkator.config.SystemdConfig = loadedServerConfig(root).systemd /** Loaded once per run, so a broken configuration is reported once and not per caller. */ private var cachedServerConfig: de.hoennig.werkator.config.ServerConfig? = null /** + * Read from the repository root like every other file this command touches — the + * layers sit there, not in whatever directory the process happens to run in, and + * an applied fragment must reach the generated unit even when `init` is invoked + * from a subdirectory. + * * A configuration error here is not fatal — the units are still generated with defaults — * but it must not pass for "nothing configured": without the warning a broken `.werkator.yml` * looks exactly like an unset `publicBaseUrl` and the host integration is skipped silently. */ - private fun loadedServerConfig(): de.hoennig.werkator.config.ServerConfig = + private fun loadedServerConfig(root: Path): de.hoennig.werkator.config.ServerConfig = cachedServerConfig ?: run { try { - configLoader.load(Paths.get(".")).server + configLoader.load(root).server } catch (e: Exception) { println("Warning: the effective configuration could not be loaded (${e.message})") println(" continuing with default server settings — check the generated unit and host files") @@ -340,8 +345,8 @@ class InitCommand( javaExecutable = javaExecutableResolver(), jarPath = jarPath, envFile = envFile, - memoryMax = loadedSystemdConfig().memoryMax, - tasksMax = loadedSystemdConfig().tasksMax, + memoryMax = loadedSystemdConfig(root).memoryMax, + tasksMax = loadedSystemdConfig(root).tasksMax, ), ) println("created ${unitFile.toFile().relativeTo(normalizedWorkingDir.toFile())}") @@ -364,7 +369,7 @@ class InitCommand( // generated host integration like the units: only meaningful behind a web // frontend, so it needs a public base URL; unused elsewhere and harmless - val server = loadedServerConfig() + val server = loadedServerConfig(root) if (server.publicBaseUrl.isNotBlank()) { val htaccessFile = werkatorDir.resolve(SystemdServiceFiles.HTACCESS_NAME) htaccessFile.toFile().writeText(SystemdServiceFiles.htaccessContent(server.port)) diff --git a/src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt b/src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt index 2d16448..a2f289f 100644 --- a/src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt +++ b/src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt @@ -253,6 +253,29 @@ class InitCommandTest : FunSpec() { initCommand.run() } + test("--systemd reads the configuration from the repository root, not the current directory") { + val tempDir = Files.createTempDirectory("werkator-init-test") + // written before the run, so `init` keeps it instead of creating a template + tempDir.resolve(".werkator.yml").toFile().writeText( + "server:\n publicBaseUrl: \"https://werkator.example.org/\"\n port: 18099\n", + ) + initCommand.workingDir = tempDir + initCommand.systemd = true + initCommand.jarPathResolver = { Paths.get("/home/ci/bin/werkator.jar") } + initCommand.javaExecutableResolver = { Paths.get("/usr/bin/java") } + + every { gitService.getTopLevel(tempDir) } returns tempDir + every { gitService.getOriginUrl(tempDir) } returns "https://git.example.org/my-org/my-repo.git" + + initCommand.run() + + // the host integration is generated only when the root's config has a public base URL + val htaccess = tempDir.resolve(".git/werkator/${SystemdServiceFiles.HTACCESS_NAME}") + htaccess.toFile().shouldExist() + htaccess.toFile().readText() shouldContain "18099" + tempDir.resolve(".git/werkator/${SystemdServiceFiles.MAINTENANCE_PAGE_NAME}").toFile().shouldExist() + } + test("--systemd warns once when the effective configuration cannot be loaded") { val tempDir = Files.createTempDirectory("werkator-init-test") val brokenLoader = mockk()