Skip to content

0.1 Config | CLI, declarative .env, cascade

Omisen edited this page Aug 17, 2026 · 3 revisions

Configuration resolution: where the parameters come from and in which order of priority. It replaces the logic of the original Bash lib/cli.sh, whose semantics (not form) it reproduces. It lives in src/cli.rs (flags), src/config.rs (cascade, .env parser, validators), src/prompt.rs (prompts) and src/secret.rs (password). The result is a Context that the steps read.


The priority cascade

Every parameter is resolved with this precedence (highest first):

1. CLI argument   →   2. interactive prompt   →   3. .env file   →   4. final default

In Rust the distinction between “flag not passed” and “flag passed with the default value” is modelled with Option<T>: Some(v) = explicitly supplied (at any value), None = not supplied. That replaces Bash's CLI_*_SET booleans, carrying the same information in a typed way.

The instance name is resolved first, because everything else is named after it: the system user, the PostgreSQL role, the database, the install dir, the unit, the config file, the helper. Those are derived defaults, so an explicit value in a .env beats them — which is why a configuration file for a named instance should name as little as possible, or it hands the second instance the first one's user and database without a word. The gevent port is derived the same way, from --port + 3, and refused if it equals it.

Resolution (ResolvedConfig::resolve) is a pure function: no I/O, no prompts. The interactive prompts live in prompt.rs and fill a further layer that overlays the .env. If stdin/stdout are not a TTY the prompts are skipped (CLI → .env → default).


Parameters and validation

Parameter CLI flag .env key Default Validation
Version --version ODOO_VERSION 18.0 16|17|18|19N.0; or 16.0..19.0
OS user --odoo-user ODOO_USER odoo identifier ^[A-Za-z0-9._-]+$
DB user --db-user DB_USER = odoo-user identifier
Port --port ODOO_PORT 8069 integer 1–65535
DB name --db-name DB_NAME odoo identifier (non-empty)
Install dir --install-dir ODOO_INSTALL_DIR /opt/odoo/odoo<N> absolute, under /opt/odoo
Admin password --admin-passwd ODOO_ADMIN_PASSWD admin non-empty; admin requires confirmation
Nginx --with-nginx WITH_NGINX false boolean
Config file --config <FILE> an existing .env file

ODOO_HOME is the constant /opt/odoo: an ODOO_HOME key in the .env produces a warning and is ignored.


Subtle rules (carried over from Bash)

db_user follows odoo_user

If --db-user is not passed, db_user = odoo_user (including with a customised odoo_user). An explicit CLI value, or a DB_USER in the .env that differs from the odoo default, decouples them.

The admin password

Allowed only with an explicit interactive confirmation.

Situation Outcome
password ≠ admin fine, no confirmation
password = admin, TTY present y/N confirmation prompt
password = admin, non-interactive error + stop (it cannot be confirmed without a TTY)
empty password error

The password never reaches the logs: it is wrapped in a Secret type whose Debug is redacted (Secret(****)), and it does not appear in the configuration summary.

Install-dir scope

It must be /opt/odoo or below. The check uses Path::starts_with (component-based), so /opt/odoofoo is correctly outside the scope — more robust than Bash's string-prefix comparison.

Version normalisation

18 and 18.0 are equivalent: internally 18.0 (full) is kept and 18 (short) is derived for file and unit names.


The .env parser — declarative, never executed

Bash did source "$CONFIG_FILE": it executes the file as Bash code with root privileges — a code-execution vector. The port does not reproduce that.

parse_env_file reads KEY=VALUE line by line:

  • blank lines and comments (#) are ignored;
  • an export prefix is tolerated;
  • quotes wrapping the value are stripped;
  • no command expansion, no eval;
  • unknown keys → warning and ignore (never a failure).

A “dangerous” value such as ODOO_ADMIN_PASSWD=$(rm -rf /) is treated as a literal string and never executed. A dedicated test proves it with a sentinel file that has to survive the parsing.

# production.env — example
ODOO_VERSION=18
ODOO_USER=odoo
ODOO_PORT=8069
DB_NAME=odoo
WITH_NGINX=true

The flow in main

parse CLI
  └─▶ load .env (if --config)              # parsed, never executed
       └─▶ interactive prompts (if TTY)    # only for fields not passed on the CLI
            └─▶ resolve (cascade + validation, pure)
                 └─▶ confirm the 'admin' password if needed
                      └─▶ Context  →  preflight checks → lock → step execution

The configuration summary is printed before anything is mutated, and with --dry-run the flow stops right after the plan.


Design notes

  • The UI sits behind a boundary: config.rs does not depend on prompt.rs. The rich UI (inquire for prompts, indicatif for progress) lives in prompt.rs/progress.rs, and the steps never talk to it — see 2. UI + dry-run.
  • Typed errors (ConfigError, thiserror) instead of Bash's exit 1: invalid version, port out of range, install-dir out of scope, empty db_name, admin password non-interactively — each is an error, never a panic.
  • No .unwrap()/.expect() in non-test code.
  • Resolution is covered by tests: the CLI > prompt > env > default cascade, the db_user rule, the .env parser with its no-eval proof, the validations, the password hard stop, version normalisation.

Clone this wiki locally