Entity & service model
Ægir's state lives in a handful of Drupal node types: server,
platform, site, client, plus the task node that drives
every transition. This page is the operator-side reference for those
entities — their on-disk shape, their real status constants, their
permissions, and the provision-* backend each one maps to. The task
mechanics are in Task queue engine.
Server / service / service-type
A server is any host that provides one or more services; each service has a service type (the implementation). This three-layer model lets Ægir express heterogeneous topologies without hardcoding "MySQL on localhost, Nginx everywhere." In BOA practice most installs are flat (web + db on one Devuan VM), but the model still carries the topology.
Server (host)
├── Service: HTTP
│ └── Service type: Nginx ← BOA always uses Nginx
└── Service: Database
└── Service type: MySQL/Percona ← BOA always uses Percona
The task queue is not a server service. Ægir's hook_hosting_service
types are web / db / SSL only; there is no "queue" service type. BOA
processes tasks with a cron job on the Master (runner.sh →
hosting-dispatch), not a server-attached daemon.
The hosting modules behind this:
hosting_server— base server node type + admin UI;hosting.ip.inchandles per-server multi-IP.hosting_db_server— DB-server node form + the MySQL service interface. Every BOA host has one db_server pointing at local Percona; remote DB hosts use the same node type.hosting_web_server+hosting_nginx+hosting_ssl— the web-service backend.hosting_nginxbinds the abstract web service to the concrete Nginx config generation inlib/functions/nginx.sh.inc;hosting_sslruns cert issuance/renewal/toggle as Ægir tasks.
Each server node has a matching Drush alias on disk under .drush/:
server_master.alias.drushrc.php (master server) and
server_localhost.alias.drushrc.php (db server, usually the same host).
These carry the context data Provision reads for every provision-* call.
The alias files are generated output — never edit them. Ægir treats the
per-owner alias files (<entity>.alias.drushrc.php plus the
aliases.drushrc.php index, under /var/aegir/.drush/ for the master and
/data/disk/<oct>/.drush/ per Octopus) as the persistence layer for its
frontend contexts, and regenerates them from the frontend node on every
task — a hand edit survives only until the next Verify clobbers it. Drush
properties Ægir does not manage belong in the site-local
sites/<domain>/drushrc.php, which nothing in BOA rewrites.
The distributed-topology modules hosting_web_cluster / hosting_web_pack
ship in the fork but are N-graded in BOA — not exercised on a typical
install (see Discontinued features).
Platform
A platform is a Drupal codebase on disk, hosting one or more sites.
Where a platform lives depends on how it was created:
- A custom (UI-uploaded) platform lives under the instance's
hosting_platform_base_path,<root>/static/— any path under/data/disk/oN/static/is valid. A per-platform subdirectory ofstatic/platforms/is the recommended layout, purely to keepstatic/'s standard entries (control/,usage,trash,files,goaccess) from being buried under platform directories. - A BOA-bundled platform lives under
/data/disk/oN/distro/NNN/<codebase>/, whereNNNis a zero-padded numeric revision counter (the same_nextnummechanism as the Hostmaster codebase, not a catalogue symbol) and<codebase>is the built Drupal tree.
Which codebases get built is selected by the per-instance platform token list
(_PLATFORMS_LIST / static/control/platforms.info, e.g. DE2 DX5 SOC UC7),
but those tokens never appear as the directory name. The tokens are BOA's own
platform symbols:
- base Drupal cores (
DE1–DE3for D11.x,DX0–DX6for D10.x,DL9/DL7/DL6for D9/D7/D6), - plus distribution symbols (
CK1/CK2/CK3,SOC,UC7, …), - or the keyword
ALLfor every available platform.
A platform tree is the standard Drupal layout — index.php, modules/,
themes/, profiles/, and a sites/ tree with all/ (shared
modules/themes/drush) and per-site folders.
Platform states surfaced in the UI:
- Queued — new, awaiting Verify.
- Verified — usable.
- Locked — no new sites; existing sites still work
(
HOSTING_PLATFORM_LOCKED). - Deleted — soft-deleted, record kept.
Note that "locked" is a platform concept: hosting_platform.module
defines HOSTING_PLATFORM_LOCKED = -1. There is no site-level locked
state.
BOA treats a platform as immutable once any site is hosted on it. In-place Composer/Drush changes to a live platform's code (adding modules, upgrading core) are unsupported; the supported lifecycle is build a new platform, migrate sites to it, delete the old. See Migration & cloning.
Site
A site is a deployed Drupal instance, referencing a platform, a DB server, a client (ownership), and zero or more aliases.
Status constants
hosting_site.module defines exactly four status constants — there is
no INSTALLED and no site-level LOCKED:
HOSTING_SITE_DELETED = -2 // soft-deleted (record kept, files gone)
HOSTING_SITE_DISABLED = -1 // operator-disabled
HOSTING_SITE_QUEUED = 0 // new, awaiting Install
HOSTING_SITE_ENABLED = 1 // installed + enabled (the "live" state)
A site reaches the live state as HOSTING_SITE_ENABLED (value 1), not a
fabricated HOSTING_SITE_INSTALLED. Each transition is driven by a Task
node:
QUEUED ─→ (Install task) ─→ ENABLED
ENABLED ─→ (Disable task) ─→ DISABLED
ENABLED ─→ (Delete task) ─→ DELETED
ENABLED ─→ (Migrate task) ─→ ENABLED (on a new platform)
ENABLED ─→ (Backup task) ─→ ENABLED (tarball generated)
On-disk site files
Per site, under <platform-root>/sites/<domain>/:
settings.php— Drupal site settings, BOA-generated.drushrc.php— Ægir Drush config.modules//themes//files//private/.
On current BOA, a new site installed on an Octopus (/data/disk)
account gets files/ and private/ moved into the owning account's static
store — /data/disk/<account>/static/files/<domain>/{files,private} — and
replaced with symlinks as part of installation: Provision's install hook
delegates the root-privileged move to the autosymlink tool.
This is scoped to /data/disk accounts only (the master hostmaster account
keeps plain directories) and fail-open — any failure leaves plain real
directories and the install still succeeds.
Kill-switches:
- box-wide
/data/conf/disable_native_files_symlink.cnf, or - per-account
static/control/no_native_files_symlink.info.
Existing sites whose files/private are still real directories are
not auto-converted by a normal Install/Verify. Full subsystem detail —
store layout, tools, nightly automation — in
Files symlinking.
The optional per-site override surface is a boa_site_control.ini placed
in the site's modules/ subfolder
(sites/<domain>/modules/boa_site_control.ini) — copied from the
/data/conf/default.boa_site_control.ini template, which a
Verify seeds into the site as default.boa_site_control.ini for reference.
Edits take ~60 s to apply (FPM opcache). Use that INI for overrides;
settings.php itself is regenerated on every Verify, so direct edits to
it are wiped.
Permissions & ownership (BOA values)
The per-site permissions matrix as BOA actually sets it (verified against
provision-private, not the upstream architecture doc — BOA diverges from
upstream on two of these):
| Path | Ownership | Mode | Note |
|---|---|---|---|
settings.php |
aegir:www-data |
0440 (-r--r-----) |
Web server reads, cannot write. Carries no database password — see DB credentials. |
drushrc.php |
aegir:aegir |
0440 (-r--r-----) |
Set by Provision/Config/Drushrc.php ($mode = 0440). The web group is deliberately not on this file: it is where the literal DB credentials live. |
files/ |
aegir:www-data |
02775 |
Setgid for group inheritance. |
files/* |
www-data:www-data |
0644 |
Web-server-created files. |
private/ |
aegir:www-data |
02775 |
Same model as files/. |
sites/all/{modules,themes,libraries} |
aegir:aegir |
02775 |
Set by provision_drupal.drush.inc. |
Two upstream-vs-BOA divergences to note: upstream's architecture doc lists
drushrc.php as 0400 and files/ as 2770, but BOA uses 0440 and
02775 respectively. Use the BOA values above.
If permissions drift (Composer wrote with wrong ownership, etc.), the
operator fix-drupal-{platform,site}-{permissions,ownership}.sh scripts
restore the canonical state.
Client
A client owns one or more sites (hosting_client). It:
- powers multi-tenancy access control — which UI user may administer which sites;
- ties into BOA's per-Octopus tenant isolation (each Octopus instance has its own clients, isolated from other instances on the same host);
- backs per-client quotas (
hosting_quota).
A single-tenant install does not need clients; a multi-tenant install makes every external customer a client so per-client access control prevents cross-tenant visibility.
Packages (module & theme registry)
Alongside the server/platform/site/client nodes, Ægir keeps a registry of the code on your platforms:
- A package (
hosting_package, apackagenode) is one installable unit — a module, theme, or install profile — tracked network-wide. - A package instance (
hosting_package_instance) records that package as it exists on one platform or site: its version, a computed version code, its installed schema version, its enabled/disabled status, and the release it came from. - A third small table,
hosting_package_languages, tracks translations.
Nothing populates this by hand. Platform Verify enumerates the codebase and
calls hosting_package_sync() / hosting_package_instance_sync() for the
platform's base, sites/all, and each profile
(hosting_platform.drush.inc:76-131); site Verify records a per-site instance of
what that site has enabled.
The registry is the data behind two visible behaviours:
- the migrate/clone upgrade-safety comparison (which reads instance schema versions to decide where a site may move), and
- the platform node's module/theme listing.
On Drupal 10+ / Composer platforms the registry still populates — the schema
version comes from Drupal's update.update_hook_registry service rather than the
D7 API — so package tracking is not silently empty on modern cores, though the
detected package set is core-version dependent.
Entity → backend mapping
Every entity has a Provision backend counterpart that runs when a task dispatches:
- Sites —
provision/platform/{install,backup,clone,migrate,…}.provision.inc. A per-site backup writes a flat tarball<backup_path>/<uri>-<timestamp>.tar.gz(wherebackup_path = <aegir_root>/backups), e.g.~/backups/foo.example.com-20260623.101500.tar.gz— not a per-site<site>/<timestamp>.tar.gzsubdirectory. - Platforms —
provision/platform/verify.provision.incplus theplatform/drupal/*per-Drupal-major helpers. - Servers —
provision/Provision/Context/server.phpplus the service-type backends underprovision/db/andprovision/http/.
The CLI verbs that operate on these entities take Drush aliases, not
bare domains. For example provision-clone is
drush @site provision-clone @new_site @platform_name, where @new_site
must be a Drush alias already generated by provision-save — a bare
target domain will not work.
Renaming the Ægir hostname (renameaegirhost)
When a host's FQDN changes, the Ægir hostname baked into Drush aliases,
Nginx vhosts and the Ægir DB must follow. BOA ships a single universal
tool, renameaegirhost, that renames in place for either the Master
or an Octopus account — the --aegir-root argument selects which:
renameaegirhost --aegir-root /var/aegir ← BOA Master
renameaegirhost --aegir-root /data/disk/oN ← Octopus account oN
It validates the path as a real Ægir root, then:
- detects the old hostname from
<aegir-root>/.drush/server_master.alias.drushrc.phpand the new one fromhostname -f; - rewrites every Drush alias under
<aegir-root>/.drush/; - renames and rewrites the vhost files under
<aegir-root>/config/server_master/nginx/vhost.d/; - renames the hostname inside the Ægir DB with serialise-safe, targeted
statements — identity rows keyed by
hosting_contextnid,REPLACE()sweeps over plain-varchar columns, and an unserialise/replace/re-serialise pass over thevariabletable; a plain dump is taken first as a backup only, neversed-patched or re-imported (a blanket dump+sedwould corrupt PHP-serialised byte lengths when the FQDN length changes). That dump is a hard gate: it must exit clean, be non-empty and carry the dumper's own completion trailer, or the run aborts rather than rewrite the database without a complete backup; - reloads Nginx;
- flushes the Drush cache and runs a 5-pass Ægir task queue to regenerate aliases, vhosts and db-host entries from the updated database — behind a fail-closed gate: if any site directory still carries the old hostname the run aborts first, because the queue would import those as brand-new sites, giving each a duplicate panel node (the duplicate being the half that owns the data). Resolve the listed directories and re-run; a dry run reports them instead of aborting;
- waits for every renamed site to actually serve — up to
_RENAME_SERVE_WAITseconds per site (default 180), accepting 200/301/302 but self-calibrating against the box's catch-all vhost so an "Under Construction" 200 for a nonexistent Host never counts. The closing summary ends with eitherSites : all N renamed site(s) confirmed servingorNOT SERVING : <uris>— the run still exits 0, so read that line rather than the exit status; a 400 there is the trusted-host check. The wait exists becausesettings.phpis resolved through PHP's per-worker realpath cache, so a single immediate check proves nothing either way. The gate covers only sites the rename moved — an account-axis move still needs the usual manual check.
Sites whose name contains the box hostname follow the box. For each such
tenant the tool carries: the site directory itself, its per-site Drush alias
file, its static/files store and the site's own files/private symlinks
into it, its clients/<client>/ symlink, the URI-derived values inside the
provision-generated settings.php (public/private/temp file paths, the D10+
config sync directory, the syslog identity, the absolute local.settings.php
include, and trusted_host_patterns in both its plain and
backslash-escaped spellings — the escaped one is what produces an HTTP 400
when left stale), and the site's per-site PHP pin row in
static/control/multi-fpm.info (the rows key on the site URI, so a stale row
goes inert and the site silently serves on the account's default PHP). It
then queues one site verify per renamed site, because the queue's server
verifies regenerate no per-site artefact at all — none of this is
self-healing if left behind. The database name and user are never
rewritten: a settings.php carrying the old URI on a
database/username/password/db_url line is refused and counted, and
the summary prints ATTENTION : N settings.php left untouched — rewrite the
URI values in such a file by hand, never the database name.
On Octopus roots the tool also rewrites _DOMAIN (and any other old-FQDN
values) in the account's /root/.oN.octopus.cnf, which feeds the install and
config legs. The satellite UPGRADE, however, re-reads its _DOMAIN from the
per-instance identity stamp in the account's log directory — not from the
cnf — so the tool rewrites those stamps too (log/domain.txt and
log/setupmail.txt, ownership and mode preserved): a stamp naming a host
that no longer exists is exactly what the upgrade's own _DOMAIN cross-check
trips over. The master root has no octopus cnf, so the cnf step no-ops there.
Pre-flight for a standalone in-place rename. The tool takes the new
hostname from the system FQDN (hostname -f), and on a BOA box that identity
is cnf-driven: any later barracuda run — including an install's cron-fired
final phase — re-asserts _MY_HOSTN back onto the running hostname (and on a
DHCP-managed host a background monitor restores it from /etc/hostname within
seconds). A stale cnf therefore reverts the rename mid-flight, flipping
Provision into remote-host mode against the old identity — the observed
collateral is a failed self-rsync and a regenerated legacy Nginx config that
fails nginx -t. Before running the tool on an existing box, set the new
identity in all four places first: /etc/hostname, /etc/hosts,
/etc/mailname, and _MY_HOSTN (plus _MY_FRONT) in /root/.barracuda.cnf.
The tool warns when _MY_HOSTN disagrees with the detected new hostname —
treat that warning as a stop sign. Migration targets are unaffected: their
fresh install writes the cnf with the final FQDN.
Flags:
--dry-run— print planned changes, modify nothing.--force-old FQDN— override the auto-detected old hostname.
Re-running renameaegirhost is convergent: already-renamed values are
guard-skipped, so a repeat over a rename completed by the current tooling
changes nothing (including a
subdomain-augmenting rename, where the new FQDN contains the old one). One
qualification: a re-run on a box renamed by earlier tooling repairs the
per-site surfaces the older rename left behind — settings.php, the
files/private symlinks, the client symlink, the alias file and the static
store. Every repair stays conditioned on old-name evidence, so a correct site
(including one freshly installed on a migration target) is untouched and not
even queued; on such a box the aliases already carry the new hostname, so pin
the old one with --force-old. After a
partial or aborted rename the on-disk aliases may already carry the new
hostname, which defeats old-hostname auto-detection (old == new, a silent
no-op) — resume by pinning the old hostname explicitly:
--force-old old.example.com.
The tool assumes BOA cron is stopped — the caller must stop it first.
Reading the residual report. The in-DB rename ends by reporting a count of
remaining old-hostname references in the variable and task-history
(hosting_task_arguments) tables. What lands there stays behind by design and
is not a failure: task-history rows keep their old-hostname arguments forever,
and values the rename deliberately leaves untouched — serialised variables
embedding PHP objects, and values that already fail to unserialise — are each
printed by name during the run for manual review. In a subdomain-augmenting
rename the count also excludes any value already carrying the new hostname, so
a zero residual does not prove zero old-hostname references — a mixed value
holding both forms is neither rewritten nor counted, and must be found by hand
if it matters.
There is no separate renamemaster tool: an earlier transitional
renamemaster was folded into the universal renameaegirhost.
Migration tooling calls it automatically — an xoct import and an xmass
cutover both invoke it (the cutover runs it for /var/aegir and every
/data/disk/oN), so a host move regenerates contexts without a manual
rename. See Migration & cloning.
Related
- Architecture overview — the layered model and file-system layout.
- Task queue engine — how each status transition is actually driven.
- Multi-Octopus model — per-instance isolation and the client/tenant mapping.
- Database — Percona host-side setup that
hosting_db_serverreferences. - Nginx internals — the Nginx generation that
hosting_nginxreferences. - Reference appendix — the consolidated variable and
command indexes, including the
provision-*backend commands this page cites (provision-clone,provision-save).