Files
govoplan-core/docs/RELEASE_DEPENDENCIES.md
2026-07-07 16:00:38 +02:00

16 KiB

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.

Backend

Local development:

cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m pip install -r requirements-dev.txt

Release install from a core checkout plus tagged module repositories:

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:

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:

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

Runtime Module Package Changes

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.

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:

govoplan-module-installer --format shell

Apply from a controlled operator shell while maintenance mode is active:

govoplan-module-installer --apply --build-webui

For real install/uninstall work, prefer supervised mode with the deployment's restart command and health endpoint:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

cd /mnt/DATA/git/govoplan-core
scripts/generate-release-lock.sh
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 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:

cd /mnt/DATA/git/govoplan-core
scripts/push-release-tag.sh --version 0.1.2

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:

  • govoplan-addresses
  • govoplan-appointments
  • govoplan-cases
  • govoplan-connectors
  • govoplan-dms
  • govoplan-erp
  • govoplan-fit-connect
  • govoplan-forms
  • govoplan-identity-trust
  • govoplan-idm
  • govoplan-ledger
  • govoplan-notifications
  • govoplan-ops
  • govoplan-payments
  • govoplan-portal
  • govoplan-reporting
  • govoplan-scheduling
  • govoplan-search
  • govoplan-tasks
  • govoplan-templates
  • govoplan-workflow
  • govoplan-xoev
  • govoplan-xrechnung
  • govoplan-xta-osci

Release lockfile strategy

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.

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 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.
  • Do not commit local sibling paths into release manifests.