chore: consolidate platform split checks
This commit is contained in:
@@ -1,8 +1,17 @@
|
||||
# GovOPlaN Release Dependencies
|
||||
|
||||
Release installs must not depend on sibling checkout paths. Local development can keep editable installs and `file:` WebUI links, but release packaging should resolve modules from tagged git refs or from a package registry.
|
||||
This document owns release package composition, signed package catalogs,
|
||||
license checks, catalog publishing, migration baselines, and the final release
|
||||
checklist.
|
||||
|
||||
## Backend
|
||||
Operator runtime configuration and module install/uninstall execution live in
|
||||
`DEPLOYMENT_OPERATOR_GUIDE.md`.
|
||||
|
||||
## Backend Packages
|
||||
|
||||
Release installs must not depend on sibling checkout paths. Local development
|
||||
can keep editable installs and `file:` WebUI links, but release packaging must
|
||||
resolve modules from tagged git refs or from a package registry.
|
||||
|
||||
Local development:
|
||||
|
||||
@@ -18,280 +27,41 @@ cd /mnt/DATA/git/govoplan-core
|
||||
./.venv/bin/python -m pip install -r requirements-release.txt
|
||||
```
|
||||
|
||||
`.[server]` is resolved relative to the current working directory. If you create the virtualenv elsewhere, still run the install command from the core checkout:
|
||||
`.[server]` is resolved relative to the current working directory. If you
|
||||
create the virtualenv elsewhere, still run the install command from the core
|
||||
checkout:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
/tmp/govoplan-release-test/bin/python -m pip install -r requirements-release.txt
|
||||
```
|
||||
|
||||
`requirements-release.txt` pins the module repositories to the release tag. Update those refs when cutting a release:
|
||||
`requirements-release.txt` pins the module repositories to the release tag.
|
||||
Update those refs when cutting a release:
|
||||
|
||||
```text
|
||||
govoplan-access git@git.add-ideas.de:add-ideas/govoplan-access.git v0.1.4
|
||||
govoplan-admin git@git.add-ideas.de:add-ideas/govoplan-admin.git v0.1.4
|
||||
govoplan-tenancy git@git.add-ideas.de:add-ideas/govoplan-tenancy.git v0.1.4
|
||||
govoplan-policy git@git.add-ideas.de:add-ideas/govoplan-policy.git v0.1.4
|
||||
govoplan-audit git@git.add-ideas.de:add-ideas/govoplan-audit.git v0.1.4
|
||||
govoplan-files git@git.add-ideas.de:add-ideas/govoplan-files.git v0.1.4
|
||||
govoplan-mail git@git.add-ideas.de:add-ideas/govoplan-mail.git v0.1.4
|
||||
govoplan-campaign git@git.add-ideas.de:add-ideas/govoplan-campaign.git v0.1.4
|
||||
govoplan-calendar git@git.add-ideas.de:add-ideas/govoplan-calendar.git v0.1.4
|
||||
govoplan-access git@git.add-ideas.de:add-ideas/govoplan-access.git v0.1.6
|
||||
govoplan-admin git@git.add-ideas.de:add-ideas/govoplan-admin.git v0.1.6
|
||||
govoplan-tenancy git@git.add-ideas.de:add-ideas/govoplan-tenancy.git v0.1.6
|
||||
govoplan-organizations git@git.add-ideas.de:add-ideas/govoplan-organizations.git v0.1.6
|
||||
govoplan-identity git@git.add-ideas.de:add-ideas/govoplan-identity.git v0.1.6
|
||||
govoplan-policy git@git.add-ideas.de:add-ideas/govoplan-policy.git v0.1.6
|
||||
govoplan-audit git@git.add-ideas.de:add-ideas/govoplan-audit.git v0.1.6
|
||||
govoplan-files git@git.add-ideas.de:add-ideas/govoplan-files.git v0.1.6
|
||||
govoplan-mail git@git.add-ideas.de:add-ideas/govoplan-mail.git v0.1.6
|
||||
govoplan-campaign git@git.add-ideas.de:add-ideas/govoplan-campaign.git v0.1.6
|
||||
govoplan-calendar git@git.add-ideas.de:add-ideas/govoplan-calendar.git v0.1.6
|
||||
```
|
||||
|
||||
## Runtime Module Package Changes
|
||||
## WebUI Packages
|
||||
|
||||
The admin module manager can hot-enable and hot-disable packages that are
|
||||
already installed. It does not install or uninstall Python/npm packages from
|
||||
inside the running server.
|
||||
Local development uses `webui/package.json`, which may point at sibling module
|
||||
checkouts while active development is happening.
|
||||
|
||||
For runtime package changes, create an operator install plan in Admin > System >
|
||||
Modules. The module manager shows the trusted installer preflight status and
|
||||
blocks unsafe uninstalls before the operator touches packages.
|
||||
|
||||
Preflight from the server shell:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --format shell
|
||||
```
|
||||
|
||||
Apply from a controlled operator shell while maintenance mode is active:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --apply --build-webui
|
||||
```
|
||||
|
||||
For real install/uninstall work, prefer supervised mode with the deployment's
|
||||
restart command and health endpoint:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--supervise \
|
||||
--migrate \
|
||||
--build-webui \
|
||||
--health-url http://127.0.0.1:8000/health \
|
||||
--restart-command '<restart govoplan server>'
|
||||
```
|
||||
|
||||
To let the admin UI trigger package work without executing pip/npm inside a
|
||||
FastAPI request, run the installer daemon in a separate operator shell. This is
|
||||
the preferred development/early-production mode for now because the operator can
|
||||
watch output, stop before queueing risky changes, and keep restart commands
|
||||
deployment-specific:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--daemon \
|
||||
--migrate \
|
||||
--build-webui \
|
||||
--database-backup-command 'pg_dump --format=custom "$GOVOPLAN_DATABASE_URL" > "$GOVOPLAN_DATABASE_BACKUP_PATH"' \
|
||||
--database-restore-check-command 'pg_restore --list "$GOVOPLAN_DATABASE_BACKUP_PATH" >/dev/null' \
|
||||
--database-restore-command 'pg_restore --clean --if-exists --dbname "$GOVOPLAN_DATABASE_URL" "$GOVOPLAN_DATABASE_BACKUP_PATH"' \
|
||||
--health-url http://127.0.0.1:8000/health \
|
||||
--restart-command '<restart govoplan server>'
|
||||
```
|
||||
|
||||
Admin > System > Modules can then queue the saved install plan as a supervised
|
||||
request. Install rows can be planned directly from the approved package catalog;
|
||||
uninstall rows are generated from installed, disabled modules so the Python
|
||||
distribution and WebUI package names do not need to be typed by hand. The
|
||||
daemon claims one queued request at a time and writes request/run records below
|
||||
`runtime/module-installer`. For process-manager one-shot usage or tests, use
|
||||
`--daemon-once`. The daemon also writes
|
||||
`runtime/module-installer/daemon.status.json`; check it with:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --daemon-status --format json
|
||||
```
|
||||
|
||||
The installer uses a runtime lock, snapshots `pip freeze` plus WebUI
|
||||
`package.json`/`package-lock.json`, writes a run record below
|
||||
`runtime/module-installer/runs`, and marks planned rows as applied only after
|
||||
all commands succeed. When `--migrate` is used with a `sqlite:///` database URL,
|
||||
the installer also snapshots the SQLite database with SQLite's backup API before
|
||||
running migrations. For other database engines, pass external backup/restore
|
||||
hooks:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--supervise \
|
||||
--migrate \
|
||||
--database-backup-command 'pg_dump --format=custom "$GOVOPLAN_DATABASE_URL" > "$GOVOPLAN_DATABASE_BACKUP_PATH"' \
|
||||
--database-restore-check-command 'pg_restore --list "$GOVOPLAN_DATABASE_BACKUP_PATH" >/dev/null' \
|
||||
--database-restore-command 'pg_restore --clean --if-exists --dbname "$GOVOPLAN_DATABASE_URL" "$GOVOPLAN_DATABASE_BACKUP_PATH"' \
|
||||
--health-url http://127.0.0.1:8000/health \
|
||||
--restart-command '<restart govoplan server>'
|
||||
```
|
||||
|
||||
The backup command runs before migrations. The restore-check command validates
|
||||
the produced backup artifact before migrations proceed, without restoring over
|
||||
the live database. The restore command is stored in the run record and runs
|
||||
during rollback unless an override is passed to `--rollback`.
|
||||
|
||||
Supervised mode treats package command failure, migration failure, restart
|
||||
failure, and health timeout as rollback triggers. It restores the Python/WebUI
|
||||
package snapshots, re-runs the restart command when supplied, and restores the
|
||||
saved install plan state so the operator can correct it. The supervisor must
|
||||
run outside the FastAPI server process; the admin UI saves and validates plans
|
||||
but does not mutate packages from an HTTP request.
|
||||
|
||||
After a successful install plan, the installer adds installed modules to saved
|
||||
startup state by default so the restarted server can discover and enable them.
|
||||
After a successful uninstall plan, the installer removes uninstalled modules
|
||||
from saved startup state by default. Use
|
||||
`--no-activate-installed-modules` or
|
||||
`--keep-uninstalled-modules-in-desired` only for staged rollout workflows that
|
||||
will update module state separately.
|
||||
|
||||
Uninstall is non-destructive by default. A planned uninstall row can set
|
||||
`destroy_data: true` to request destructive module retirement. The module must
|
||||
provide an automated retirement provider, and the installer snapshots the
|
||||
database before dropping module-owned tables. For SQLite this uses the built-in
|
||||
snapshot path; for PostgreSQL or another non-SQLite database, provide
|
||||
`--database-backup-command`, `--database-restore-check-command`, and
|
||||
`--database-restore-command`. If a destructive run fails during package removal,
|
||||
the installer restores the database snapshot before returning the failed run
|
||||
result; supervised restart/health failures also roll back through the normal
|
||||
supervisor path.
|
||||
|
||||
Package rollback is automatic. SQLite database rollback is automatic for
|
||||
installer runs that used `--migrate` and captured a database snapshot.
|
||||
Non-SQLite rollback is automatic when the run used
|
||||
`--database-backup-command` and `--database-restore-command`; otherwise migrated
|
||||
non-SQLite runs are blocked before package changes are applied.
|
||||
|
||||
Rollback uses the saved run snapshot:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --rollback <run-id>
|
||||
govoplan-module-installer --rollback <run-id> --database-restore-command '<override restore command>'
|
||||
```
|
||||
|
||||
Database hook commands run with these environment variables:
|
||||
|
||||
- `GOVOPLAN_INSTALLER_RUN_DIR`: the run snapshot directory
|
||||
- `GOVOPLAN_DATABASE_URL`: the configured database URL
|
||||
- `GOVOPLAN_DATABASE_BACKUP_PATH`: a suggested backup artifact path inside the
|
||||
run directory
|
||||
- `GOVOPLAN_DATABASE_BACKUP_METADATA`: optional JSON metadata path that backup
|
||||
commands may write for operator diagnostics
|
||||
|
||||
Avoid embedding secrets directly in commands; prefer environment variables,
|
||||
service credentials, or deployment-local secret injection.
|
||||
|
||||
Inspect installer history and lock state from the operator shell:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --list-runs --format json
|
||||
govoplan-module-installer --show-run <run-id> --format json
|
||||
govoplan-module-installer --lock-status --format json
|
||||
govoplan-module-installer --list-requests --format json
|
||||
govoplan-module-installer --show-request <request-id> --format json
|
||||
govoplan-module-installer --cancel-request <request-id> --format json
|
||||
govoplan-module-installer --retry-request <request-id> --format json
|
||||
```
|
||||
|
||||
Package catalogs can be local files or remote static resources, for example
|
||||
served by `govoplan-web`. Set `GOVOPLAN_MODULE_PACKAGE_CATALOG` for a local file
|
||||
or `GOVOPLAN_MODULE_PACKAGE_CATALOG_URL` for a remote catalog matching
|
||||
`docs/module-package-catalog.example.json`; the admin UI will show those entries
|
||||
and can save them into the install plan. This keeps the release approval
|
||||
decision outside the running server while avoiding hand-typed package refs.
|
||||
Remote catalogs can be cached for offline inspection:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_URL=https://govoplan.example/catalogs/v1/channels/stable.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_CACHE=/srv/govoplan/runtime/catalog-cache/stable.json
|
||||
```
|
||||
|
||||
Validate the catalog before handing it to operators:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer --validate-package-catalog docs/module-package-catalog.example.json --format json
|
||||
```
|
||||
|
||||
Release catalogs should be signed, channel-gated, expiring, and sequence
|
||||
tracked. The supported signing format is an Ed25519 signature over the
|
||||
canonical catalog JSON object with the `signature` and `signatures` fields
|
||||
removed. Core accepts the legacy single `signature` field and the newer
|
||||
`signatures` array used during key rotation. Sign a catalog with:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--sign-package-catalog docs/module-package-catalog.example.json \
|
||||
--catalog-signing-key-id release-key-1 \
|
||||
--catalog-signing-private-key /path/to/ed25519-private.pem
|
||||
```
|
||||
|
||||
Validate an approved release catalog with:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--validate-package-catalog docs/module-package-catalog.example.json \
|
||||
--require-signed-catalog \
|
||||
--approved-catalog-channel stable \
|
||||
--catalog-trusted-key release-key-1=<base64-ed25519-public-key> \
|
||||
--format json
|
||||
```
|
||||
|
||||
For the admin UI/daemon path, configure the same policy through environment
|
||||
variables:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG=/path/to/catalog.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_APPROVED_CHANNELS=stable
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS='{"release-key-1":"<base64-ed25519-public-key>"}'
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_SEQUENCE_STATE=/srv/govoplan/runtime/catalog-sequences.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE=true
|
||||
```
|
||||
|
||||
Trusted keys can also be loaded from
|
||||
`GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE`. A URL-backed keyring is
|
||||
supported for development and tightly controlled deployments through
|
||||
`GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_URL` plus
|
||||
`GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_CACHE`, but production systems
|
||||
should pin trusted keys locally.
|
||||
|
||||
Catalog entries can declare `license_features`. If
|
||||
`GOVOPLAN_LICENSE_ENFORCEMENT=true`, core blocks planning catalog installs
|
||||
whose required features are not present in the configured offline license:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_LICENSE_FILE=/srv/govoplan/license.json
|
||||
GOVOPLAN_LICENSE_TRUSTED_KEYS_FILE=/srv/govoplan/trust/license-keyring.json
|
||||
GOVOPLAN_LICENSE_ENFORCEMENT=true
|
||||
```
|
||||
|
||||
See `docs/CATALOG_TRUST_AND_LICENSING.md` for key rotation, replay protection,
|
||||
and licensing details. See `docs/RELEASE_CATALOG_WORKFLOW.md` for the concrete
|
||||
release-machine workflow that generates signing keys and publishes signed
|
||||
catalogs through `govoplan-web`. Unsigned catalogs remain usable for local
|
||||
development when signature enforcement is off, but the admin UI labels them as
|
||||
unsigned.
|
||||
|
||||
Install rows must use tagged package/git refs or registry packages, not local
|
||||
`file:` or workspace links. The installer daemon can run `npm install` and
|
||||
`npm run build` for WebUI package changes; that is the supported path. Browser
|
||||
remote bundles are still experimental and should be treated as a controlled
|
||||
deployment option, not the normal install/uninstall mechanism.
|
||||
|
||||
Module manifests can declare core compatibility bounds and uninstall guard
|
||||
providers. Preflight blocks incompatible manifest contracts/core versions,
|
||||
active modules, desired startup state, protected modules, and active dependents.
|
||||
Default uninstall is non-destructive: module data and schema remain dormant if
|
||||
the package is removed. Persistent-data guards therefore warn by default instead
|
||||
of requiring export/delete. A module that supports explicit data/schema
|
||||
retirement should register a retirement provider. When `destroy_data` is set on
|
||||
an uninstall plan row, that provider is allowed to destroy module-owned data
|
||||
after the installer has captured a database snapshot; otherwise it is used only
|
||||
for preflight reporting.
|
||||
|
||||
## WebUI
|
||||
|
||||
Local development uses `webui/package.json`, which may point at sibling module checkouts while active development is happening.
|
||||
|
||||
Release WebUI installs should use `webui/package.release.json`. It points module dependencies at the same tagged git repositories. After the module tags referenced there exist, generate the committed release lockfile without touching the development package files:
|
||||
Release WebUI installs should use `webui/package.release.json`. It points
|
||||
module dependencies at the same tagged git repositories. After the module tags
|
||||
referenced there exist, generate the committed release lockfile without
|
||||
touching the development package files:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
@@ -300,16 +70,48 @@ cd webui
|
||||
PATH=/home/zemion/.nvm/versions/node/v22.22.3/bin:$PATH /home/zemion/.nvm/versions/node/v22.22.3/bin/npm run build
|
||||
```
|
||||
|
||||
The module repositories include root-level npm package manifests so git installs can resolve `@govoplan/access-webui`, `@govoplan/admin-webui`, `@govoplan/files-webui`, `@govoplan/mail-webui`, `@govoplan/campaign-webui`, and `@govoplan/calendar-webui` from repository roots even though their source lives below `webui/src`.
|
||||
The module repositories include root-level npm package manifests so git
|
||||
installs can resolve `@govoplan/access-webui`, `@govoplan/admin-webui`,
|
||||
`@govoplan/files-webui`, `@govoplan/mail-webui`,
|
||||
`@govoplan/campaign-webui`, and `@govoplan/calendar-webui` from repository
|
||||
roots even though their source lives below `webui/src`.
|
||||
|
||||
The normal release path is automated by `scripts/push-release-tag.sh`: it bumps or accepts the target version, updates Python/WebUI/module manifest versions, commits/tags/pushes the module repositories first, regenerates `webui/package-lock.release.json`, and then commits/tags/pushes core. If the working tree has already been bumped, pass the current version explicitly:
|
||||
### Release Lockfile Strategy
|
||||
|
||||
The supported release composition currently is the full GovOPlaN product: core
|
||||
plus access, admin, tenancy, organizations, identity, policy, audit,
|
||||
dashboard, files, mail, campaign, calendar, docs, and ops. Keep one committed
|
||||
full-product release lockfile at
|
||||
`webui/package-lock.release.json`, generated from
|
||||
`webui/package.release.json` in a clean release workspace. Development
|
||||
`package-lock.json` may continue to point at local `file:` dependencies.
|
||||
|
||||
Frontend module permutations are regression-tested through
|
||||
`GOVOPLAN_WEBUI_MODULE_PACKAGES` and temporary build output, not through
|
||||
committed lockfiles for every possible combination. If a smaller composition
|
||||
becomes a separately shipped product, add an explicit release manifest and
|
||||
lockfile pair for that product, for example
|
||||
`package.release.files-mail.json` and `package-lock.release.files-mail.json`,
|
||||
generated in a clean release workspace from tagged git dependencies.
|
||||
|
||||
## Release Tag Script
|
||||
|
||||
The normal release path is automated by `scripts/push-release-tag.sh`: it bumps
|
||||
or accepts the target version, updates Python/WebUI/module manifest versions,
|
||||
commits/tags/pushes the module repositories first, regenerates
|
||||
`webui/package-lock.release.json`, and then commits/tags/pushes core. If the
|
||||
working tree has already been bumped, pass the current version explicitly:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
scripts/push-release-tag.sh --version 0.1.2
|
||||
scripts/push-release-tag.sh --version 0.1.6
|
||||
```
|
||||
|
||||
The script also includes GovOPlaN roadmap/scaffold module repositories that do not yet have package metadata. Those repositories are committed, tagged, and pushed with the same release tag, but they are tag-only until they contain `pyproject.toml`, module manifests, or WebUI packages. Tag-only repositories are not listed in `requirements-release.txt` or `webui/package.release.json`.
|
||||
The script also includes GovOPlaN roadmap/scaffold module repositories that do
|
||||
not yet have package metadata. Those repositories are committed, tagged, and
|
||||
pushed with the same release tag, but they are tag-only until they contain
|
||||
`pyproject.toml`, module manifests, or WebUI packages. Tag-only repositories
|
||||
are not listed in `requirements-release.txt` or `webui/package.release.json`.
|
||||
|
||||
Current tag-only module repositories:
|
||||
|
||||
@@ -325,7 +127,6 @@ Current tag-only module repositories:
|
||||
- `govoplan-idm`
|
||||
- `govoplan-ledger`
|
||||
- `govoplan-notifications`
|
||||
- `govoplan-ops`
|
||||
- `govoplan-payments`
|
||||
- `govoplan-portal`
|
||||
- `govoplan-reporting`
|
||||
@@ -338,17 +139,486 @@ Current tag-only module repositories:
|
||||
- `govoplan-xrechnung`
|
||||
- `govoplan-xta-osci`
|
||||
|
||||
### Release lockfile strategy
|
||||
## Catalog Trust And Licensing
|
||||
|
||||
The supported release composition currently is the full Multi Seal Mail product: core plus access, admin, tenancy, policy, audit, files, mail, campaign, and calendar. Keep one committed full-product release lockfile at `webui/package-lock.release.json`, generated from `webui/package.release.json` in a clean release workspace. Development `package-lock.json` may continue to point at local `file:` dependencies.
|
||||
GovOPlaN module install and uninstall must remain operator-controlled. The
|
||||
running server may plan and validate package changes, but package mutation is
|
||||
performed by the separate installer daemon or an operator shell during
|
||||
maintenance mode.
|
||||
|
||||
Frontend module permutations are regression-tested through `GOVOPLAN_WEBUI_MODULE_PACKAGES` and temporary build output, not through committed lockfiles for every possible combination. If a smaller composition becomes a separately shipped product, add an explicit release manifest and lockfile pair for that product, for example `package.release.files-mail.json` and `package-lock.release.files-mail.json`, generated in a clean release workspace from tagged git dependencies.
|
||||
`govoplan-web` is the public static distribution surface for official catalog
|
||||
resources:
|
||||
|
||||
- signed module package catalogs, grouped by release channel
|
||||
- public catalog keyrings
|
||||
- public license verification keyrings
|
||||
- examples and operator-facing download paths
|
||||
|
||||
`govoplan-core` is the verifier and orchestrator:
|
||||
|
||||
- fetches a local or remote module catalog
|
||||
- verifies catalog signatures against configured trusted keys
|
||||
- enforces approved release channels
|
||||
- rejects expired or not-yet-valid catalogs
|
||||
- records accepted catalog sequence numbers for replay protection
|
||||
- checks catalog entry license feature requirements before planning installs
|
||||
- writes installer plans and request records
|
||||
|
||||
Feature and platform modules own their package artifacts, manifests, migration
|
||||
metadata, retirement providers, and optional lifecycle behavior.
|
||||
|
||||
Core accepts either a local catalog file or a remote URL:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG=/srv/govoplan/catalogs/stable.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_URL=https://govoplan.example/catalogs/v1/channels/stable.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_CACHE=/srv/govoplan/runtime/catalog-cache/stable.json
|
||||
```
|
||||
|
||||
If both file and URL are set, the URL wins. The cache is used when a remote
|
||||
fetch fails, so an operator can still inspect the last known catalog. A cached
|
||||
catalog must still pass signature, freshness, channel, and replay validation.
|
||||
|
||||
An official catalog is a JSON object with:
|
||||
|
||||
- `catalog_version`
|
||||
- `channel`
|
||||
- `sequence`
|
||||
- `generated_at`
|
||||
- `not_before` when delayed activation is needed
|
||||
- `expires_at`
|
||||
- `modules`
|
||||
- `signatures`
|
||||
|
||||
Each module entry can declare:
|
||||
|
||||
- backend package name and pinned install reference
|
||||
- WebUI package name and pinned install reference
|
||||
- display metadata and tags
|
||||
- `license_features`, the feature entitlements required to plan that install
|
||||
|
||||
The signature is Ed25519 over canonical JSON with both `signature` and
|
||||
`signatures` removed. Core accepts the legacy single `signature` field and the
|
||||
new `signatures` array.
|
||||
|
||||
Trusted catalog keys are configured locally:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE=/srv/govoplan/trust/catalog-keyring.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS='{"release-key-1":"<base64 public key>"}'
|
||||
```
|
||||
|
||||
For development or tightly controlled deployments, a keyring can be read from a
|
||||
URL and cached:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_URL=https://govoplan.example/catalogs/v1/keyring.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_CACHE=/srv/govoplan/runtime/catalog-cache/keyring.json
|
||||
```
|
||||
|
||||
Production installations should pin the trusted keyring locally or ship it
|
||||
through deployment configuration. Fetching trusted keys from the same public
|
||||
origin as the catalog is convenient, but that origin must not become the only
|
||||
trust root.
|
||||
|
||||
## Dependency Audits
|
||||
|
||||
Dependency vulnerability checks are documented in
|
||||
[`DEPENDENCY_AUDITS.md`](DEPENDENCY_AUDITS.md). The local audit runner is:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
bash scripts/check-dependency-audits.sh
|
||||
```
|
||||
|
||||
The Gitea workflow in `.gitea/workflows/dependency-audit.yml` runs the same
|
||||
check against release dependency refs on pushes, pull requests, and a weekly
|
||||
schedule.
|
||||
|
||||
Keyring entries support:
|
||||
|
||||
- `key_id`
|
||||
- `public_key` or `public_key_base64`
|
||||
- `status`: `active`, `next`, `retired`, `revoked`, or `disabled`
|
||||
- `not_before`
|
||||
- `not_after`
|
||||
|
||||
Rotation process:
|
||||
|
||||
1. Add the next public key to the local trusted keyring with status `next`.
|
||||
2. Publish catalogs signed by both current and next keys.
|
||||
3. Upgrade installations so the next key is locally trusted.
|
||||
4. Promote the next key to `active`.
|
||||
5. Retire the old key only after every supported installation trusts the new
|
||||
key.
|
||||
6. Mark a compromised key `revoked` and publish a higher sequence catalog
|
||||
signed by an uncompromised key.
|
||||
|
||||
Use replay state in production:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_SEQUENCE_STATE=/srv/govoplan/runtime/catalog-sequences.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE=true
|
||||
```
|
||||
|
||||
Core records the accepted sequence per channel after a catalog entry is planned
|
||||
from the admin interface. With strict sequence enforcement, a previously
|
||||
accepted sequence is rejected; without strict enforcement, only older sequences
|
||||
are rejected. Catalogs should always expire.
|
||||
|
||||
The sequence state file is operational state, not a trust root. Keep it on
|
||||
persistent storage and include it in normal backups:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"stable": {
|
||||
"last_sequence": 42,
|
||||
"accepted_at": "2026-07-07T12:00:00Z",
|
||||
"key_id": "release-key-1",
|
||||
"source": "https://govoplan.example/catalogs/v1/channels/stable.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the file is lost, restore it from backup. If no backup exists, reconstruct
|
||||
each channel from the highest sequence already accepted in installer run
|
||||
records, release records, or the currently deployed module package set. Do not
|
||||
lower `last_sequence` to make an older catalog pass; publish a new higher
|
||||
sequence catalog when the accepted point is uncertain.
|
||||
|
||||
If the file is corrupted, copy it aside for incident review, validate the
|
||||
current signed catalog with channel and freshness enforcement, then rewrite the
|
||||
state with the known accepted sequence. Keep
|
||||
`GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true` and approved-channel
|
||||
checks enabled during recovery. Temporarily disabling
|
||||
`GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE` allows revalidating the same
|
||||
sequence, but older sequences remain rejected once the reconstructed
|
||||
`last_sequence` is in place.
|
||||
|
||||
Approved channels are deployment policy:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_APPROVED_CHANNELS=stable,lts
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true
|
||||
```
|
||||
|
||||
The admin UI can display other catalog metadata, but core rejects catalogs from
|
||||
unapproved channels when validation is configured.
|
||||
|
||||
Catalog entries can require license features:
|
||||
|
||||
```json
|
||||
"license_features": ["module.mail", "support.standard"]
|
||||
```
|
||||
|
||||
Core checks those requirements against an offline license file before allowing
|
||||
the entry into the install plan.
|
||||
|
||||
```bash
|
||||
GOVOPLAN_LICENSE_FILE=/srv/govoplan/license.json
|
||||
GOVOPLAN_LICENSE_ENFORCEMENT=true
|
||||
GOVOPLAN_LICENSE_TRUSTED_KEYS_FILE=/srv/govoplan/trust/license-keyring.json
|
||||
```
|
||||
|
||||
License files are JSON objects with:
|
||||
|
||||
- `license_id`
|
||||
- `subject`
|
||||
- `features`
|
||||
- `valid_from`
|
||||
- `valid_until`
|
||||
- `signature`
|
||||
|
||||
Issue or renew a license from an operator/release shell that has the Ed25519
|
||||
private key:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--issue-license /srv/govoplan/license.json \
|
||||
--license-id customer-2026-07 \
|
||||
--license-subject "Example Municipality" \
|
||||
--license-feature module.mail \
|
||||
--license-feature support.standard \
|
||||
--license-valid-until 2027-07-31T23:59:59Z \
|
||||
--license-signing-key-id license-issuer-1 \
|
||||
--license-signing-private-key /srv/govoplan/secrets/license-issuer-1.pem \
|
||||
--format json
|
||||
```
|
||||
|
||||
Validate an imported license without exposing secrets:
|
||||
|
||||
```bash
|
||||
govoplan-module-installer \
|
||||
--validate-license /srv/govoplan/license.json \
|
||||
--license-trusted-key license-issuer-1="<base64 public key>" \
|
||||
--require-trusted-license \
|
||||
--license-required-feature module.mail \
|
||||
--format json
|
||||
```
|
||||
|
||||
The CLI and admin module catalog panel report the license id, subject,
|
||||
validity window, signing key id, signed/trusted state, available features, and
|
||||
missing entitlements for the configured package catalog. They do not expose
|
||||
private signing material.
|
||||
|
||||
License enforcement can run in observe-only mode by leaving
|
||||
`GOVOPLAN_LICENSE_ENFORCEMENT` unset. In that mode, missing or invalid license
|
||||
data is surfaced as a warning but does not block planning.
|
||||
|
||||
Renewal is an ordinary re-issuance with a new `license_id`, extended
|
||||
`valid_until`, and the full intended feature set. Import the renewed JSON to
|
||||
`GOVOPLAN_LICENSE_FILE`, keep the previous file for audit, and validate it
|
||||
before setting enforcement.
|
||||
|
||||
Revocation is handled through the trusted license keyring. Mark a compromised
|
||||
or invalid issuer key as `revoked` or `disabled`, publish or deploy the updated
|
||||
keyring, then reissue affected licenses with an active key. Installations that
|
||||
run with `GOVOPLAN_LICENSE_ENFORCEMENT=true` reject licenses signed only by a
|
||||
revoked key after the local keyring is updated.
|
||||
|
||||
Emergency fallback is deliberately explicit. Operators can temporarily unset
|
||||
`GOVOPLAN_LICENSE_ENFORCEMENT` to keep package planning observable while a
|
||||
license or keyring is recovered. Record the change in the operational incident
|
||||
log, keep catalog signature and channel enforcement enabled, and restore
|
||||
license enforcement after a trusted renewal validates successfully.
|
||||
|
||||
Licensing is intentionally separate from open-source code licensing. The
|
||||
catalog/license mechanism can govern support channels, official release
|
||||
eligibility, hosted update access, professional support, or commercial
|
||||
entitlements without changing the source license of the repositories.
|
||||
|
||||
Production-grade distribution still needs remote registry/git artifact
|
||||
resolution before package-manager apply, a hardened catalog publishing pipeline
|
||||
in `govoplan-web`, and automated key rotation and emergency revocation drills.
|
||||
|
||||
## Release Catalog Publishing
|
||||
|
||||
GovOPlaN release catalogs are published by `govoplan-web` as static JSON and
|
||||
verified by `govoplan-core` before installer plans are accepted. Private signing
|
||||
keys must stay outside all git repositories. Public keyrings are published with
|
||||
the website.
|
||||
|
||||
Create the first catalog signing key on the release machine:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
KEY_DIR="$HOME/.config/govoplan/release-keys"
|
||||
mkdir -p "$KEY_DIR"
|
||||
./.venv/bin/python scripts/generate-catalog-keypair.py \
|
||||
--key-id release-key-1 \
|
||||
--private-key "$KEY_DIR/release-key-1.pem" \
|
||||
--public-key "$KEY_DIR/release-key-1.pub" \
|
||||
--keyring "$KEY_DIR/catalog-keyring.json"
|
||||
```
|
||||
|
||||
Keep `release-key-1.pem` private. The generated keyring contains only public
|
||||
material.
|
||||
|
||||
Generate the signed catalog into `govoplan-web`:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
KEY_DIR="$HOME/.config/govoplan/release-keys"
|
||||
scripts/publish-release-catalog.sh \
|
||||
--version <x.y.z> \
|
||||
--sequence 202607071340 \
|
||||
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
|
||||
--build-web
|
||||
```
|
||||
|
||||
This writes:
|
||||
|
||||
- `/mnt/DATA/git/govoplan-web/public/catalogs/v1/channels/stable.json`
|
||||
- `/mnt/DATA/git/govoplan-web/public/catalogs/v1/keyring.json`
|
||||
|
||||
The wrapper validates the catalog with core using the generated public keyring.
|
||||
|
||||
For normal module/core releases, first audit and record migration baselines,
|
||||
then tag and push the module/core repos. Finally publish the website catalog:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
./.venv/bin/python scripts/release-migration-audit.py --strict
|
||||
```
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
KEY_DIR="$HOME/.config/govoplan/release-keys"
|
||||
scripts/publish-release-catalog.sh \
|
||||
--version <x.y.z> \
|
||||
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
|
||||
--build-web \
|
||||
--commit \
|
||||
--tag \
|
||||
--push
|
||||
```
|
||||
|
||||
The website tag is `catalog-v<x.y.z>`. The public URL is:
|
||||
|
||||
```text
|
||||
https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json
|
||||
```
|
||||
|
||||
The public keyring URL is:
|
||||
|
||||
```text
|
||||
https://govoplan.add-ideas.de/catalogs/v1/keyring.json
|
||||
```
|
||||
|
||||
`scripts/push-release-tag.sh` can publish the web catalog after module and core
|
||||
tags have been pushed. It runs the migration release audit in automatic mode:
|
||||
warning-only before the first recorded migration baseline, strict after a
|
||||
baseline exists. Add `--strict-migration-audit` when you want to force strict
|
||||
mode explicitly:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
KEY_DIR="$HOME/.config/govoplan/release-keys"
|
||||
scripts/push-release-tag.sh \
|
||||
--bump subversion \
|
||||
--strict-migration-audit \
|
||||
--publish-web-catalog \
|
||||
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
|
||||
--build-web-catalog
|
||||
```
|
||||
|
||||
Use `--catalog-signing-key` more than once during a key rotation window. The
|
||||
catalog will contain multiple signatures and the public keyring will include the
|
||||
corresponding public keys.
|
||||
|
||||
On a GovOPlaN installation that should consume the official stable catalog:
|
||||
|
||||
```bash
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_URL=https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_CACHE=/srv/govoplan/runtime/catalog-cache/stable.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_APPROVED_CHANNELS=stable
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE=/srv/govoplan/trust/catalog-keyring.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_SEQUENCE_STATE=/srv/govoplan/runtime/catalog-sequences.json
|
||||
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE=true
|
||||
```
|
||||
|
||||
For production, copy the public keyring into deployment configuration and pin it
|
||||
locally. Do not rely on a URL-fetched keyring as the only trust root.
|
||||
|
||||
`stable.json` includes a top-level `core_release` section for operator/update
|
||||
tooling. Core is intentionally not listed as a normal module entry because it
|
||||
must not be added to saved enabled-module state. Core upgrades should remain an
|
||||
operator-supervised package update with restart and health checks.
|
||||
|
||||
Key rotation for published catalogs:
|
||||
|
||||
1. Generate the next private key outside git.
|
||||
2. Run `publish-release-catalog.sh` with both signing keys.
|
||||
3. Publish the web catalog/keyring.
|
||||
4. Roll the new public keyring into installations.
|
||||
5. Stop signing with the old key after the supported fleet trusts the new key.
|
||||
6. Mark compromised keys as revoked in the public keyring and publish a higher
|
||||
sequence catalog signed by a trusted uncompromised key.
|
||||
|
||||
## PostgreSQL Release Check
|
||||
|
||||
Release candidates should pass a disposable PostgreSQL migration and startup
|
||||
smoke check before tagging or publishing catalogs. Start the local testbed,
|
||||
then run the permutation check from the core checkout:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core/dev/postgres
|
||||
cp .env.example .env
|
||||
docker compose --env-file .env up -d
|
||||
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
set -a
|
||||
. dev/postgres/.env
|
||||
set +a
|
||||
./.venv/bin/python scripts/postgres-integration-check.py \
|
||||
--database-url "$GOVOPLAN_POSTGRES_DATABASE_URL" \
|
||||
--reset-schema
|
||||
```
|
||||
|
||||
The script checks migrations and `/health` startup for core-only, files-only,
|
||||
mail-only, campaign-only, campaign+files, campaign+mail, and full-product
|
||||
module sets. `--reset-schema` is destructive and must only be used against a
|
||||
throwaway database.
|
||||
|
||||
## Migration Baselines
|
||||
|
||||
Development migrations may be small and numerous while a feature is moving.
|
||||
Before a stable release, unreleased migrations may be rewritten or squashed into
|
||||
a release-level baseline or release-to-release upgrade migration. After a
|
||||
release tag has shipped, released migration revision IDs are immutable.
|
||||
|
||||
The release policy is:
|
||||
|
||||
- unreleased migrations may be folded before release;
|
||||
- released migrations are never rewritten or deleted;
|
||||
- each stable release records the public migration head revisions in
|
||||
`docs/migration-release-baselines.json`;
|
||||
- fresh installations should apply release-level baselines/upgrades, not
|
||||
unreleased create-then-rename churn;
|
||||
- release-to-release schema changes should be folded into one reviewed
|
||||
migration per migration owner where practical.
|
||||
|
||||
Audit the current graph during release preparation:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
./.venv/bin/python scripts/release-migration-audit.py
|
||||
```
|
||||
|
||||
Generate the reviewed/manual squash checklist:
|
||||
|
||||
```bash
|
||||
./.venv/bin/python scripts/release-migration-audit.py --squash-plan
|
||||
```
|
||||
|
||||
After the release migrations have been reviewed and the graph is final, record
|
||||
the release baseline:
|
||||
|
||||
```bash
|
||||
./.venv/bin/python scripts/release-migration-audit.py --record-release <x.y.z>
|
||||
```
|
||||
|
||||
Use strict mode to verify that the current heads are recorded:
|
||||
|
||||
```bash
|
||||
./.venv/bin/python scripts/release-migration-audit.py --strict
|
||||
```
|
||||
|
||||
`scripts/push-release-tag.sh` runs the audit by default in automatic mode:
|
||||
non-strict while no release baseline exists, strict after the first baseline is
|
||||
recorded. Pass `--warn-migration-audit` for an explicit non-strict audit,
|
||||
`--strict-migration-audit` to force strict mode, or `--skip-migration-audit`
|
||||
only for emergency/manual release work.
|
||||
|
||||
Before the first stable release, fold the current development chain into the
|
||||
first public baseline and record that baseline in
|
||||
`docs/migration-release-baselines.json`. The tracking issue is
|
||||
`add-ideas/govoplan-core#223`.
|
||||
|
||||
## Related Operator Documents
|
||||
|
||||
- `DEPLOYMENT_OPERATOR_GUIDE.md`: runtime environment, explicit migrations,
|
||||
backup/restore commands, module installer daemon/supervisor operation, and
|
||||
rollback drills.
|
||||
- `REMOTE_WEBUI_BUNDLES.md`: experimental browser-loaded module bundles for
|
||||
controlled deployments; normal releases use package builds.
|
||||
|
||||
## Release Checklist
|
||||
|
||||
- Keep Python package versions, WebUI package versions, and git tags aligned.
|
||||
- Tag core, access, admin, tenancy, policy, audit, files, mail, campaign, calendar, and scaffold module repositories together.
|
||||
- Update `requirements-release.txt` and `webui/package.release.json` when the release tag changes.
|
||||
- Generate the committed full-product release lockfile from `package.release.json` with `scripts/generate-release-lock.sh`.
|
||||
- Add separate release manifest/lockfile pairs only for module compositions that are shipped as their own products.
|
||||
- Tag core, access, admin, tenancy, policy, audit, files, mail, campaign,
|
||||
calendar, and scaffold module repositories together.
|
||||
- Update `requirements-release.txt` and `webui/package.release.json` when the
|
||||
release tag changes.
|
||||
- Generate the committed full-product release lockfile from
|
||||
`package.release.json` with `scripts/generate-release-lock.sh`.
|
||||
- Run `scripts/release-migration-audit.py --strict` after recording a release
|
||||
baseline.
|
||||
- Run the PostgreSQL release check against a disposable database.
|
||||
- Publish the signed catalog through the release catalog publishing flow above.
|
||||
- Add separate release manifest/lockfile pairs only for module compositions
|
||||
that are shipped as their own products.
|
||||
- Do not commit local sibling paths into release manifests.
|
||||
|
||||
Reference in New Issue
Block a user