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

93 lines
5.3 KiB
Markdown

# 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](../../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)