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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ All notable changes to this project are documented here. The format is based on

### Fixed

- **A neutralised copy could still reach the outside.**
- Several rules named columns some versions lack, so they were skipped silently and left those copies in
production:
- SII and TicketBAI on 14–17;
- the EDI proxy on 14–16;
- Google Calendar on 15–17;
- IAP on 18–19.
- Other integrations were not covered at all: VERI\*FACTU, web push, cloud storage, certificates, SMS, and the
Malaysian and Greek EDI.
- A mail sink cloned from a `cli` server still sent through `odoo.conf`.

On 16–19 a copy now first runs Odoo's own `neutralize.sql` for every installed module. Then the tool's rules,
now checked against each version's sources, cover 12–19 and the OCA modules.
- **A failed restore or duplication left a half-made copy.** It could be armed and visible to the instance's
cron worker, the service stayed stopped, and every retry was refused. Each copy is now one step: done whole, or
its database is dropped and the service started again. A local restore is now done like a duplication, without
stopping the service. An interrupted copy is recognised and replaced.
- The copy's role can connect to it only once it is handed over: an Odoo with `db_name` set could reach the copy
before it was neutralised.

- **Root shell and superuser exposure.**
- The certificate paths and the venv package names reached a root shell unescaped, so a file or package named
`$(…)` ran as root.
Expand Down
49 changes: 33 additions & 16 deletions docs/operations/instance-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,29 +100,40 @@ for a refresh in place):
- **Copied (new UUID on target)** — the target gets its own `database.uuid`, `database.secret` and creation
date, as Odoo's own copy does.
- **Moved (keep UUID)** — the target keeps its identity: the database "moves".
- **Neutralize** (recommended) — the copy cannot act as production. It follows Odoo's own `neutralize.sql`
(16.0–19.0) on every version from 12 to 19, plus the OCA modules a Spanish or queue-based instance runs:
- **Neutralize** (recommended) — the copy cannot act as production. On 16.0–19.0 it first runs Odoo's own
neutralisation: the `neutralize.sql` of every module installed in the copy, read from the instance's
checkout, including payment terminals and foreign EDIs. Then, on every version from 12 to 19, it applies the
tool's own rules, which follow Odoo's and add the OCA modules a Spanish or queue-based instance runs:
- crons off, except Odoo's autovacuum and queue_job's cleanup; queued jobs held;
- outgoing mail servers off with their credentials dropped, fetchmail off, templates' fixed servers cleared,
and one active **mail sink** (`invalid:1025`) so Odoo never falls back to the `smtp_server` of `odoo.conf`;
- mail:
- outgoing mail servers are switched off, their credentials dropped and their host pointed nowhere;
- fetchmail is off and templates' fixed servers are cleared;
- one active **mail sink** (`invalid:1025`, logging in) stays, so Odoo never falls back to the
`smtp_server` of `odoo.conf`;
- payment providers, external carriers and their production mode, OAuth providers;
- Google and Microsoft calendar tokens, webhooks, IAP accounts;
- SII, TicketBAI, EDI proxy and Peppol in test mode;
- Google and Microsoft calendar tokens, webhooks, IAP accounts, web push keys and devices, cloud storage,
certificate and SMS passwords;
- test mode for the tax and invoicing links:
- Spain: SII, TicketBAI and VERI*FACTU, both Odoo's and OCA's;
- the EDI proxy, Peppol, and the Malaysian and Greek EDI;
- website domain and CDN cleared, `web.base.url` pointing at the target, the "neutralised" banner and flag.

The rules, each with its source, are in `instance_manager/neutralise.py`. They run as the copy's owner role,
not as the PostgreSQL superuser, so code the source database carries (a trigger, a function) gets no more
rights than its own role has.
The rules, each with its source, are in `instance_manager/neutralise.py`. Everything runs as the copy's owner
role, not as the PostgreSQL superuser, so code the source database carries (a trigger, a function) gets no
more rights than its own role has.

A copy is never visible to a running Odoo before it is neutralised. An Odoo's cron worker lists every database
its role owns, whatever the `dbfilter`, so a duplication goes this way:
its role owns, whatever the `dbfilter`, and one with `db_name` set connects to it by name. So the copy is owned
by postgres, and closed to every role, until it is handed over. The whole copy is **one step**: if any part
fails, the database it created is dropped, and nothing half-made is left behind.

```mermaid
flowchart LR
seed[("Copy the database<br/>owned by postgres")] --> identity["Copied mode:<br/>new identity"]
identity --> neutralise["Neutralise<br/>one statement"]
identity --> odoo["Odoo's own<br/>neutralize.sql"]
odoo --> neutralise["The tool's rules<br/>one statement"]
neutralise --> check{"Anything left<br/>that acts outside?"}
check -- yes --> stop(["Stop the plan"])
check -- yes --> stop(["Drop the copy,<br/>stop the plan"])
check -- no --> handover["Hand the database<br/>to the target role"]
handover --> start["Start the<br/>target service"]
classDef step fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
Expand All @@ -132,16 +143,22 @@ flowchart LR
classDef data fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
class identity,start step
class check ask
class neutralise,handover guard
class odoo,neutralise,handover guard
class stop stop
class seed data
```

A **restore** creates the database with the credentials you give, so it stops the instance's service while the
copy is restored and neutralised, and starts it again afterwards.
A **restore** into a local database goes the same way, restored as the instance's role; the service keeps
running. Into a remote database it uses the credentials you give: the service is stopped while the copy is
restored and neutralised, and started again afterwards — also if the restore fails, which drops the database it
created.

A module update switches crons back on. **Check a copy is neutralised** (in *Status & health*) lists, read-only,
what in a database can still act on the outside, rule by rule, and whether the mail sink is in place.
what in a database can still act on the outside, rule by rule, whether the mail sink is in place, and any
`cli` mail server.

A copy an interrupted run left behind (owned by postgres, marked as unfinished) is recognised and replaced by
the next restore or duplication; any other database is never taken for one.

Restore refuses to overwrite an existing target **database**. An existing target **filestore** needs an explicit
overwrite, and is then moved aside to `<filestore>.replaced-<timestamp>`, not deleted; the restored files are
Expand Down
7 changes: 7 additions & 0 deletions instance_manager/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,4 +974,11 @@ def tf(template: str, *args: object, **kwargs: object) -> str:
'Control characters are not allowed.': 'No se admiten caracteres de control.',
'Ignored (not a package): {}': 'Ignorado (no es un paquete): {}',
'Invalid service name. Use letters, digits and @ . _ : -': 'Nombre de servicio no válido. Usa letras, dígitos y @ . _ : -',
"Run Odoo's own neutralize.sql of every installed module (16.0-19.0)": 'Ejecutar el neutralize.sql de Odoo de cada módulo instalado (16.0-19.0)',
'Close the copy to every role until it is handed over (revoke PUBLIC connect)': 'Cerrar la copia a todos los roles hasta el traspaso (revocar CONNECT a PUBLIC)',
'Restore {} into {}, neutralise it and hand it over (undone if a step fails)': 'Restaurar {} en {}, neutralizarla y traspasarla (se deshace si falla un paso)',
'Copy {} into {}, neutralise it and hand it over (undone if a step fails)': 'Copiar {} en {}, neutralizarla y traspasarla (se deshace si falla un paso)',
'Restore the dump as role {}': 'Restaurar el dump como rol {}',
"'cli' servers send through odoo.conf's smtp_server": "los servidores 'cli' envían por el smtp_server de odoo.conf",
'The copy failed: its unfinished database {} is dropped.': 'La copia ha fallado: se elimina su base de datos a medias {}.',
}
Loading
Loading