Files
werkator/docs/prs/2026-10-05-PR#124-nginx-settings-and-systemd-files.md
miandmhoennig 108264d405
werkator build succeeded after 02:55
werkator/deploy build succeeded after 00:08
Managed nginx settings and systemd files split into their steps, gate down to 24 (v1.37.5) (#124)
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

## The Problem

Two methods of the installation are above the Kotlin target of 15, measured by the nightly mutation build of 2026-10-05:

| Method                              | Mutation-CRAP | Complexity | Mutation score |
|-------------------------------------|---------------|------------|----------------|
| `NginxProxyManager.resolveSettings` | 27.44         | 18         | 0.69           |
| `InitCommand.createSystemdFiles`    | 17.40         | 5          | 0.21           |

`resolveSettings` checks every setting of the managed nginx and builds its settings in one method; no test tried invalid ports, an invalid upstream host or a configured state directory.
`createSystemdFiles` writes six files and prints what it wrote and how to install it; no test read that output, so 38 of 48 mutants survived.

## Non-Goals

- No change in behaviour: the same settings are refused with the same warnings, the same files are written with the same lines printed.
- The other groups above 15 follow in their own PRs.

## The Scenarios

### Feature: the managed nginx refuses what it cannot run

#### Scenario#124.01: every setting nginx cannot run with is refused, a configured state directory read as the host means it

So that a misconfigured nginx is refused before a container starts, and a valid one is never refused.

- **Given** the managed nginx is enabled
- **When** a port is 0 or above 65535, both ports are the same, the upstream host is no host name, or `server.port` takes the HTTPS port
- **Then** no settings are resolved, and the warning names the reason
- **When** the ports are 1 and 65535, or an upstream host of its own is configured
- **Then** the settings carry them
- **When** `stateDir` starts with `~/`, or is relative
- **Then** it is the home directory's, or the repository's

##### Verified by

- [NginxProxyManagerUnitTest](../../src/test/kotlin/de/hoennig/werkator/server/NginxProxyManagerUnitTest.kt) `every setting nginx cannot run with is refused, the ports up to their limits accepted (Scenario#124.01)`, `a configured state directory has its ~ expanded and is read relative to the repository (Scenario#124.01)`, and the earlier tests of the server name and the `server.port` collision

### Feature: init --systemd says what it did

#### Scenario#124.02: init --systemd says what it created and how to install it

So that the person running `init --systemd` can follow the printed steps without reading the code.

- **Given** a repository whose configuration has a public base URL
- **When** `init --systemd` runs
- **Then** it prints one `created` line for the unit, the environment file, the two Docker cleanup units, the `.htaccess` and the maintenance page
  - **and** the `ln -sf` and `systemctl --user` lines that install all three units
- **When** it runs again
- **Then** it says that the environment file already exists and is not overwritten
- **When** the jar path cannot be determined
- **Then** it prints the error and writes no unit

##### Verified by

- [InitCommandUnitTest](../../src/test/kotlin/de/hoennig/werkator/commands/InitCommandUnitTest.kt) `--systemd says what it created and how to install it, and keeps a host's environment file (Scenario#124.02)`, `--systemd without a resolvable jar path generates no unit file`

## The Solution

`resolveSettings` keeps the order: disabled, then the checks, then the settings.
`refusalOf` returns the reason a configuration cannot run, in the words of the former warnings, and `null` when it can; `portsValid` and `stateDirOf` hold the port check and the state directory.

`createSystemdFiles` hands the writing to a `GeneratedFiles` helper, which writes a file and prints its `created` line, and to one step per kind of file: `writeServiceUnit`, `writeEnvFile`, `writeHostFiles`; `printInstallHints` prints the install lines.

A local PIT run of `NginxProxyManager` and `InitCommand` afterwards:

| Method                              | Mutation-CRAP | Complexity | Mutation score |
|-------------------------------------|---------------|------------|----------------|
| `NginxProxyManager.resolveSettings` | 5.58          | 5          | 0.71           |
| `NginxProxyManager.refusalOf`       | 7             | 7          | 1.00           |
| `NginxProxyManager.portsValid`      | 8.30          | 8          | 0.83           |
| `NginxProxyManager.stateDirOf`      | 3.14          | 3          | 0.75           |
| `InitCommand.createSystemdFiles`    | 2.09          | 2          | 0.71           |
| `InitCommand.writeEnvFile`          | 2.15          | 2          | 0.67           |
| `InitCommand.writeHostFiles`        | 2             | 2          | 1.00           |
| `InitCommand.printInstallHints`     | 1             | 1          | 0.86           |

The highest score left in both classes is `InitCommand.runningVersion` at 10.75.
The project maximum is now `FileArtifactStore.prune` at 23.50, so the gate goes down from 28 to 24.

## Additional Changes

- `maxMutationCrap` in `build.gradle.kts` lowered from 28 to 24.
- Version 1.37.5 with its entry in `releases.html`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #124
2026-10-05 19:29:55 +02:00

5.3 KiB

PR#124: Managed nginx Settings and systemd Files Split into Their Steps

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

Two methods of the installation are above the Kotlin target of 15, measured by the nightly mutation build of 2026-10-05:

Method Mutation-CRAP Complexity Mutation score
NginxProxyManager.resolveSettings 27.44 18 0.69
InitCommand.createSystemdFiles 17.40 5 0.21

resolveSettings checks every setting of the managed nginx and builds its settings in one method; no test tried invalid ports, an invalid upstream host or a configured state directory. createSystemdFiles writes six files and prints what it wrote and how to install it; no test read that output, so 38 of 48 mutants survived.

Non-Goals

  • No change in behaviour: the same settings are refused with the same warnings, the same files are written with the same lines printed.
  • The other groups above 15 follow in their own PRs.

The Scenarios

Feature: the managed nginx refuses what it cannot run

Scenario#124.01: every setting nginx cannot run with is refused, a configured state directory read as the host means it

So that a misconfigured nginx is refused before a container starts, and a valid one is never refused.

  • Given the managed nginx is enabled
  • When a port is 0 or above 65535, both ports are the same, the upstream host is no host name, or server.port takes the HTTPS port
  • Then no settings are resolved, and the warning names the reason
  • When the ports are 1 and 65535, or an upstream host of its own is configured
  • Then the settings carry them
  • When stateDir starts with ~/, or is relative
  • Then it is the home directory's, or the repository's
Verified by
  • NginxProxyManagerUnitTest every setting nginx cannot run with is refused, the ports up to their limits accepted (Scenario#124.01), a configured state directory has its ~ expanded and is read relative to the repository (Scenario#124.01), and the earlier tests of the server name and the server.port collision

Feature: init --systemd says what it did

Scenario#124.02: init --systemd says what it created and how to install it

So that the person running init --systemd can follow the printed steps without reading the code.

  • Given a repository whose configuration has a public base URL
  • When init --systemd runs
  • Then it prints one created line for the unit, the environment file, the two Docker cleanup units, the .htaccess and the maintenance page
    • and the ln -sf and systemctl --user lines that install all three units
  • When it runs again
  • Then it says that the environment file already exists and is not overwritten
  • When the jar path cannot be determined
  • Then it prints the error and writes no unit
Verified by
  • InitCommandUnitTest --systemd says what it created and how to install it, and keeps a host's environment file (Scenario#124.02), --systemd without a resolvable jar path generates no unit file

The Solution

resolveSettings keeps the order: disabled, then the checks, then the settings. refusalOf returns the reason a configuration cannot run, in the words of the former warnings, and null when it can; portsValid and stateDirOf hold the port check and the state directory.

createSystemdFiles hands the writing to a GeneratedFiles helper, which writes a file and prints its created line, and to one step per kind of file: writeServiceUnit, writeEnvFile, writeHostFiles; printInstallHints prints the install lines.

A local PIT run of NginxProxyManager and InitCommand afterwards:

Method Mutation-CRAP Complexity Mutation score
NginxProxyManager.resolveSettings 5.58 5 0.71
NginxProxyManager.refusalOf 7 7 1.00
NginxProxyManager.portsValid 8.30 8 0.83
NginxProxyManager.stateDirOf 3.14 3 0.75
InitCommand.createSystemdFiles 2.09 2 0.71
InitCommand.writeEnvFile 2.15 2 0.67
InitCommand.writeHostFiles 2 2 1.00
InitCommand.printInstallHints 1 1 0.86

The highest score left in both classes is InitCommand.runningVersion at 10.75. The project maximum is now FileArtifactStore.prune at 23.50, so the gate goes down from 28 to 24.

Additional Changes

  • maxMutationCrap in build.gradle.kts lowered from 28 to 24.
  • Version 1.37.5 with its entry in releases.html.

🤖 Generated with Claude Code