Thanks for helping improve Odoo Instance Manager. This project manages production Odoo servers, so changes are held to a careful, spec-first standard.
- Spec-first for non-trivial changes. New behavior or changes to existing behavior are captured as an
OpenSpec change before code. See Workflow below and
docs/architecture.mdfor how the layers fit together. - Never weaken a safety control to make something work. The preview → confirm → apply gate, phrase
confirmations, and identifier validation are load-bearing (see
docs/decisions/0001-plan-preview-apply-safety.md). - Keep planners pure. Functions in
planners.pybuildlist[Command]and must not execute anything or perform I/O. Execution lives insystem.py; orchestration in theworkflows/package. - Validate and quote every operator-supplied value that reaches a shell command or SQL string. Use the
existing
_quote/shlex.quoteand the identifier validators — do not interpolate raw input.
| Path | Role |
|---|---|
odoo_instance_manager.py |
Entry point, root check, main menu |
instance_manager/models.py |
InstanceConfig, identifier validation, path derivation |
instance_manager/support.py |
Per-version facts: Python, setuptools, PostgreSQL floor, cores, pinned uv |
instance_manager/neutralise.py |
Neutralisation rules for copied databases (pure SQL builders) |
instance_manager/planners.py |
Pure command-plan builders (config/systemd/nginx/fail2ban) |
instance_manager/system.py |
Execution primitives, existence checks, preview/apply |
instance_manager/prompts.py |
Interactive input, file picker, phrase confirmation |
instance_manager/ui.py |
Terminal rendering |
instance_manager/workflows/ |
Menus, plan assembly, discovery/audit — one module per capability over common.py |
openspec/specs/ |
Capability specifications (the source of truth for behavior) |
docs/ |
User- and operator-facing documentation |
- Python 3.9+ (the host's
python3on Debian 11),from __future__ import annotations, standard library only (no third-party runtime deps). Ruff targets py39, and CI runs the unit suite on 3.9. - Files are UTF-8 with LF newlines and a final newline.
- Conventional Commits in the imperative mood; one logical change per commit. No AI-attribution trailers.
- Match the surrounding style: small functions, early returns, type hints. Operator-facing strings are English
keys passed through
t/tf, with their Spanish ininstance_manager/i18n.py; a test fails on a missing or an unused entry.
Install the project with dev tooling and run the same gate CI runs:
pip install -e ".[dev]" # editable install + pytest, ruff
ruff check # lint (config in pyproject.toml)
pytest # unit tests (tests/)
openspec validate --specs # specs are well-formed
python3 tools/verify_data_safety.py # run the generated backup/retention/delete/copy commands
python3 tools/verify_install_runtime.py --clone 14=/path/odoo-14.0 --clone 16=/path/odoo-16.0
python3 tools/verify_neutralisation.py --odoo 18=/path/venv/bin/python:/path/odoo-18.0
python3 tools/verify_ops_configs.py # nginx -t and fail2ban on the generated config
python3 tools/verify_secrets.py # secrets in no process argument, preview or error
python3 tools/verify_privilege.py # certificates, downloads, venv replication, neutralisation as owner
python3 tools/verify_postgres_setup.py # PostgreSQL install steps: port, scram, listen_addresses, pg_hbatools/verify_data_safety.py, tools/verify_secrets.py, tools/verify_privilege.py,
tools/verify_postgres_setup.py and tools/verify_ops_configs.py also run in CI.
tools/verify_data_safety.py executes the generated scheduled-backup script, the
retention prune and the delete step against stub binaries, and the template copy, forced drop and purge
discovery against a PostgreSQL cluster of its own (it needs initdb and refuses to run as root). Run it after
changing any of those.
tools/verify_privilege.py runs the certificate steps with real openssl (a key that does not match must leave
the live files untouched, a hostile file name must stay a name), the wkhtmltopdf and venv-replication steps
against stubs, and the neutralisation as the copy's owner against a PostgreSQL cluster of its own, where a
trigger in the copy tries to make its owner a superuser. Run it after changing those planners or
_post_db_mode_commands.
tools/verify_postgres_setup.py runs the PostgreSQL steps of an install against a cluster of its own on a port
other than 5432, storing md5 by default as PostgreSQL 13 does and with a conf.d file, and checks the role's
scram password, listen_addresses set with one restart, and the pg_hba rule added once. Run it after changing
those planners.
tools/verify_install_runtime.py is not part of CI: it downloads the pinned uv (and refuses a wrong
checksum), installs the interpreter the matrix picks, builds a real venv for each Odoo checkout you pass and
starts odoo-bin --version; for Odoo <= 16 it also shows the unpinned setuptools breaks it. It needs network
access and the build dependencies, and writes only to a temp directory. Run it after changing
instance_manager/support.py or the venv steps.
tools/verify_neutralisation.py installs real Odoo databases (an Odoo venv and checkout per version) in a
PostgreSQL cluster of its own, arms them like production, runs the duplication's own seed, neutralisation,
check and hand-over commands, and has Odoo itself try to send a mail from the copy. Run it after changing
instance_manager/neutralise.py or the duplication/restore plans.
tools/verify_ops_configs.py downloads the distribution's nginx package (apt-get download, extracted, never
installed) and fail2ban's source release, then runs nginx -t, fail2ban-regex and fail2ban-client -t on the
generated vhosts, filter and jails, including their rollbacks. It runs fail2ban 1.1.0, 1.0.2 and 0.11.2 (the
older two on a Python 3.11/3.10 and 3.9/3.8 found on PATH or through uv, converted with lib2to3 as the
distributions do), and has each ban a stub ufw. It also runs the staged writes' failure modes and logrotate's
rollback. Run it after changing those planners.
CI (.github/workflows/ci.yml) runs ruff, pytest, a byte-compile, openspec validate, and the eunomai
docs-check / provenance-check gates on every push and PR to main.
Tests use stdlib unittest (collected by pytest) so they run with python -m unittest discover -s tests
even without dev deps installed. Ruff enforces F/B/I/UP/E/W; E501 (line length) is deliberately deferred
because many long lines are embedded shell/SQL command strings — aim for ≤100 cols in new code.
For non-trivial changes, use the OpenSpec flow:
/opsx:explore # think through the change (optional)
/opsx:propose <name> # create the change: proposal, design, tasks, spec deltas
/opsx:apply # implement the tasks
/opsx:archive # fold the spec deltas into openspec/specs/ and archive
Validate specs and changes at any time:
openspec validate --specs
openspec list --specs- Run the tool against a disposable VM or container, never a production host, to exercise the affected menu path end to end.
- Confirm the affected capability spec under
openspec/specs/still matches the behavior (update it via a change when behavior shifts). - Keep the docs honest: if behavior changes, update the affected
docs/page and the README map in the same change.