Interactive, root-run Python CLI to install, maintain, and audit multi-instance Odoo Community servers on the Debian/Ubuntu (apt) family — validated on Ubuntu 24.04, but the tool detects the OS codename, nginx, PostgreSQL, and Odoo major and renders version-correct configuration rather than assuming a single stack. It never mutates the host directly: every action assembles a command plan, previews it, and applies it only after confirmation. This file is the single authored source of truth for AI agents working on this project.
- This repository is the project root. A single Python package, no nested repos.
odoo_instance_manager.py— entry point (root check, UTF-8 console, main menu).instance_manager/— the package, in strict layers (seedocs/architecture.md):models.py—InstanceConfig, identifier validation, path derivation.support.py— per-version facts (Python range and its evidence, setuptools, uv pin, PostgreSQL floor, cores). Pure data, declared once.neutralise.py— the catalogue of a copy's neutralisation, each rule cited to its source. Builds SQL only.planners.py— pure builders returninglist[Command](odoo.conf, systemd, nginx, fail2ban, TLS). No I/O, no execution.system.py— execution primitives, existence probes,preview_commands/apply_commands.prompts.py— interactive input, file picker,confirm_with_phrase.ui.py— terminal tables and styling.workflows/— a package split by capability (install, manage, backup_restore, fail2ban, firewall, services, purge, report, health, diskusage, addons, logrotate, scheduled_backup, …) over a sharedcommon.py; menus, plan assembly, discovery, and the read-only audit.
openspec/specs/— the behavior source of truth (one spec per capability). Changes live inopenspec/changes/.docs/— user/operator docs, each with frontmatter. Grown surfaces are grouped into folders (docs/operations/,docs/security/); setup/reference/audit pages stay flat atdocs/;docs/decisions/holds ADRs. The README is the routable map.- English is canonical. The app UI defaults to English, and all docs and specs are English. Spanish is
offered only as an optional UI language through the i18n layer (
instance_manager/i18n.py): every operator-facing string goes throught/tfwith the English text as the source of truth — never a hardcoded literal, and never Spanish-first. Docs must quote the English UI labels; do not add a new UI language without adding its i18n catalog.
- Python 3.9+ (the host's
python3, down to Debian 11),from __future__ import annotations, standard library only (no third-party runtime deps). - Files are UTF-8, newlines LF, final newline at EOF.
- Conventional Commits, imperative mood, one logical change per commit. No AI-attribution trailers.
- Match the surrounding code: small functions, early returns, type hints.
- Keep planners pure — building a command is not running it. Execution belongs to
system.py. - Quote and validate every operator-supplied value reaching a shell command or SQL string (use
_quote/shlex.quoteand the identifier validators). Never interpolate raw input.
- Non-trivial changes are spec-first. Capture intent and a short plan before code, and keep a record of
what changed and why. Behavior is specified in
openspec/specs/; use the OpenSpec/opsx:*flow (/opsx:explore → /opsx:propose <name> → /opsx:apply → /opsx:archive) and validate withopenspec validate --specs. - Preview → confirm → apply is inviolable. Every host-mutating action must go through a previewed plan;
destructive/data actions must keep their exact phrase confirmation. Do not add a code path that mutates the
system without a plan. (See
docs/decisions/0001-plan-preview-apply-safety.md.) - Keep docs honest. When behavior changes, update the affected
docs/page and the README map in the same change. - Keep the root
README.mdcurrent. It is the friendly front door and the routable map: a clear, friendly description and usage of the utility, a diagram of what it does (in the colour classes ofdocs/architecture.md, Diagram conventions), a link to the supported-platforms matrix indocs/platforms.md, and links out todocs/. Update it in lockstep with any behavior, capability, or docs change. - Keep the changelog and version in lockstep. User-facing changes go under
## [Unreleased]inCHANGELOG.md(Keep a Changelog format) as they land. To cut a release, follow SemVer: rename[Unreleased]to[<version>] - <YYYY-MM-DD>(leave a fresh empty[Unreleased]on top), bumpversioninpyproject.tomlto match, add the[<version>]compare/tag link at the bottom, then tagv<version>. Pushing the tag triggers the Release workflow (.github/workflows/release.yml): it verifies the tag matches thepyprojectversion, runs the checks, builds the sdist + wheel, and publishes/updates the GitHub Release with those artifacts and the changelog notes. The tag, thepyprojectversion, and the changelog heading must always agree (the workflow fails the release if they don't). - Secure by default. Validate input at boundaries, never hardcode secrets, and quote everything that reaches a shell or SQL. The tool runs as root — assume every input is attacker-controlled.
- Add dependencies deliberately. This project is standard-library-only by design; introducing a runtime dependency is itself a decision worth an ADR — pin it, scan for known CVEs, and justify it.
- Some actions are irreversible or sensitive — force-push, history rewrite, secret access, deleting
instances/databases: pause and confirm first. Safe-controls hooks enforce a floor when installed (see
docs/security/safe-controls.md).
- Runtime guardrails come from the eunomai Claude Code plugin's
PreToolUsehooks (ask-by-default on force-push /rm -rf/ secret access; deny on AI-attribution commit trailers). Fail-open, a floor-raiser, not a security boundary. - Static path rules use the native
permissionsbaseline in.claude/settings.json— seedocs/security/safe-controls.md.
Structural, read-only gates (run from the project root):
python -m ruff check . # lint (target py39: the manager runs on the host's python3)
python -m pytest -q # unit tests (also on Python 3.9 in CI)
openspec validate --specs # every capability spec is well-formed (CI pins openspec 1.4.1)
# eunomai docs-check / provenance-check via the installed plugin/CLIText that runs is verified by running it: the tools/verify_*.py scripts execute what the planners generate
(against stubs, throwaway PostgreSQL clusters, real nginx / fail2ban / logrotate / openssl, and real Odoo for the
manual ones). Which run in CI, what each needs and when to run it is stated once, in
CONTRIBUTING.md. A verifier that verifies nothing fails.