Metadata-Version: 2.4
Name: odooctl
Version: 0.2.0
Summary: CLI-first Odoo-aware deployment platform for self-hosted Odoo projects
Project-URL: Homepage, https://github.com/odooctl/odooctl
Project-URL: Documentation, https://github.com/odooctl/odooctl#readme
Project-URL: Repository, https://github.com/odooctl/odooctl
Project-URL: Issues, https://github.com/odooctl/odooctl/issues
Author-email: Rami Shalabi <hi@ramishalabi.xyz>
License-Expression: AGPL-3.0-or-later
License-File: LICENSE
Keywords: backup,cli,deployment,docker-compose,odoo,staging
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Framework :: Pydantic :: 2
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: System :: Archiving :: Backup
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: pydantic>=2
Requires-Dist: pyyaml>=6
Requires-Dist: rich>=13
Requires-Dist: typer>=0.12
Provides-Extra: api
Requires-Dist: fastapi>=0.111; extra == 'api'
Requires-Dist: httpx>=0.27; extra == 'api'
Requires-Dist: uvicorn>=0.29; extra == 'api'
Provides-Extra: dev
Requires-Dist: build>=1; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: s3
Requires-Dist: boto3>=1.34; extra == 's3'
Description-Content-Type: text/markdown

# <img src="https://raw.githubusercontent.com/odooctl/odooctl/master/docs/assets/logo.svg" width="26" alt=""> odooctl

[![CI](https://github.com/odooctl/odooctl/actions/workflows/ci.yml/badge.svg)](https://github.com/odooctl/odooctl/actions/workflows/ci.yml)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://github.com/odooctl/odooctl/blob/master/pyproject.toml)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)

`odooctl` is a CLI-first, Odoo-aware control plane for self-hosted Odoo on Docker Compose — think open-source Odoo.sh for your own server. It handles the operational lifecycle generic deploy tools miss: verified backups, sanitized staging clones, module updates, rollback, environment promotion, upgrade rehearsal, and health checks, all Odoo- and PostgreSQL-aware. It runs against the compose stack you already have; you keep your server, your data, and your compose files.

## Why odooctl

- **Safety by default.** Deploys to protected environments take a database + filestore backup first; failed module updates and failed health checks stop the deployment with a non-zero exit.
- **Verify before destroy.** Restores land in a temporary database and are only swapped into place after the restore succeeds — a bad backup never destroys a working environment.
- **Staging clones you can trust.** `odooctl clone production staging` sanitizes by default: mail servers, crons, payment providers, queue jobs, OAuth secrets, IAP tokens, and webhook URLs are neutralized — including wiping Odoo 19 WebAuthn passkeys — so staging cannot email customers or charge cards.
- **Adopt what you already run.** `odooctl import` detects an existing compose deployment read-only, previews the generated config, and only writes on `--yes` — then registers the project, runs preflight checks, and takes a safety backup.
- **Protected environments.** Production-tier environments require elevated confirmation for destructive operations, in the CLI, API, and web UI alike.
- **Secrets stay secret.** Config references secrets by env-var name only; logs, errors, and streamed operation events are redacted.
- **A local API and web UI, RBAC'd.** `odooctl serve` runs a localhost-only REST API plus a no-build web UI with viewer/operator/admin/owner roles, HMAC bearer tokens, and a privileged runner kept in a separate process.
- **Upgrade rehearsal, not upgrade roulette.** `odooctl migrate rehearse` clones production into a throwaway DB, runs the (OpenUpgrade-backed) upgrade there, health-checks, and writes a report. Production is never touched.

## Quickstart

### A. Adopt an existing Docker Compose deployment

```bash
# Read-only detection and preview — no containers touched, no secrets stored
odooctl import /srv/odoo/acme

# Adopt: write odooctl.yml, register the project, run doctor, take a safety backup
odooctl import /srv/odoo/acme --name acme --yes

# First verified backup, then a sanitized staging environment in one command
odooctl -p acme backup production --verify
odooctl -p acme env create staging --clone-from production
```

### B. Start a fresh project

```bash
mkdir acme && cd acme
odooctl setup --name acme --stack odoo-19-community   # or run interactively: odooctl setup
```

`setup` scaffolds `odooctl.yml` from a pinned stack template (secrets referenced by env-var name, never inlined). Edit the domain, database names, and your `docker-compose.yml`, then:

```bash
export ODOO_DB_PASSWORD='a-strong-secret'
odooctl validate
odooctl doctor
odooctl deploy production --branch main
```

Either way, day-2 operations look like:

```bash
odooctl backup production --verify
odooctl clone production staging --sanitize
odooctl update-modules staging --modules sale,stock
odooctl restore staging --backup latest
odooctl rollback production --mode code
odooctl migrate rehearse --env production --to 19.0 --openupgrade
odooctl status
```

## Features

| Feature | Command(s) | Docs |
| --- | --- | --- |
| Import / takeover of existing stacks | `odooctl import` | [docs/getting-started.md](docs/getting-started.md) |
| Deploy with pre-deploy backups | `odooctl deploy` | [docs/deployment.md](docs/deployment.md) |
| Verified backups, safe restores | `odooctl backup --verify`, `odooctl restore` | [docs/backup-restore.md](docs/backup-restore.md) |
| Sanitized staging clones | `odooctl clone`, `odooctl env create` | [docs/staging-clone.md](docs/staging-clone.md), [docs/environments.md](docs/environments.md) |
| Rollback (code or full) | `odooctl rollback` | [docs/rollback.md](docs/rollback.md) |
| Environment promotion | `odooctl promote` | [docs/environments.md](docs/environments.md) |
| Preflight checks | `odooctl doctor` | [docs/doctor.md](docs/doctor.md) |
| Upgrade rehearsal (17 → 18 → 19) | `odooctl migrate matrix / scan / rehearse` | [docs/migration.md](docs/migration.md) |
| Disaster-recovery drills | `odooctl dr drill` | [docs/disaster-recovery.md](docs/disaster-recovery.md) |
| Domains / SSL via reverse proxy | `odooctl domain` | [docs/domains-ssl.md](docs/domains-ssl.md) |
| Local REST API + operation queue | `odooctl serve`, `odooctl runner`, `odooctl ops` | [docs/api.md](docs/api.md) |
| RBAC, tokens, secret store | `odooctl security` | [docs/rbac.md](docs/rbac.md) |
| Stack / addon catalog | `odooctl catalog`, `odooctl setup` | [docs/catalog.md](docs/catalog.md) |
| Scheduled backups / checks | `odooctl schedule` | [docs/getting-started.md](docs/getting-started.md) |

## Web UI

The optional web UI is a no-build vanilla-JS SPA served by the local API:

```bash
pip install 'odooctl[api]'
export ODOOCTL_API_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
odooctl serve            # binds 127.0.0.1:8787 by default
```

Open `http://localhost:8787/` and paste a token minted with:

```bash
odooctl security token mint --action api --env "*" --project "*" --role operator
```

<!-- TODO: screenshot -->

The server is localhost-only by design and speaks plain HTTP — do not bind it to a public address; use an SSH tunnel for remote access. The API process never touches Docker or PostgreSQL directly: privileged work is delegated to `odooctl runner` through a durable queue with short-lived, single-use capability tokens. See [docs/web-ui.md](docs/web-ui.md) and [docs/api.md](docs/api.md).

## How it compares

Compared to **Odoo.sh**, odooctl is self-hosted and free: your server, your data, no per-worker pricing — with the same core workflow of environments, backups, staging builds, and upgrade testing. Compared to **doodba**, odooctl is not a project scaffolding framework; it manages the operational lifecycle (backup, clone, restore, promote, rehearse) of whatever compose project you already have — including one built with doodba. Compared to **hand-rolled compose scripts**, every destructive path here ships with tested safety rails: pre-deploy backups, restore-into-temp-then-swap, sanitization that is on by default, protected-environment confirmation, and secret redaction, backed by 700+ unit tests plus a real-Odoo integration suite.

## Supported Odoo versions

Odoo **17, 18, and 19** (Community) are integration-tested: a disposable-stack harness in `tests/integration/` runs the full operator lifecycle against real `odoo:17`/`odoo:18`/`odoo:19` images. See [docs/odoo-versions.md](docs/odoo-versions.md) for per-version notes.

## Install

PyPI publication is coming soon — `pipx install odooctl` / `uv tool install odooctl` will work once released. Today, install from source:

```bash
git clone https://github.com/odooctl/odooctl && cd odooctl
pipx install .          # or: uv tool install .
odooctl --help
```

The host also needs Docker Engine with the Compose plugin and `tar`. With the recommended `execution_mode: docker`, PostgreSQL client tools run inside your DB container, so the host needs none. See [docs/installation.md](docs/installation.md).

## Development

```bash
uv venv
uv pip install -e '.[dev]'
pytest -q                              # unit suite
pytest -m integration tests/integration  # opt-in real-Odoo matrix (needs Docker)
```

## Safety defaults

- Deploys to protected environments create database and filestore backups first.
- Module-update and health-check failures fail the deployment with a non-zero exit.
- Restores go into a temporary database and are swapped in only after success.
- Clone sanitization is on by default and disables mail, fetchmail, crons, payment providers, queue jobs, and automation rules; scrubs OAuth/IAP/webhook credentials; deletes Odoo 19 passkeys; and rewrites and freezes `web.base.url`.
- Secrets are referenced via environment variables (`password_env`) and redacted from logs, errors, and operation events; never commit secret values.
- The API/runner split is structurally enforced: `odooctl security runner-check` verifies the API layer imports no privileged adapters.

See [docs/security.md](docs/security.md) for the trust model.

## Contributing, security, license

Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). Good entry points are issues labeled [`good first issue`](https://github.com/odooctl/odooctl/labels/good%20first%20issue) and [`help wanted`](https://github.com/odooctl/odooctl/labels/help%20wanted). Report vulnerabilities privately per [SECURITY.md](SECURITY.md).

odooctl is free software, dual-licensed: [AGPL-3.0-or-later](LICENSE) for open use — including running it commercially for yourself or your clients — with a commercial license available for embedding or reselling it in proprietary products. See [LICENSING.md](LICENSING.md).
