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 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. |
drushrc.php |
aegir:aegir |
0440 (-r--r-----) |
Set by Provision/Config/Drushrc.php ($mode = 0440). |
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 serialize-safe, targeted
statements — identity rows keyed by
hosting_contextnid,REPLACE()sweeps over plain-varchar columns, and an unserialize/replace/re-serialize pass over thevariabletable; a plain dump is taken first as a backup only, neversed-patched or re-imported (a blanket dump+sedwould corrupt PHP-serialized byte lengths when the FQDN length changes); - 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.
On Octopus roots the tool also rewrites _DOMAIN (and any other old-FQDN
values) in the account's /root/.oN.octopus.cnf, which drives future
octopus runs. The master root has no octopus cnf, so that 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 completed rename changes nothing (including a
subdomain-augmenting rename, where the new FQDN contains the old one). 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 — serialized variables
embedding PHP objects, and values that already fail to unserialize — 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).