Client production bootstrap¶
client_production creates one fresh Odoo 19 production through the client's
Oduflow 1.75 REST API. The production is named main, uses the verified GitHub
branch, and sets template_name to an empty string. Its repository URL never
contains a credential. This is separate from the control-plane Odoo in Megaflow.
Automatic control-plane queue¶
After configure succeeds, Odoo queues production through the fixed
oduflow.production_start/status endpoints. The worker runs only
roles.client_production. The manager's Start / Retry Production button
also supports previously configured instances. It creates only a missing
journal operation; existing infrastructure is not reallocated.
The provider verifies VM/volume ownership and attachment and supplies canonical
network.public_ipv4 in committed pillar. Git credentials must already exist in
the encrypted bundle or come from explicit ODUFLOW_GITHUB_CLIENT_TOKEN;
the management token is never used as an implicit fallback. The demo uses the
user-provided temporary development credential in that explicit setting.
The role prepares configuration, checks authenticated application health, closes only the host's public HTTP/HTTPS ingress, creates and hardens production, verifies its immutable container ID, restores its own host rules, retries ACME through Traefik when needed, verifies real HTTPS login/logout, and, when the immutable snapshot opts in, takes the first cold R2 backup and verifies a scoped restore. It does not edit provider firewall groups. The standalone provider guard below remains useful for operator-driven debugging.
An interrupted bootstrap keeps ingress closed. A Docker prerequisite service restores an unfinished host guard after reboot before containers start. Failed creation or an unknown POST outcome never opens ingress or authorizes another POST. Previously hardened, published production is verified without resetting its password, closing ingress, recreating resources or restarting containers.
Odoo retains separate durable dispatch receipts for configuration and production; 250 polls at 30-second intervals cover the worker's two-hour execution window. A completed production operation is not repeated after configuration updates. Actual coding/LLM workflow verification remains a separate final step.
Inputs¶
The existing client application pillar must include dns.production,
dns.oduflow, oduflow.ui_password, and:
oduflow:
production_admin_password: <generated encrypted instance secret>
git:
host: github.com
username: <GitHub credential username>
token: <authorized GitHub token>
repo: oduflow/oduflow-demo
branch: <actual repository branch recorded by the control plane>
Salt writes configuration and the three credential files under /etc/oduflow
as root-owned mode 0600, with changes hidden. The helper checks the single
configured team against /etc/oduflow/oduflow.toml: team ID, hostname, API bind
address/port, UI credential, and mounted data directory must agree. The current
client configuration uses team 1 and 172.17.0.1:8000.
Before creation, the helper stores the Git credential in
/srv/oduflow/data/team_1/.git-credentials using git credential approve with
stdin. The token is absent from process arguments, environment, repository URL,
Docker labels and diagnostic output. It verifies read access to the configured
branch with git ls-remote; errors are sanitized. Git's store is plaintext on
disk, root-owned mode 0600, in the client team's data directory. A temporary
token must be replaced with the intended long-lived credential before future
fetches or deployment updates can work after revocation.
First deployment order¶
- Keep the client's provider firewall closed to public inbound TCP
80and443, for both IPv4 and IPv6. SSH and VPN remain independently permitted. Oduflow starts a routable production container before its API call returns; therefore this external publication guard is required throughout bootstrap. Usescripts/guard-client-production.pyto preserve and close the exact provider rules. A successful API response is insufficient: verify fresh external connections actually fail. In the live demo, provider rules were removed while the public endpoints still responded. The additionalclient/scripts/client-publication-guard.py close --instance-uuid <UUID> --public-ip <IP>blocks only inbound TCP 80/443 on the identified public interface, for IPv4 and IPv6, before Docker DNAT. Its root-only receipt records the current boot. These temporary host rules do not survive a reboot; keep the same boot throughout initial creation and hardening. - On that client, write
/etc/oduflow/production-publication-guard.json, owned by root and mode0600, after checking the actual firewall rules:
{
"instance_uuid": "<exact client UUID>",
"domain": "oduflow-demo.oduflow.sh",
"firewall_group_id": "<attached and verified provider firewall ID>",
"public_ingress_closed": true
}
The guard is the controller's attestation. The helper does not call Vultr or
independently inspect the provider firewall, and never opens public ports.
3. Refresh the minion's pillar and apply client_production through the master:
The state starts the configured client applications if needed. Its creation
command only runs when the publication guard exists. Read its stateful result:
publication is allowed only after it reports status: hardened.
4. The helper writes the Odoo administrator login (admin by default) and strong
password through an Odoo ORM shell over Docker stdin, then explicitly commits.
No admin or PostgreSQL password is placed in argv. It validates the production
metadata and Docker labels and targets the inspected immutable container ID.
5. Open public TCP 80/443 only after hardening, remove the guard, and verify
HTTPS plus Odoo login. HTTP-01 issuance cannot complete while the ports are
closed; allow Traefik to retry after publication. A hardened receipt confirms
the password transaction, not completion of certificate issuance or the
application's public health check.
Restore the provider's saved rules using the hardened container attestation,
then run client-publication-guard.py restore with the same identity to remove
only its own host rules. Run /usr/local/libexec/oduflow-verify-production as root on
the client (or the repository wrapper client/scripts/verify-client-production.py): it checks the immutable container identity, performs a real Odoo
19 HTTPS login/logout, and checks both panels reject anonymous API requests
and accept their configured credentials. Its output contains only statuses.
Recovery and repeatability¶
The root-only receipt at /var/lib/oduflow/production/receipt.json is fsynced
before the creation POST. An interrupted or failed POST is never blindly repeated.
A subsequent run may adopt a matching production only when that receipt exists;
name, domain, clean repository URL, branch and image must match. An existing
production without a receipt is not modified.
If the outcome is unknown and no production exists, the helper stops for explicit reconciliation. Do not delete the receipt as an automatic retry mechanism: the original API request could still be executing. A completed receipt makes reruns read-only and does not reset a password changed subsequently by the administrator. The Salt state deliberately skips its command after the publication guard is removed. Credential rotation is a separate operation.