# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [3.5.0] - 2026-05-24 **Release status:** **3.3.4** is the last published release. **3.5.0** consolidates all changes since **3.3.4** (HA, Engine/Vault alignment, client libraries, Web UI shared packages). ### Breaking - **HA / interactive ACME:** Removed **`IPrimaryReplicaWorkload`**, **`PrimaryReplicaGate`**, **`PrimaryReplicaShutdownHostedService`**, and **`CertsFlowPrimaryReplica`**. All replicas may run configure-client, init, orders, challenge completion, certificate download, apply, and revoke. The API no longer returns **HTTP 503** with **`urn:maksit:certs-ui:primary-replica-required`**. Clients that retried on that signal should use normal error semantics only. - **HTTP-01 challenge:** **`AcmeChallengeAsync`** no longer writes tokens under **`AcmeFolder`** or reads legacy on-disk files. Challenge text is served from PostgreSQL only; ingress must reach **`GET /.well-known/acme-challenge/{token}`** on this app (or equivalent). - **Startup / filesystem:** Removed the shared **`init`** marker under **`DataFolder`**, **`AcmeFolder`**, and **`DataFolder`** settings, and default server **acme**/**data** PVC mounts. Followers wait until the database reports at least one user. - **Configuration:** **`CertsUIEngineConfiguration`** renamed to **`CertsEngineConfiguration`** in appsettings, Helm **`values.yaml`**, and secrets templates. - **Engine layout:** **`Persistance`** → **`Persistence`**; **`CertsLinq2DbMapping`** → **`CertsUILinq2DbMapping`**. - **Engine query ports (Vault-style):** **`IUserQueryService`**, **`IApiKeyQueryService`**, and **`IApiKeyEntityScopeQueryService`** use synchronous **`Search`** / **`Count`** with optional **`Expression>?`** predicates, **`skip` / `limit`**, and **`Result`** types — not async paged **`Search…Async`** with string filters. - **ACME session persistence:** **`IAcmeSessionStore`**, **`AcmePostgresSessionStore`**, **`AcmeSessionSnapshot`**, and **`AcmeSessionJsonSerializer`** removed. **`ILetsEncryptService`** depends on **`IAcmeSessionPersistenceService`** (**`AcmeSessionPersistenceServiceLinq2Db`**) for **`acme_sessions`** JSON load/save. - **`ICertsFlowDomainService`:** Constructor takes **`IRegistrationCacheDomainService`** instead of **`IRegistrationCachePersistenceService`**. - **Web UI:** Removed the in-repo shared UI stack (layout, DataTable, form editors, deep/enum helpers, local toast). The SPA depends on **`@maks-it.com/webui-core`**, **`webui-components`**, and **`webui-contracts`** (private npm registry; **`src/MaksIT.WebUI/.npmrc`**). - **E2E tests:** **`CertsUiApiKeyE2ETests`** removed from **`MaksIT.CertsUI.Tests`**; API-key E2E lives in **`MaksIT.CertsUI.Client.Tests`** (`Category=E2E`). - **Terms of Service API:** Interactive ACME uses only **`GET /api/certs/{sessionId}/terms-of-service`** (stateless **`isStaging`** variant removed). - **Startup migrations:** Removed legacy EF-era baseline, **`VersionInfo` → `version_info`** rename repair, PascalCase → snake_case column repair, and **`RunMigrationsService.BaselineVersion`**. **`MigrateUp`** expects schema managed only by in-process FluentMigrator migrations. - **Database:** **`users.JwtTokensJson`** removed; sessions live in **`jwt_tokens`** only (no duplicate JSON on **`users`**). - **Deprecated host:** Removed legacy **`MaksIT.Webapi`** and **`MaksIT.Webapi.Tests`** in favor of **`MaksIT.CertsUI`** / **`MaksIT.CertsUI.Tests`**. ### Added - **`MaksIT.CertsUI.Client`:** HTTP client library with API key auth (**`ICertsUIClient`**, **`CertsUIClient`**, **`ServiceCollectionExtensions`**, **`CertsUIApiException`**). - **`MaksIT.CertsUI.Contracts`:** Shared wire types (**`AccountResponse`**, **`HostnameResponse`**, **`RuntimeInstanceIdResponse`**). - **`MaksIT.CertsUI.Client.PowerShell`:** Binary module (**`Connect-CertsUI`**, **`Get-CertsUIAccounts`**, **`Invoke-CertsUICreateAccount`**, etc.; requires **PowerShell 7** on **.NET 10**). - **`MaksIT.CertsUI.Client.Tests`:** Unit tests plus relocated API-key E2E suite. - **PowerShell E2E:** **`src/e2e-tests/`** scenarios and **`Test-CertsUiApiKeyE2E.ps1`** / **`.bat`** (Vault-style **`CERTSUI_E2E_CREDENTIALS`**). - **Docs:** **`assets/docs/POWERSHELL_CLIENT_MODULE.md`**, **`assets/docs/ARCHITECTURE_LAYERING.md`**, HA and login architecture docs; **[CONTRIBUTING.md](CONTRIBUTING.md)** links layering doc and **`dotnet test`** guidance. - **New backend host:** **`MaksIT.CertsUI`** WebAPI with controllers, JWT and JWT-or-API-key authorization, hosted services, and mapping/configuration abstractions. - **Engine platform:** Domain-oriented **`MaksIT.CertsUI.Engine`** (`Domain`, `Dto`, `DomainServices`, `Persistence`, `QueryServices`, `Infrastructure`, `FluentMigrations`) with Linq2Db mappings and migration services. - **Engine (Vault parity):** **`EntityScopeBase`**; **`UserAuthorization`** / **`ApiKeyAuthorization`** aggregates; split **`UserEntityScope`** / **`ApiKeyEntityScope`**; **`IdentityDomainService`** / **`ApiKeyDomainService`** refactor aligned with MaksIT.Vault. - **`ExpressionCompose`** for composing nested Linq2Db predicates. - **`IRegistrationCacheDomainService`** / **`RegistrationCacheDomainService`**, **`RegistrationCachePayloadDocument`**, and **`RegistrationCachePayloadJsonTests`**. - **`IAcmeSessionPersistenceService`**, **`AcmeSessionPersistenceServiceLinq2Db`**, and **`AcmeSessionPayloadMapper`** for PostgreSQL-backed ACME **`State`**. - **`PostgresStartupWait`** — waits for PostgreSQL and retries FluentMigrator **`MigrateUp`** on transient startup failures. - **API / RBAC:** **`GetActingJwtTokenData`** maps API keys to a synthetic **`JwtTokenData`** principal; **`RbacHelpers.EnsureActorMayAssignGlobalAdmin`** / **`EnsureActorMayPatchGlobalAdminFlag`**. - **HA runtime coordination:** DB-backed HTTP-01 challenge persistence and runtime leases (**`acme_http_challenges`**, **`app_runtime_leases`**, **`acme_sessions`**, **`terms_of_service_cache`**); coordinated bootstrap and renewal execution. - **Kubernetes:** Per-component Helm **`replicaCount`**, PodDisruptionBudget support, health endpoints (**`/health/live`**, **`/health/ready`**), optional **`kubernetesUpstreamHosts`** for in-cluster YARP upstreams. - **LetsEncrypt:** Per-host ACME rate-limit cooldown on **`RegistrationCache`**; **`AcmeProblemKind`** enumeration; in-memory **`AcmeSessionStore`** (later superseded by PostgreSQL persistence); partial **`LetsEncryptService`** files; **`State.TryGetAccountKey`**; **`LetsEncrypt.Tests`** for retry parsing and cooldown JSON. - **Frontend:** Users/API Keys pages and forms; identity/API-key UX with list/filter/paging (later migrated to **`webui-*`** packages). - **Test suite:** **`MaksIT.CertsUI.Tests`** and **`MaksIT.CertsUI.Engine.Tests`** with Postgres/WebAPI fixtures. - **Release tooling:** **`DotNetDockerPush`** per-image **`versionEnvFiles`** (temporary **`VITE_APP_VERSION`** rewrite) and optional per-image **`contextPath`** (upstreamed to **maksit-repoutils** 1.0.11). - **Helm / config:** **`certsEngineConfiguration.autoSyncSchema`** (default **`true`**) for add-only column sync on startup. ### Changed - **Identity / API key controllers:** Use **`GetActingJwtTokenData`** instead of JWT-only **`GetJwtTokenData`** for user and API-key CRUD/search. - **IdentityService** / **ApiKeyService:** Build predicates and call **`Count`** + **`Search`** on query services; thin-search wiring (Pattern B). - **Web UI:** Migrated to **`@maks-it.com/webui-*`** packages; **`createWebUiHttpClient`**; **`apiRoutes.ts`**; pages under **`src/pages/`**; **`webUi/dataSources.ts`**; models reorganized under domain folders aligned with **`webui-contracts`**. Earlier step: **`axiosConfig`** helpers return **`{ payload, status, ok }`**. - **ACME sessions:** Let's Encrypt client **`State`** persisted in PostgreSQL **`acme_sessions`** so any replica can continue after load balancing. - **LetsEncrypt / `HttpClient`:** **`ConfigureClient`** uses absolute ACME directory URL instead of assigning **`BaseAddress`** on the shared client. - **`CertsFlowDomainService`:** **`PurgeStaleHttpChallengesAsync`** (HTTP-01 cleanup); **`AutoRenewal`** calls it before renewal work; skips hostnames in ACME cooldown window. - **`LetsEncryptService`:** Uses **`IAcmeSessionPersistenceService`**; dropped **`Newtonsoft.Json`** from Engine (STJ-only JSON paths). - **`CacheService`:** Thin façade over **`IRegistrationCacheDomainService`**. - **Bootstrap / renewal:** **`InitializationHostedService`** acquires **`certs-ui-bootstrap`** (**`RuntimeLeaseNames.BootstrapCoordinator`**) for empty-database admin creation, then releases. **`AutoRenewal`** acquires **`certs-ui-renewal-sweep`** (**`RuntimeLeaseNames.RenewalSweep`**) per sweep. Lease name **`certs-ui-primary`** replaced by bootstrap and renewal sweep constants. - **HA / ToS cache:** Terms of Service PDF caching moved from pod filesystem to PostgreSQL **`terms_of_service_cache`** with TTL/HTTP validators. - **FluentMigrator:** Standard **`VersionInfo`** table and columns; **`.ScanIn(…).For.All()`** discovery; migrations run in **`Program.cs`** after **`Build()`** before hosted services; logged host/database and migration count; coordination DDL repair after **`MigrateUp`** when tables missing despite applied version. - **Schema sync:** **`AutoSyncSchema`** add-only (**`ADD COLUMN IF NOT EXISTS`**; no DROP); desired map includes **`users.IsActive`**, **`TwoFactorSharedKey`**. - **Docker Compose / Helm:** YARP upstream env vars for in-cluster vs Compose hostnames; **`components.server.service.sessionAffinity`** defaults to **`false`** (stateless LB); **`certsClientRuntime.apiUrl`** defaults to **`/api`**; optional **`preStop`** sleep and **`terminationGracePeriodSeconds`** for rolling updates. - **Container image:** **`MaksIT.CertsUI`** Dockerfile installs **`libgssapi-krb5-2`** for Npgsql GSS support on slim images. - **Namespace and solution layout:** Standardized around **`MaksIT.CertsUI*`** host/engine split; ACME contracts moved from legacy **`Entities`/`Models`** into **`Domain`** / **`Dto`**. - **LetsEncrypt:** Broader nullable annotations; **`CachedHostname`** primary constructor; certificate loading via **`X509Certificate2.CreateFromPem`** / **`X509CertificateLoader.LoadCertificate`**. - **Integration tests:** **`InMemoryUserStore`**, **`CacheServiceTests`**, **`CertsFlowServiceTests`**, **`ApiKeyQueryServiceIntegrationTests`**, **`AccountServicePatchAccountIntegrationTests`** aligned with new ports. - **README:** Architecture references, HA guidance, E2E instructions for dotnet test and PowerShell scenarios. ### Fixed - **Startup / HA:** Bootstrap lease no longer blocks extra replicas when users already exist; cooperative cancel on host shutdown; **`CoordinationTableProvisioner`** with explicit **`public.*`** DDL; **`RuntimeLeaseServiceNpgsql`** uses **`public.app_runtime_leases`**; post-migrate verification for coordination tables. - **FluentMigrator:** Empty connection string no longer puts runner in connectionless/preview mode; throw when engine connection string missing; maintenance DB bootstrap warns instead of failing when role cannot **`CREATE DATABASE`**. - **Identity / PostgreSQL:** **`users.JwtTokensJson`** column dropped; new inserts no longer hit **`23502`**; token rows normalized into **`jwt_tokens`**. - **Helm / reverseproxy:** YARP upstreams no longer default to unresolvable Compose hostnames in Kubernetes when **`kubernetesUpstreamHosts`** is enabled. - **LetsEncrypt:** **`RevokeCertificate`** fails correctly on non-success; disposes HTTP response on success; **`NewOrder`** logs authorization status; **`TryGetCachedCertificate`** returns **`false`** when private key blob missing. - **`AccountService.PatchAccountAsync`:** Returns account built from cache after reload, not stale in-memory instance. - **WebUI Terms of Service:** PDF worker loaded from Vite-bundled asset (**`pdf.worker.min.mjs?url`**) for dev and production. ### Removed - **`CertsFlowPrimaryReplica`**, **`PrimaryReplicaRequiredObjectResult`**, **`CertsFlowResultExtensions`** / **`ToCertsFlowActionResult`**; **`CertsFlowController`** uses **`ToActionResult()`**. - **Web UI (local):** **`DataTable`**, **`FormLayout`**, **`Layout`**, **`LazyLoadTable`**, editor components, **`Toast`**, **`Offcanvas`**, **`useFormState`**, **`localStorage/identity`**, legacy **`functions/`** / **`models/`** trees superseded by **`webui-*`** packages. - **Web UI:** Primary-replica **503** auto-retry in **`axiosConfig.ts`**. - **Configuration / Helm:** **`AcmeFolder`**, **`DataFolder`**, default **acme**/**data** PVC mounts; **`AddMemoryCache()`** host registration (unused). - **Legacy engine layout:** Obsolete top-level engine files, old **`.vscode`** project files. ### Upgrade notes (from 3.3.4) **3.3.4** is the last published release. **3.5.0** uses a new host, PostgreSQL-backed registration cache, and FluentMigrator schema — there is no in-place migration of Let's Encrypt account material from **3.3.4**. Use backup → install → restore: 1. **On 3.3.4 (before upgrade):** Sign in to the Web UI, open **Utilities**, and **Download cache files** (full registration-cache ZIP). Store the file safely — it contains your Let's Encrypt account keys and cached certificate data. Optionally export Postman/API backups of any other settings you rely on. 2. **Prepare 3.5.0:** Provision a **new empty PostgreSQL database** (or drop and recreate the engine database). Update Helm/secrets: rename **`certsUIEngineConfiguration`** → **`certsEngineConfiguration`**, set **`ConnectionString`**, and remove legacy **`acme`** / **`data`** bind mounts from **`docker-compose.override.yml`** if present. 3. **Deploy 3.5.0:** Install the new server, client, and reverse-proxy images (pin tags to **`3.5.0`**, not **`latest`**). On first start, FluentMigrator creates the schema; bootstrap creates the default admin when the database has no users. 4. **Restore accounts:** Sign in on **3.5.0**, open **Utilities**, and **Upload cache files** with the ZIP from step 1. Verify accounts and certificates in the UI before decommissioning **3.3.4**. 5. **Re-create operators:** Users, API keys, and JWT sessions from **3.3.4** are not migrated automatically — recreate users/API keys in **3.5.0** as needed. **Also check when upgrading:** - **Operations:** Retarget alerts from lease **`certs-ui-primary`** to **`certs-ui-bootstrap`** and **`certs-ui-renewal-sweep`**. - **Web UI build:** Configure npm auth for **`@maks-it.com/*`** (see **`src/MaksIT.WebUI/.npmrc`**) when building the client image locally. - **E2E:** **`dotnet test`** on **`MaksIT.CertsUI.Client.Tests`** with **`Category=E2E`**; set **`CERTSUI_E2E_EXPECT_MIN_DISTINCT_INSTANCES=2`** for HA (PowerShell E2E defaults to **1** for Docker Compose). - **Repo utils:** **`utils/`** refreshed from **maksit-repoutils** 1.0.14 (`engines/`, `plugins/`, `modules/`, `tools/`). Run **`utils\Invoke-ReleasePackage.bat`** for release; **`utils\Invoke-TestEngine.bat`** for tests and coverage badges; refresh via **`utils\Update-RepoUtils.bat`** (set **`dryRun`: false** in **`utils/tools/Update-RepoUtils/scriptSettings.json`**). ## [3.3.4] - 2026-04-01 ### Added - `MaksIT.Webapi.Tests`: service-level unit tests (settings, cache, identity, agent, account, certs flow) and domain tests for `Settings`. - Postman collections under `src/Postman` updated to match current `MaksIT.Webapi` routes, JWT flow, and cache endpoints. ### Fixed - WebUI Terms of Service (Let's Encrypt): PDF viewer loads `pdfjs-dist` worker from a Vite-bundled asset (`pdf.worker.min.mjs?url`) so rendering works in dev and production instead of failing on missing or wrong worker URLs. - `AccountService.PatchAccountAsync` returns the account built from the cache after reload, not a stale in-memory instance. ## [3.3.3] - 2025-12-20 ### Changed - Relicensed project from GPL-3.0 to Apache-2.0. ## [3.3.2] - 2025-12-20 ### Changed - Minimal Helm chart and documentation improvements. ## [3.3.1] - 2025-11-22 ### Changed - Public release following the v3.3.0 pre-release. ## [3.3.0] - 2025-11-15 ### Changed - Pre-release of the v3.3.x line. ## [3.2.0] - 2025-09-11 ### Added - New WebUI with authentication. ## [3.1.0] - 2024-08-11 ### Changed - Stabilized release following v3.0.0. ## [3.0.0] - 2024-05-31 ### Added - WebAPI and containerization. ## [2.0.0] - 2019-11-01 ### Changed - Dependency injection pattern implementation. ## [1.0.0] - 2019-06-29 ### Added - Initial release.