Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
branches: [main]
# The release runs the same checks first.
workflow_call:

jobs:
checks:
Expand Down Expand Up @@ -37,12 +39,17 @@ jobs:

- name: Build the wheel and import it from a clean venv
run: |
pip install build
pip install build==1.6.1
python -m build --wheel --outdir dist
python -m venv /tmp/wheel-check
/tmp/wheel-check/bin/pip install dist/*.whl
cd /tmp && /tmp/wheel-check/bin/python -c "import odoo_instance_manager, instance_manager.workflows.manage, instance_manager.support, instance_manager.neutralise"

- name: Unit tests on the oldest supported Python (3.9, Debian 11)
run: |
python3.9 -m pip install -q pytest
python3.9 -m pytest -q

- name: Verify the generated data commands (stubs + a throwaway PostgreSQL)
run: python tools/verify_data_safety.py

Expand Down
22 changes: 20 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ permissions:
contents: write # create the release and upload assets

jobs:
# Every check CI runs (lint, tests on 3.9 and 3.12, the verify tools, specs, docs).
ci:
uses: ./.github/workflows/ci.yml

release:
needs: ci
runs-on: ubuntu-latest
steps:
- name: Checkout project
Expand All @@ -23,7 +28,15 @@ jobs:
python-version: "3.12"

- name: Install project (with dev deps) + build tooling
run: python -m pip install --upgrade pip && pip install -e ".[dev]" build twine
run: python -m pip install --upgrade pip && pip install -e ".[dev]" build==1.6.1 twine==7.0.0

- name: Verify the tag is on main
run: |
git fetch --quiet origin main
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
echo "::error::Tag $GITHUB_REF_NAME is not on main"
exit 1
fi

- name: Verify the tag matches the pyproject version
run: |
Expand Down Expand Up @@ -81,9 +94,14 @@ jobs:
gh release upload "$GITHUB_REF_NAME" dist/* --clobber
gh release edit "$GITHUB_REF_NAME" --notes-file release_notes.md
else
# A pre-release (v1.3.0-rc1) never becomes the latest release.
case "$GITHUB_REF_NAME" in
*-*) kind="--prerelease" ;;
*) kind="--latest" ;;
esac
gh release create "$GITHUB_REF_NAME" dist/* \
--title "$GITHUB_REF_NAME" \
--notes-file release_notes.md \
--latest \
"$kind" \
--verify-tag
fi
15 changes: 13 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ applies it only after confirmation. This file is the single authored source of t
- `odoo_instance_manager.py` — entry point (root check, UTF-8 console, main menu).
- `instance_manager/` — the package, in strict layers (see `docs/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 returning `list[Command]` (odoo.conf, systemd, nginx, fail2ban, TLS).
No I/O, no execution.
- `system.py` — execution primitives, existence probes, `preview_commands`/`apply_commands`.
Expand All @@ -34,7 +37,8 @@ applies it only after confirmation. This file is the single authored source of t

## Conventions

- Python 3.12+, `from __future__ import annotations`, **standard library only** (no third-party runtime deps).
- 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.
Expand Down Expand Up @@ -86,6 +90,13 @@ applies it only after confirmation. This file is the single authored source of t
Structural, read-only gates (run from the project root):

```bash
openspec validate --specs # every capability spec is well-formed
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/CLI
```

Text 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`](CONTRIBUTING.md). A verifier that verifies nothing fails.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ All notable changes to this project are documented here. The format is based on

### Changed

- **The release workflow** runs every CI check first and publishes only tags on `main`. A pre-release tag is not
marked as the latest release. ruff, build and twine are pinned.
- `odoo-instance-manager --help` and `--version` work without root, and other arguments are refused.

- **Docs refreshed against the code**, with diagrams in shared semantic colours (`docs/architecture.md`,
*Diagram conventions*): the main menu, the layers, the plan flow (the phrase is asked before the preview), the
per-version install, the neutralised copy, and what each fail2ban jail blocks. Pages follow the menu's
Expand All @@ -43,6 +47,19 @@ All notable changes to this project are documented here. The format is based on

### Fixed

- **Ctrl+C during a long step** (apt, pip, a restore) returned to the menu while the step kept running behind
it. The whole step is now stopped and waited for.
- **Unexpected errors no longer end in a traceback:** an unreadable file, a port that does not speak HTTP, a
missing picker directory, or Ctrl+C at the language prompt now return to the menu with the error named. An
out-of-range number in the file picker is no longer taken as a path.
- **The tool did not start on the `python3` of Ubuntu 22.04 or Debian 11/12** (one line of Python 3.12 syntax).
It now needs Python 3.9+, and CI runs the suite on 3.9.
- **Scheduled backups** of an instance with a remote database dumped the local server; they are refused now. The
status also shows whether the last run made a backup.
- **Spanish interface.** The posture and paths tables are translated; 86 unused catalog entries are gone, and a
test now catches both.
- **Verify tools** fail when they verify nothing (no `--odoo`, no `--clone`, no nginx binary, run as root in CI).

- **fail2ban web and Odoo bans never applied on Ubuntu 22.04 and Debian 11.** fail2ban 0.11.2 split the
`Nginx Full` profile name, and ufw refused it. They now use the tool's own `ufw-odoo-web` action (ports 80
and 443), checked on fail2ban 0.11.2, 1.0.2 and 1.1.0.
Expand Down
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ are held to a careful, spec-first standard.

## Conventions

- Python 3.12+, `from __future__ import annotations`, standard library only (no third-party runtime deps).
- Python 3.9+ (the host's `python3` on 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, Spanish operator-facing strings.
- Match the surrounding style: small functions, early returns, type hints. Operator-facing strings are English
keys passed through `t`/`tf`, with their Spanish in `instance_manager/i18n.py`; a test fails on a missing or an
unused entry.

## Dev loop

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ tool is for you. If you use Odoo's SaaS/Online or a managed host, you don't need

- **You approve every change.** Each action shows the exact list of commands and runs them only after you
confirm; anything destructive also requires typing a confirmation phrase.
- **Nothing to install.** Pure **Python 3.12 standard library** — no pip packages, no runtime dependencies.
- **Self-cleaning.** If an install fails partway, it rolls back its own residues so you can retry cleanly.
- **Nothing to install.** Pure **Python standard library** (3.9+) — no pip packages, no runtime dependencies.
- **Undo of a failed install.** If an install fails partway, it shows the steps that undo what that run made
and asks before running them, so you can retry cleanly. Copies are one step: done whole, or dropped.

## Requirements

- A **Debian/Ubuntu (apt) server**, run as **root** (`sudo`) — validated on **Ubuntu 24.04**, and
version-adaptive across other releases (see [Supported platforms](docs/platforms.md)).
- **Python 3.12+** (ships with 24.04). Nothing else.
- **Python 3.9+** as `python3` — what Debian 11 (3.9), Ubuntu 22.04 (3.10), Debian 12 (3.11) and Ubuntu 24.04
(3.12) ship. Nothing else.

The interface is available in **English** (default) or **Spanish** — chosen at startup or via `OIM_LANG=en|es`
(see [Interface language](docs/language.md)).
Expand Down
3 changes: 2 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ plan before applying — see [Architecture](architecture.md).

- A **Debian/Ubuntu (apt) family** server, validated on Ubuntu 24.04. Configuration is version-adaptive, so
other apt releases work too — see [supported platforms](platforms.md).
- Python 3.12+ available as `python3` (the tool uses 3.12 syntax).
- Python 3.9+ available as `python3` — every supported release ships one (Debian 11: 3.9, Ubuntu 22.04: 3.10,
Debian 12: 3.11, Ubuntu 24.04: 3.12). The interpreter each Odoo version needs is chosen separately.
- Run as root: the tool refuses to start otherwise.

```bash
Expand Down
5 changes: 3 additions & 2 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ assuming a single stack:
| Component | Supported | Notes |
|-----------|-----------|-------|
| OS family | Debian/Ubuntu (**apt**) | Package steps target apt. A non-apt OS is detected and warned, not driven blindly. |
| Ubuntu | 22.04 (jammy), 24.04 (noble) | Validated primary targets. Newer releases work; the wkhtmltopdf table maps noble → the jammy build. |
| Debian | 11 (bullseye), 12 (bookworm) | wkhtmltopdf assets pinned for both. |
| Ubuntu | 22.04 (jammy), 24.04 (noble) | 24.04 is validated end to end. For 22.04 the version-specific parts are checked against its own tools (fail2ban 0.11.2, nginx 1.18 rules, Python 3.10). The wkhtmltopdf table maps noble → the jammy build. |
| Debian | 11 (bullseye), 12 (bookworm) | Same checks against their tools (fail2ban 0.11.2 / 1.0.2, PostgreSQL 13's md5 default, no `auth.log` on 12). wkhtmltopdf assets pinned for both. |
| Python (to run the manager) | 3.9+ | The host's `python3`: 3.9 on Debian 11, 3.10 on Ubuntu 22.04, 3.11 on Debian 12, 3.12 on Ubuntu 24.04. CI runs the unit suite on 3.9 and 3.12. |
| nginx | 1.18 → 1.25+ | HTTP/2 directive form chosen by detected version; `proxy_cookie_flags` written from 1.19.3. The version is the installed nginx's, or the one apt would install when the plan installs it; when neither is known the vhost is written for an older nginx. `tools/verify_ops_configs.py` runs `nginx -t` on the generated vhosts with the nginx the host distribution ships (1.24 on Ubuntu 24.04). |
| PostgreSQL | 13+ | Local drops use `dropdb --force` (13+); Odoo's own floor per version is below. Every supported Ubuntu/Debian ships 13 or newer. |
| Odoo Community | 12 → 19 | Per-version interpreter, setuptools, config keys — see the table below. Official Odoo or OCB. |
Expand Down
35 changes: 20 additions & 15 deletions docs/server-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@ server you are auditing or preparing to hand off.

## What it reports

- **System overview** — hostname, OS, kernel, architecture, virtualization, uptime, IPs, and the
versions/paths of Python, the PostgreSQL client, Nginx, and the host **wkhtmltopdf** version, plus the
run/boot state of the PostgreSQL and Nginx services and a read-only `nginx -t` config probe. Missing probes
show a "not detected" marker.
- **Per-instance detail** — discovered by cross-referencing:
- systemd units whose content references Odoo (`odoo-bin` / `Description=Odoo`),
- valid Odoo config files (must not be backup-like and must carry at least two expected Odoo keys),
- Odoo-related Nginx vhosts,
The report's sections, in the order it prints them:

- **Server summary** — hostname, OS, kernel, architecture, virtualization, uptime, IPs, and the versions and
paths of Python, the PostgreSQL client, Nginx and **wkhtmltopdf**. It also gives the run and boot state of the
PostgreSQL and Nginx services and a read-only `nginx -t` probe. A missing probe shows "not detected".
- **Detected Odoo services**, **Artifacts found by search**, **Detected Odoo configuration files**,
**Odoo-related Nginx configs**, **Detected filestores** — what discovery cross-references:
- systemd units whose content references Odoo (`odoo-bin` / `Description=Odoo`);
- valid Odoo config files (not backup-like, with at least two expected Odoo keys);
- Odoo-related Nginx vhosts;
- filestore roots (excluding backup-like and `.ssh` paths).

Each instance's home, config, and Python are derived from the service `ExecStart` when possible.
- **TLS posture** — classifies each instance's certificate (self-signed / Let's Encrypt / custom / incomplete
/ none) and reports certificate metadata and expiry status (OK / WARN if expiring within the threshold /
MISSING / ERROR).
- **Odoo version** — read from `<home>/odoo/odoo/release.py` (`version_info`) when available.
- **Instances: Odoo / paths / DB**, **Instances: Python**, **Instances: Nginx / ports** — per instance:
- home, config and paths, derived from the service's `ExecStart` when possible;
- the Odoo version, read from `<home>/odoo/odoo/release.py`;
- the venv's Python, read from its `pyvenv.cfg`, never by running the instance's interpreter as root;
- the instance's databases, its vhosts and its ports.
- **Production posture** — a per-instance table of the checks listed under
[security & production posture](operations/instance-management.md#security--production-posture), computed
from each instance's `odoo.conf` plus host facts; read-only.
- **TLS certificate detail** — each instance's certificate mode (self-signed / Let's Encrypt / custom /
incomplete / none) with its metadata and expiry (OK / WARN within the threshold / MISSING / ERROR).
- **Active TLS checks** — only when you opt in: read-only `openssl` checks with an expiry threshold.

Instance discovery also recognizes the legacy config path `/etc/<instance>/odoo.conf` in addition to the
default `/etc/odoo/<instance>/<instance>.conf`.
Expand All @@ -46,7 +50,8 @@ configuration, or deletion command — see the
Two operator-initiated extras are the only things it may write or actively probe, both harmless:

- **Export the report** — when you opt in, it writes a single report file to the path you choose (default under
`./reports/`, relative to where you launched the tool as root). This is the only file the audit creates.
`./reports/`, relative to where you launched the tool as root). This is the only file the audit creates: a new
file (an existing file, or a link in its place, is refused) readable by root only.
- **Active TLS checks** — when you opt in with an expiry threshold, it runs read-only `openssl` certificate
checks; no certificate or service is modified.

Expand Down
Loading
Loading