Skip to content

Commit 8d2e35b

Browse files
Roadmap: config-as-code, Sigstore signing and Scorecard shipped
build/hmconfig.ps1 closes the config-as-code item the roadmap called the highest-value discretionary one. Signing is half done and recorded as such: Sigstore covers verifiability for anyone who checks, but SmartScreen and the UAC prompt care about Authenticode and not about Sigstore, so Windows still shows an unknown-publisher warning and that half stays open. Scorecard publishes to code scanning rather than to the public badge API, since there is no reason to send repository telemetry to a third party for a number we can read ourselves.
1 parent 208b6cf commit 8d2e35b

2 files changed

Lines changed: 7 additions & 6 deletions

File tree

Roadmap.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ strong and where it is thin far more honestly than any prose summary.
6464
| **Forward-looking** | | | | |
6565
| [Planned work](#planned-work) || 2 | 16 | 2 |
6666
| [Future-proofing: standards and protocols](#future-proofing-standards-and-protocols) | 1 || 5 | 2 |
67-
| [Future-proofing: platform and supply chain](#future-proofing-platform-and-supply-chain) | 2 | | 4 | 2 |
68-
| [Future-proofing: deployment and operations](#future-proofing-deployment-and-operations) | 2 || 7 ||
69-
| **Total** | **520** | **5** | **181** | **14** |
67+
| [Future-proofing: platform and supply chain](#future-proofing-platform-and-supply-chain) | 3 | 1 | 2 | 2 |
68+
| [Future-proofing: deployment and operations](#future-proofing-deployment-and-operations) | 3 || 6 ||
69+
| **Total** | **521** | **5** | **177** | **14** |
7070

7171
Three things stand out and are worth naming rather than leaving to be inferred.
7272
**Storage and the administration surface are the best-covered areas**, and the
@@ -1025,9 +1025,9 @@ Future-proofing: platform and supply chain
10251025
| ✅ | **.NET target policy, written down** | Audited every project in the tree, not just the shipping ones. **All six shipped artefacts and the Control Panel publish as `net10.0`** — verified from their `runtimeconfig.json`, so nothing reaching a user is on .NET 8. The rest is deliberate, and now recorded so it is a decision rather than drift: the **regression suite and its three companions stay on .NET Framework 4.8.1**, which has *no end date* (Component Lifecycle Policy — supported as long as its host Windows) and drives the server over COM interop, so moving it to .NET 10 would be a large, risky change to the release gate for no benefit. **Six projects still on .NET Framework 4.5 — support ended 13 January 2016 — were bumped to 4.8.1**; they are dormant upstream dev tools referenced by no script, and since the 4.5 targeting pack is absent from a modern VS install they could not build at all before. **Seven projects remain older still**: five on .NET Framework 3.5 SP1 (supported to 10 January 2029), and two — `MemoryTests` and `TestInvalidConnections` — that declare no `TargetFramework` at all and carry Visual Studio 2005 markers (`ProductVersion 8.0.50727`, `ToolsVersion 3.5`, `OldToolsVersion 2.0`). All seven are dormant, referenced by no solution the build touches, and buildable by nobody today. Modernising code nothing exercises is a decision to take deliberately rather than blind; retiring them is the more honest option and is worth a separate look. |
10261026
|| **Decide the OpenSSL branch** | 4.0.x is not LTS and dies 14 May 2027. Decide by Q1 2027 whether to follow to the next LTS or move back to 3.5. |
10271027
|| **Declare a supported Windows floor** | Server 2019 / Windows 10 21H2, effective with the first release after 12 Jan 2027. Zero code cost — Server 2019 is still `_WIN32_WINNT=0x0A00`, and that macro should *not* be raised. |
1028-
| | **Artifact signing** | SBOMs are already attached to every release in both SPDX and CycloneDX. The gap is signing: Authenticode on the installer and binaries, and ideally Sigstore for the release artefacts. This is the supply-chain item that is actually missing, and the CRA makes update integrity a formal requirement if scope ever changes. |
1028+
| 🔄 | **Artifact signing** | **Sigstore half done.** [`sign-release.yml`](.github/workflows/sign-release.yml) signs every release asset with cosign, keyless via OIDC -- deliberately keyless, because for a single-maintainer project a long-lived signing key stored in Actions is itself the thing most worth stealing. The signature is verified in the same job, so a malformed bundle cannot reach a user. **Authenticode is still open, and the two are complementary rather than alternatives:** SmartScreen and the UAC prompt care about Authenticode and not about Sigstore, so cosign gives verifiability to anyone who checks while Windows still shows an unknown-publisher warning. Azure Trusted Signing is the realistic route. |
10291029
|| **security.txt (RFC 9116)** | Served at `/.well-known/security.txt` by the web services listener, with a derived (never hardcoded) `Expires`, a `Policy` pointing at `SECURITY.md`, and a deliberate refusal to serve anything at all when no contact is configured — a placeholder address is worse than no file. Note it inherits the listener caveat above: the web services ports default to 0, and the server now says so at startup. |
1030-
| | **OpenSSF Scorecard / OSPS Baseline** | Run it, publish the score, fix what is cheap. Buyers increasingly ask. |
1030+
| | **OpenSSF Scorecard** | [`scorecard.yml`](.github/workflows/scorecard.yml), weekly and on push, publishing SARIF to code scanning so supply-chain posture sits beside the CodeQL findings. Deliberately **not** published to the public OpenSSF badge API -- there is no reason to send repository telemetry to a third party to obtain a number we can read ourselves. Expect Branch-Protection and Code-Review to fail by design on a single-maintainer repository; the actionable checks are Pinned-Dependencies, Token-Permissions and Dangerous-Workflow. OSPS Baseline remains open. |
10311031
| ⏸️ | **SQL Server Compact** | Long dead upstream and the one real dependency liability. Migration targets are SQLite or LocalDB. Deferred because it is a data-migration project for existing installs, not a swap, and no date forces it. |
10321032
| ⏸️ | **Boost and C++ standard upgrades** | No forcing function. Move when there is a reason. |
10331033

@@ -1038,7 +1038,7 @@ Future-proofing: deployment and operations
10381038
|:-:|---|---|
10391039
|| **Document the silent-install contract** | Done — see [README](README.md#unattended-install). Two limitations are now written down rather than discovered: the administrator password cannot be set on the command line at all, and a silent *upgrade* therefore cannot supply it either. Originally: The Inno script *already* branches on `WizardSilent()` and forwards credentials to `DBSetupQuick`, and already checks both `Exec` success and the child exit code so a failed schema upgrade cannot masquerade as success. None of that is documented, so an admin automating a hundred installs has to read the Pascal. Publish the switches and exit codes. Cheapest high-value win on this list. |
10401040
|| **Migration *into* the server** | The biggest genuine opportunity here and currently unexploited: with Microsoft turning off Basic auth and EWS, people are moving. There is no documented import path. Competitors have imapsync, `doveadm import`, PST and Maildir/mbox routes. |
1041-
| | **Configuration as code** | The highest-value discretionary item. Config lives in a database plus an INI file, which is the right architecture; the missing piece is a dump/diff/apply CLI so config is reviewable and reproducible. Note the Unix competitors offer less here than the "declarative config" framing suggests. |
1041+
| | **Configuration as code** | [`build/hmconfig.ps1`](build/hmconfig.ps1) -- `export`, `diff` and `apply` the whole configuration as reviewable JSON, over the COM API so it can never disagree with the rules the server itself enforces. Two design decisions worth knowing: **`apply` is a dry run without `-Force`, and deletions additionally require `-AllowDelete`**, because deleting an account deletes its mail and a stale checkout, a partial export or a mistyped domain must not be able to do that -- additive and update operations are the default precisely because they cannot lose mail. And **passwords are never exported**: the server stores hashes, a hash is not a password, and a config file that silently reset every mailbox password on apply would be far worse than one that cannot set them at all. New accounts get a random password that must be reset. |
10421042
|| **Prometheus naming fixes** | The bulk is idiomatic — `hmailserver_` prefix, `_total` on counters, `_seconds` base units, protocol as a label. Five cheap deviations: `hmailserver_state` is a numeric enum where OpenMetrics defines StateSet; `uptime_seconds` should be `start_time_seconds` as a Unix timestamp; no `build_info`; the two latency metrics are summaries with no quantiles, so only a mean is computable — histograms with buckets would give p95/p99; `database_up` collides conceptually with Prometheus's synthetic `up`. |
10431043
|| **Per-domain and per-account metric labels** | Every counter is global, which is what stops metrics becoming reporting. |
10441044
|| **Ship Grafana dashboard JSON in the repo** | [`hmailserver/docs/grafana-dashboard.json`](hmailserver/docs/grafana-dashboard.json) — 16 panels, built against the metric names the exporter actually publishes rather than the ones it should. The panel descriptions state where the exporter is weak, so the dashboard does not quietly imply a tail-latency signal that is not there. |

hmailserver/docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Elsewhere in the repository
2121
| | |
2222
|---|---|
2323
| [README.md](../../README.md) | What the server does, how to build it, how to install it |
24+
| [build/hmconfig.ps1](../../build/hmconfig.ps1) | Putting the server configuration in version control: export it as JSON, review a change as a diff, apply it to another machine. `apply` is a dry run unless you pass `-Force`, and deletions need `-AllowDelete` on top of that. |
2425
| [ARCHITECTURE.md](../../ARCHITECTURE.md) | Changing the code: the module map, where a given change belongs, and the constraints that have caused real bugs here |
2526
| [Roadmap.md](../../Roadmap.md) | The full capability matrix — what exists, what is planned, what is deliberately not, and every known defect |
2627
| [.github/SUPPORT.md](../../.github/SUPPORT.md) | Where to ask what: issues, discussions, or the hMailServer forum |

0 commit comments

Comments
 (0)