Infrastructure master automation¶
oduflow_master manages a logical infrastructure master independently of client
instances and cloud providers. It installs onto an explicitly selected SSH host;
it does not purchase a VM or replace the currently configured master implicitly.
Only Oduflow Admin can change nodes, submit operations or enter credentials.
Observers can read sanitized node, operation and backup records. Global company
rules also apply to searches, counts and individual records.
Configure a node¶
Open Oduflow → Infrastructure Masters and create a node with its SSH address,
port, login and independently verified OpenSSH SHA256:... host-key fingerprint.
Unknown or mismatched host keys are not accepted. Select password, private key,
or certificate authentication. An SSH certificate is a separate input and still
requires its matching private key. A sudo password is optional for accounts that
cannot use noninteractive sudo.
Enter the public master hostname, distinct master/control VPN addresses, HTTPS control gateway origin and certificate contact email. The deployment revision defaults to the control checkout's current Git commit and is visible under Deployment Details. The node UUID identifies the logical master throughout backup and recovery; it is not an editable cloud-resource identifier.
Before deployment, create the public DNS records for the master hostname and point them at the intended host. This workflow does not create or switch DNS records. Public reachability and DNS must be ready for certificate issuance. A later failover also requires an explicit DNS change at the appropriate point after fencing the old master.
A new Headscale installation requires the control gateway to join its VPN.
Retrieve the private enrollment handoff with the standalone CLI's handoff
command and apply it through the control gateway's enrollment process. Do not
copy the handoff into operation comments or ordinary Odoo fields. Until this
connection exists, control_network_pending is an expected prerequisite failure.
Use Store Credentials to enter SSH and control credentials. Inputs are
removed before ordinary ORM storage; the transient wizard persists only an
encrypted payload and clears it after use. The permanent secret model has no
access grants, views or chatter. Fernet encryption uses the existing deployment
ODUFLOW_ENCRYPTION_KEY and binds ciphertext to its node or immutable operation.
The encryption key is never stored in Odoo.
For a new Deploy operation, omitted pillar/API credentials can be taken privately
from ODUFLOW_PILLAR_TOKEN and ODUFLOW_SALT_API_PASSWORD. They are frozen in
the encrypted operation bundle and are not displayed in the wizard. The gateway
CA certificate must be supplied explicitly. Changing node credentials does not
change credentials already frozen for an operation.
Operations and ambiguous outcomes¶
- Deploy installs and checks the master stack on the selected host.
- Check checks the existing master and records a reduced verified result.
- Backup creates and verifies an encrypted repository snapshot. Services may pause briefly while a consistent local copy is captured.
- Restore opens a separate recovery dialog for a new empty target.
Every operation has a unique request UUID, immutable configuration, its digest, connection snapshot and encrypted secret snapshot. Local bundle/credential preflight runs before durable dispatch intent. A deterministic local failure is reported as failed with a fixed error category; no remote request is sent.
Dispatch intent is committed independently of the queue transaction. After an ambiguous send or worker death, the queue polls the same UUID. It never converts an unknown result into a successful operation. A missing remote request may resume its interrupted upload through the transport's exact-request protocol: the remote executor checks immutable inputs under its lock and commits its claim before spawning. An accepted executor is never spawned again, including after its death. A missing local/remote response alone is not proof that execution did not happen.
Operations block configuration changes and other operations while queued, running or unresolved. Reconcile continues the existing request; it does not create a new operation. Polling is bounded. Transport error strings and raw command output are not placed in queue descriptions, operation messages or chatter. The UI displays fixed error categories and reduced counts. A terminal receipt is matched to node UUID, request UUID, action and configuration digest.
The master executor invokes fixed Salt phases in an isolated process pinned to
Salt 3006.27. It reuses the reviewed state cache/event guards: automatic sls.p
and compiled state-cache writes are discarded in memory, per-state events are
suppressed, and parallel state execution is refused before spawning. The local
Salt cache directory is private tmpfs storage under /dev/shm; normal worker
cleanup removes it. Raw Salt JSON is captured only for reduction, not written to
operation receipts or logs. Global job/pillar caching stays disabled.
Backup and recovery¶
Set a dedicated Restic S3 repository URL and enter its repository password and S3 credentials through Store Credentials. Enable Initialize Empty Backup Repository only for a new repository; a successful backup clears that option. Keep these recovery credentials in an independent private location. A backup must remain usable if the Odoo database or its encryption key is unavailable.
A registered backup contains a verified snapshot ID, manifest digest, source revision, creation time, file/byte counts and pinned service versions. Operators cannot manufacture or edit a verified backup record through ordinary RPC. Its source operation retains the configuration and encrypted credentials used for that snapshot.
Restore requires all of the following:
- A verified snapshot belonging to the logical master.
- A different target SSH address and separately verified host key.
- Fresh target SSH credentials; a current Odoo password recheck by the same administrator who opened the dialog.
- An explicit statement that the old master is powered off or network-isolated and will remain fenced, plus confirmation that the target is empty.
Fencing is an operator attestation, not an automatic inspection of the old host. The queued operation stores its method, user, timestamp and snapshot. Remote checks enforce the empty-target and identity constraints. The selected backup supplies the master identity, network configuration and source revision; current editable node settings do not silently replace its hostname or VPN identity. The new target SSH port is applied explicitly.
The node continues to show the original connection until the restore's terminal receipt is verified. Only then does it switch to the new connection and restored configuration. Health checks must complete as part of the remote restore; verifying archive integrity alone does not mean the master is operational.
Standalone recovery CLI¶
The same transport is available without a running Odoo database through
ops.master_bootstrap.cli. Run it from the reviewed source checkout with a
private, owner-only JSON input file:
python3 -m ops.master_bootstrap.cli --input /private/master.json --request REQUEST_UUID start --action install
python3 -m ops.master_bootstrap.cli --input /private/master.json --request REQUEST_UUID status
python3 -m ops.master_bootstrap.cli --input /private/master.json --request REQUEST_UUID resume --action install
python3 -m ops.master_bootstrap.cli --input /private/master.json --request REQUEST_UUID handoff --output /private/enrollment.json
The input has this shape; secret-file values are absolute paths to separate private files, not their contents:
{
"connection": {
"host": "192.0.2.10",
"port": 22,
"username": "root",
"host_key_sha256": "SHA256:<verified fingerprint>",
"private_key": "<private key material>"
},
"config": {
"node_uuid": "<logical master UUID>",
"hostname": "master.example.org",
"master_vpn_ip": "100.64.0.1",
"control_vpn_ip": "100.64.0.2",
"pillar_url": "https://control.example.org",
"acme_email": "admin@example.org",
"ssh_port": 22,
"source_revision": "<full reviewed Git revision>"
},
"secret_files": {
"pillar-token": "/private/pillar-token",
"api-password": "/private/api-password",
"gateway-ca.crt": "/private/gateway-ca.crt",
"master-backup.json": "/private/master-backup.json"
}
}
The entire input file is private because SSH credentials can be inline. The CLI
also accepts the transport's password/certificate/private-key-passphrase and
sudo-password inputs. It does not read credentials from environment variables.
For restore, include the selected restore.snapshot_id and
restore.manifest_digest in configuration and the source_fencing attestation.
Stdout contains only a reduced receipt/error. Enrollment handoff material is
written only to the explicit private output file, never stdout.
Standalone means that the CLI can run and restore the master's identity files without using an Odoo database to launch the operation. Final operational health still depends on the control VPN, gateway trust and reachable Odoo pillar service. If those services are unavailable, identity recovery and an operationally healthy master are separate outcomes; the CLI does not bypass the final health checks.
Verification limits¶
Odoo tests cover role/company enforcement, encrypted wizard storage, immutable snapshots, preflight failures, lost responses, strict receipt adoption and password-confirmed recovery. Transport and backup tests cover their own SSH, filesystem, process, repository and identity contracts. These tests do not prove that a new production host has been provisioned. Report source revision, deployed modules and any real host/recovery checks separately.