Skip to Content

Developer guide

This guide explains how the repository's code fits together and where to make changes. The documentation portal links operational instructions; source comments and docstrings explain local contracts and implementation choices.

Start with the owning component

Area Entry points and responsibility Focused evidence
Shared settings and catalogs config/, data/repos/; target resolution and repository selection Backup registry, restore configuration, and updater catalog tests
Backup production scripts/backup/backup_odoo18_dbs.sh, sourced backup_config.bash test_backup_registry.sh, test_backup_locking.sh
Backup transport scripts/laptop-backup/; latest links, synchronization, service following, deployment, restore delegation test_backup_locking.sh, test_backup_follow.sh, test_backup_registry.sh
Restore scripts/restore/restore_database.sh and its five lib/ libraries Library tests, test_restore_cli_contract.sh, test_restore_safety.sh
Legacy restore Database-specific commands in _pmiregistry/scripts/restore/ _pmiregistry/tests/test_restore_local_sources.sh, _pmiregistry/tests/test_restore_cli.sh
Database copy/delete scripts/duplicate/duplicate_database.sh, scripts/dropdb/deletedb test_duplicate_database.sh, test_deletedb.sh
Database creation scripts/create/create_database.py test_create_database.py, focused CLI/completion tests
Administrator recovery scripts/admin/reset_admin_password.py; shared password/runtime helpers in scripts/create/create_database.py test_reset_admin_password.py, shared helper and CLI tests
Migration scripts/migrate/, openupgrade_overlays/ Approved disposable upgrade validation; no general mocked migration suite here
Requirement collection scripts/fetch/fetch_requirements.py test_fetch_requirements.py
Repository updates scripts/update/pmirepos_update.py and version-specific commands test_enhanced_update18_catalog.py covers the Odoo 18 catalog path
Translation export scripts/translate/update_pot_addon.py, update_pot_repo.py Preview/command inspection; no dedicated export test suite here
Addon test/lint tooling scripts/test/, reusable testkit/ assets Scheduler, namespace, Ruff policy, and testkit layout tests
Host integration scripts/integration/, scripts/completion/ Integration layout and completion tests
Python compatibility tools/python_compatibility.py delegates to sibling _pmiguidelines test_python_compatibility.py
Deployment templates templates/, testkit/workflows/, testkit/pre-commit/ Inspect intended consumers and rendered configuration before deployment

Unless explicitly located in _pmiregistry, tests above live in ../tests/. They cover specific contracts, not every path of every entry point. Do not infer coverage of older updaters, migrations, or translation operations from an unrelated passing test.

For database creation and administrator recovery, start with the shared implementation guide. It maps helper ownership, configuration precedence, registry/transaction boundaries, credential publication, and the tests needed when changing either command.

Configuration and generated-source boundaries

The active /srv/pmiverse/_pmiregistry/config/pmiverse.conf is sourced Bash (PMIVERSE_CONF overrides its location). _pmiconfig/config/pmiverse.conf is an empty generic template, whereas config/pmisetup.conf is INI read by Python's ConfigParser. They are different interfaces. The database registry connects aliases to database names, suite versions, relative source directories, target directories, and optional backup-parent directories. The instance registry adds the producing host, output roots, connection defaults, and database membership. See configuration and the backup registry specification.

Host-separated restore mirrors use data/backups/<host>/<database>. Producers on the originating hosts still write data/backups/<database>. Latest-only synchronization flattens selected archives into one directory and rejects duplicate basenames across hosts. Keep these three layouts distinct when editing registries or test expectations.

data/repos/<version>/*.csv supplies repository names, URLs, and destination folder names to the updater selected for that version. A folder named OCB can select special root placement instead of a child repository directory. Group-to-directory mappings differ between the generic and retained versioned updaters; inspect get_clone_system_dir in the actual consumer before changing a catalog. Versioned requirement files are maintained deployment inputs. The requirement collector instead generates scripts/fetch/requirements-*.txt by default; modify its inputs or implementation, not those generated results.

testkit/ owns reusable templates and shared policy. Downstream scaffold files and copied workflows are consumers, not alternative canonical sources. templates/ contains deployment inputs: edits do not install units, reload systemd, update an instance, or run an upgrade. Service units separate ordinary startup, full upgrades, selected-module maintenance, backup production, and rsync. Timers schedule the unit named by their configuration; they do not automatically schedule every instantiated @.service.

Bash interfaces and process ownership

Operational entry points generally use strict shell modes. Sourced restore libraries define functions without running operations or replacing the caller's traps. The completion library is also sourced, but must tolerate failures without enabling strict modes or terminating an interactive shell.

Configuration libraries publish named globals; functions using local -n write results into caller-owned variables. Completion fixtures similarly rely on Bash dynamic scope to fill the caller's cur, prev, words, and cword. An assignment that appears unused within its file can therefore be part of a cross-file interface. Renaming it or exporting it merely to silence an inspection changes that interface.

Keep executable argument vectors in arrays. In the legacy Odoo restore commands, the interpreter and odoo-bin path are separate elements; quoted array expansion preserves spaces and glob characters. Quoted heredoc delimiters keep SQL/Python text literal; unquoted delimiters intentionally interpolate shell values. Comments inside heredocs become part of generated programs or output, so they require the same care as other template edits.

Traps own process cleanup. Capture the original exit status before executing cleanup commands, preserve ownership of temporary files, and avoid deleting artifacts whose creation was not confirmed. A service follower owns its journalctl child, not the systemd synchronization job. Detaching the terminal must leave that job running.

Backup and restore state transitions

The Odoo 18 producer resolves targets before writing, locks the canonical backup root, creates a temporary dump/ZIP, validates the ZIP, then publishes with a same-filesystem hard link that refuses an existing destination. Retention runs after publication. Each database succeeds or fails independently; the final status aggregates failures and completed archives remain available.

Latest links are published with individual renames. Synchronization first stages every configured host and checks basename uniqueness, then replaces destination files individually and prunes stale files last. A rename is atomic for one file; the complete set is not an atomic transaction and can contain mixed generations after interrupted publication.

The generic restore separates configuration, archive validation, PostgreSQL primitives, neutralization, and publication. Its entry point owns resource locks, phase changes, traps, and temporary names. Publication observes actual database/path state before rollback: an interrupted command may have taken effect before its success flag was recorded. Unknown or ambiguous state means preserve artifacts and report recovery information, rather than guessing. See the detailed restore architecture before changing these transitions.

Legacy per-target restore commands share configuration resolution but retain their own destructive restore bodies. They do not acquire all the generic runner's guarantees. The Odoo 12 backup producer in _pmiregistry likewise writes final ZIP names directly and remains a separately documented legacy implementation.

Database duplication retains incomplete destinations after failure and only cleans its private dump directory. Assigning a new UUID does not neutralize a copy. Database deletion performs the database drop first, then rechecks the filestore's device/inode before removal; it cannot roll the database back if filestore removal fails.

Python tools and migration code

Requirement collection prunes excluded directories, expands local includes without leaving the source root, and merges normalized names with identical environment markers. Group order is OCB, OCA, then PMIVerse: earlier ownership excludes that package from later groups. This is an inventory of declarations, not a dependency solver. Parsing completes before publication, but replacing the output files is not atomic as a group.

The generic updater returns per-repository worker results and uses fast-forward-only pulls. Enhanced version-specific updaters use ordinary pulls and initialize configuration/logging during import. Older sequential updaters have neither the worker pool nor the retry layer. Importing those modules is not an inert way to obtain CLI help; tests redirect configuration, logging, and subprocesses before import.

Ruff synchronization compares parsed policy but edits TOML source text to preserve unrelated sections. It refuses staged/unstaged edits in an existing target policy, reparses proposed text before writing, and reports competing nested configurations without rewriting them. Comment or marker changes in the canonical source can affect later synchronization output.

Translation exporters create deterministic l10n_... databases and write POT files in addon repositories. The single-addon and repository commands have different cleanup and publication behavior; see the translation implementation guide.

The migration runner clones through successive Odoo versions using each version's environment, configuration, and OpenUpgrade path. Derived databases and backups are recovery artifacts, not an all-or-nothing transaction. The SQL repair backfills missing sale-attachment visibility when that column exists. The website-sale overlay converts translated ribbon HTML and copies legacy attachment metadata into product documents. Its inserts rely on migration sequencing; do not invoke the migration hook as an independent replayable maintenance command.

Test and lint infrastructure

The single-addon runner prepares the database before pytest. The namespace plugin extends odoo.addons.__path__ instead of placing addon roots on top-level sys.path, where an addon could shadow a standard-library module. The base environment fixture supplies a cursor per test and rolls it back; tests must not perform addon installation themselves.

The repository runner discovers candidates, applies include/exclude globs, dispatches per-addon workers, then derives summaries and badges from their artifacts. The rolling scheduler tracks and reaps each PID once, including children that finish before a wait starts. Stop-on-first-failure handling is in the serial branch; parallel work is drained before reporting.

Scaffolding preserves existing test files unless forced. Its preview suppresses file copies but still creates logging/test directories. Initialization can also edit README/ignore files and install pre-commit hooks. Badge publishing compares run-summary addon membership with the destination repository before pruning; the absence of a particular SVG alone does not establish a partial run.

Repository shell tests use temporary trees and command mocks. Some extract a function or scheduling block rather than source an operational entry point. Keep extraction markers stable, keep mocks first in PATH, and preserve failure-injection arguments passed through wrapper functions. The safety test's run_only_neutralize receives environment overrides indirectly through expect_failure; its argument forwarding is required even when a static analyzer cannot find the call.

Maintaining documentation and inspections

Document input/output contracts, side effects, ordering, ownership, and reasons for non-obvious code. Avoid comments that merely restate assignments or test names. Keep detailed user instructions in the component README/runbook and link them from the portal rather than copying command lists into every file.

ShellCheck source hints describe checked-in dependencies; /dev/null hints are reserved for runtime-generated or environment-owned source files. Narrow annotations preserve intentional globs, literal injection data, nameref outputs, and indirect trap calls. Explain such annotations where they occur. The test inspection notes describe the Python monkeypatch annotations as well.

For a documentation-only change, syntax/inspection checks and comparison with the pre-edit executable source are useful first checks. Run focused fixture tests when comments affect extraction markers or generated templates. Running the actual backup, migration, restore, update, or deployment command is not a substitute for those checks.