Skip to main content

How Antelao is put together

A handful of ideas explain most of the product's behaviour: what a server, an instance and a database are, which part decides and which does the work, why every change is a background job, and where the state lives.

Server, instance, database​

Three nouns, nested.

A server is one Linux host running the managed stack. An instance is one Odoo deployment on it: its own container, its own Odoo version and edition, its own addons and its own host names. A database is an Odoo database inside an instance. All databases from all instances live in the one shared ohm-postgres container.

Each instance has a twelve-character id, and its container is named ohm- followed by it.

The agent, the API and who does the work​

The ports the console, the API and the agent answer on:

PortProtocolBound toFirewall scriptServiceWhat it is
5201HTTPLoopbackNothingohm-agentThe host agent, for the command line and the server's own parts
5202HTTPSEvery interfaceYour addressesohm-agentThe host agent's page and API. Protected by its key and by who may reach the agent
5311HTTPLoopbackNothingohm-apiThe management API, for the command line and the console on the server itself
5312HTTPSEvery interfaceYour addressesohm-apiThe management API
5321HTTPLoopbackNothingohm-webThe console, for the server itself and an SSH tunnel
5322HTTPSEvery interfaceYour addressesohm-webThe console
5323HTTPSEvery interfaceYour addressesohm-webThe browser editor an instance's Code tab opens, with the console's certificate. Allow it wherever you allow 5322

Four parts, and the split between them is the single most useful thing to know.

The API decides. It owns the job queue, the scheduler and the master database. The console holds no Docker socket and no domain code at all: it is an HTTP client of the API and nothing else. ohm is the same by default, and only touches the host directly in local mode.

The agent does. It is a container the host's own systemd unit runs, and it is what actually creates containers, writes files and installs the server - including recreating ohm-api and ohm-web themselves. That is why it is a separate thing: something has to be able to rebuild the management plane, and it cannot be the management plane.

The split is absolute rather than conventional. The API holds no Docker socket and mounts no data tree - not the instance trees, not the backups, not the keys. Anything that touches the daemon or the disk, it asks the agent to do. Two things follow that are worth knowing before you debug anything:

  • A container that has only the API in it cannot be used to inspect an instance's files. There is nothing there to inspect.
  • The license's hardware identity is read by the agent, on the host, and handed to the API. The API is not the thing being identified, which is why replacing the API container does not disturb a license.

Two practical consequences:

  • If the API container is down, the console can show you nothing and change nothing. The agent's own page on 5202 still works, and ohm --local still works, which is why the repair commands are local-only.
  • The agent and the API must be the same build. A mismatch refuses every dispatch rather than letting a stale agent act on a model it no longer shares. Core services shows both builds, in red when they disagree.

Everything is a background job​

Every state-changing operation is queued as a job rather than performed inline. The API accepts your request, writes a job row, returns immediately and works through the queue.

This is why the console shows queued -> running -> completed rather than success straight away, and why a remote ohm command streams a log until it finishes.

Jobs are persisted and resumable. If the API restarts mid-queue, queued jobs are picked up again; jobs that were running are marked Interrupted, because half-finished work may be destructive and re-running it blindly is worse than stopping.

Concurrency is bounded by job concurrency under Configuration, which defaults to 1, meaning jobs run one at a time. Changing it takes effect without a restart. Two rules hold whatever you set it to:

  • Two jobs never run against the same instance at once.
  • A server-level job (one with no instance) drains the queue and runs alone, because it mutates Docker, PostgreSQL or the filesystem that everything else shares.

Secrets never reach a job. A password you submit is kept out of the job and used once; a password the server generates is shown to you once, and can be read for ten minutes only. Neither appears in the job, its result or its log.

Every operation, with the log it captured. It is the first place to look when something started from the console does not finish.

Where state lives​

WhatWhere
The host's half of the server configuration/etc/antelao/agent.env and /var/lib/ohm-agent/host-state.json, served by the agent
The policy halfThe master database
Instance configurationThe master database
Users, jobs, schedules, statistics, the license registerThe master database
Instance data trees, backups, secretsThe filesystem under /var/lib/odoo
Licenses*.lic files under the application root's license/ directory

There is no server configuration file. The two halves and their owners are set out in Configuration and state.

The master database is PostgreSQL in the bundled ohm-postgres container by default, and can be an external SQL Server. Its schema is owned by the application and upgraded forward. That upgrade is forward-only: an older binary refuses to start against a newer schema, so a rollback means restoring the database from before the upgrade too.

Saved, running, and what still differs​

An instance has two configurations: the one you have edited, and the one the running container was actually built with. Antelao keeps a snapshot of the second.

Saving a configuration applies itself: the apply is queued behind the save and converges the running system - the odoo.conf, the nginx site, a restart when the change needs one. You do not have to remember a second step.

What still differs afterwards is on the Pending changes page, each entry with its reason and the action that goes with it: an apply on its way, a failed apply with its error and a retry, or something the apply deliberately leaves to you - the image rebuild, the certificate, the modules, the server install. The badge beside your account counts them.

Some settings only take effect when the container is recreated rather than restarted. Anything that changes a bind mount is in that category, addons paths above all. The apply picks the right action; doing it by hand with a restart may silently do nothing.

Events​

The API publishes one event stream that carries signals, not data: "this job changed", "this instance changed state", "there are new statistics". Clients then re-read the authoritative state through the normal endpoints, which are role-scoped.

A signal that says "something changed" therefore shows nothing to a viewer who is not allowed to see what changed.