Odoo 18 database and filestore backups
scripts/backup/backup_odoo18_dbs.sh selects its databases and paths from
/srv/pmiverse/_pmiregistry/config/pmiverse.conf. It does not contain a production database list.
The default selector is the machine's short hostname. Unknown hosts and
ambiguous selections fail before database access or backup writes.
Registries
PMIVERSE_DB_REGISTRY remains the six-column database/restore registry.
PMIVERSE_INSTANCE_REGISTRY connects database aliases to an instance, using
nine comma-separated fields:
instance_id,host,version,filestore_base,backup_root,pg_host,pg_port,pg_user,database_aliases
Instance IDs must be unique. Paths are absolute, and database aliases are
separated by |. Each alias must resolve uniquely to a database with the same
Odoo version. Empty PostgreSQL fields retain libpq defaults; nonempty PGHOST,
PGPORT, and PGUSER environment values override registry connection settings.
Use the service account's existing PostgreSQL authentication; do not store
credentials in either registry.
Instance IDs, host names, and alias membership belong to
/srv/pmiverse/_pmiregistry/config/pmiverse.conf.
The registry's host field is the OS short hostname returned by hostname -s.
PMIS_BACKUP_REMOTE_HOSTS selects SSH sources for laptop synchronization;
these may be SSH configuration aliases that differ from OS hostnames.
--host overrides the registry host selector; it does not initiate SSH access.
An instance record supplies
paths and membership, while the host list selects sources to pull.
For multiple Odoo 18 instances on one host, add distinct instance IDs and use
--instance for each scheduled job. Configure separate filestore paths and
PostgreSQL endpoints as needed. Instances with unique database names can share
the host backup root and its latest index; the root lock requires their jobs
to run sequentially. Use distinct roots for instances with overlapping database
names. The flat laptop latest index requires globally unique database names;
separate-root instances require separately configured synchronization runs and
destinations. This producer deliberately selects only version 18 records.
Preview and invocation
scripts/backup/backup_odoo18_dbs.sh --host backup-a --dry-run
scripts/backup/backup_odoo18_dbs.sh --host backup-b --instance backup-b-18 --dry-run
scripts/backup/backup_odoo18_dbs.sh --help
--dry-run validates and prints the plan without contacting PostgreSQL,
creating directories, taking locks, or running the latest updater. Config is
trusted executable shell, so inspect alternate --config FILE inputs first.
Run without --dry-run only as an authorized backup operation.
CLI selectors override PMIVERSE_CONF, PMIS_BACKUP_HOST, and
PMIS_BACKUP_INSTANCE respectively. Nonempty BACKUP_ROOT, FILESTORE_BASE,
newline-delimited PMIS_BACKUP_DATABASES, BACKUP_TIMESTAMP, and
LATEST_UPDATER remain supported. Supplying the first three together without a
host or instance selector supports an explicitly configured unregistered
instance. Database names must be safe path components; duplicates, latest,
path traversal, and option-like names are rejected. Timestamps use
YYYYMMDD_HHMMSS.
Deployment-specific Odoo 18 backup launchers live in
/srv/pmiverse/_pmiregistry/scripts/backup/. They delegate to this producer;
see the deployment notes.
The examples above use synthetic hosts and instance IDs.
Archives and paths
Each remote server continues to write under
/srv/pmiverse/_pmisuite18/data/backups/:
<database>/<timestamp>.<database>.dump.zipcontains a custom-format PostgreSQL dump at ZIP root and, when present,<database>/filestore files.latest/<database>_latest.zipis a relative symlink to the newest archive.- Each database retains ten conventionally named regular archives by default.
Set
PMIS_BACKUP_RETENTIONin the active configuration to override individual databases, using actual database names rather than registry aliases:
PMIS_BACKUP_RETENTION='demo.example,30
staging.example,3'
Counts must be decimal integers from 1 through 999999999, without leading zeros.
Unlisted databases keep ten; zero is rejected. Blank lines and # comment
lines are ignored. Duplicate database entries and malformed rows fail before
database access or backup writes, including during --dry-run.
Entries for other hosts are allowed; explicit database selections use the same
settings. --dry-run displays each selected database's retention count.
After each successful publication, archives beyond the configured count are
removed in descending modification-time order (filename breaks ties).
Lowering a count takes effect on that database's next successful backup.
The canonical ZIP layout contains:
- One timestamped custom-format PostgreSQL dump at the archive root, named
<timestamp>.<db_name>.dump. - When the filestore exists, a
<db_name>/...directory at the archive root with relative paths.
The producer validates ZIP integrity, publishes without overwriting, and uses one nonblocking lock per canonical backup root. Failures return nonzero while allowing other databases to finish; successfully published archives survive. Missing filestores produce a warning and a dump-only archive. Database and filestore reads are not a coordinated transactional snapshot.
Required tools include Bash, PostgreSQL pg_dump, zip, unzip, flock, and
GNU core utilities (realpath, stat, ln, etc.). The service account needs
database access, filestore read access, and write access to its backup root.
Synchronization and rollout
The installation's full-history rsync services live in
/srv/pmiverse/_pmiregistry/templates/systemd/rsync/. They pull from the remote root
into separate directories on the local restore server. Synthetic examples:
| Remote source | Local destination |
|---|---|
backup-a:/srv/pmiverse/_pmisuite18/data/backups/ |
/srv/pmiverse/_pmisuite18/data/backups/backup-a/ |
backup-b:/srv/pmiverse/_pmisuite18/data/backups/ |
/srv/pmiverse/_pmisuite18/data/backups/backup-b/ |
Their --delete is limited to each host's local tree. The database registry's
restore source names include the local host subdirectory. For a restore run
directly on a remote producer, use --source to select its database directory
under the unchanged remote root.
Laptop synchronization still reads backups/latest/ on each remote host and
publishes a flat local latest set. Laptop restore uses the final source-directory
component as the original database name.
Only the local full-history mirror paths change. Editing templates does not install them or move existing archives; remote archives need no migration:
- Preview each server's selection and confirm its hostname or configure
PMIS_BACKUP_HOSTin its service environment. - Plan retention of existing local mirrors under the old
backups/andbackups-main/trees. Local history is not automatically relocated. Restore those copies with an explicit--sourceuntil local history has been migrated through a separately approved operation. Remote history remains in place and continues to use the producer's existing retention. - Deploy the producer, helper, and shared config together on each server.
Update the scheduled service environment if required. For multiple local
instances, the
backup_odoo18_dbs@.servicetemplate passes%ias the instance ID; install and schedule each instance explicitly, adapting the service account when needed. - Update the local restore registry and full-history rsync services together. Pre-create local mirror destinations with appropriate ownership before starting rsync. Laptop synchronization keeps its existing remote source paths. Avoid overlapping producer jobs that share one backup root.
Standalone pmis-backup-latest uses the same instance selection by default;
its --backup-root/BACKUP_ROOT overrides remain available. On a multi-instance
host its service needs PMIS_BACKUP_INSTANCE or an explicit root.
No live backup, migration, synchronization, or unit installation is part of
source validation. Run bash tests/test_backup_registry.sh and
bash tests/test_backup_locking.sh for isolated fixture coverage.
Odoo 12 scripts and units remain legacy assets outside this workflow. See generic restore and the laptop workflow.