Skip to content

Powered by Grav

Entity & service model

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.

TXT
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.shhosting-dispatch), not a server-attached daemon.

The hosting modules behind this:

  • hosting_server — base server node type + admin UI; hosting.ip.inc handles 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_nginx binds the abstract web service to the concrete Nginx config generation in lib/functions/nginx.sh.inc; hosting_ssl runs 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 of static/platforms/ is the recommended layout, purely to keep static/'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>/, where NNN is a zero-padded numeric revision counter (the same _nextnum mechanism 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 (DE1DE3 for D11.x, DX0DX6 for D10.x, DL9/DL7/DL6 for D9/D7/D6),
  • plus distribution symbols (CK1/CK2/CK3, SOC, UC7, …),
  • or the keyword ALL for 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:

PHP
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:

TXT
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, a package node) 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:

  • Sitesprovision/platform/{install,backup,clone,migrate,…}.provision.inc. A per-site backup writes a flat tarball <backup_path>/<uri>-<timestamp>.tar.gz (where backup_path = <aegir_root>/backups), e.g. ~/backups/foo.example.com-20260623.101500.tar.gz — not a per-site <site>/<timestamp>.tar.gz subdirectory.
  • Platformsprovision/platform/verify.provision.inc plus the platform/drupal/* per-Drupal-major helpers.
  • Serversprovision/Provision/Context/server.php plus the service-type backends under provision/db/ and provision/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:

TXT
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:

  1. detects the old hostname from <aegir-root>/.drush/server_master.alias.drushrc.php and the new one from hostname -f;
  2. rewrites every Drush alias under <aegir-root>/.drush/;
  3. renames and rewrites the vhost files under <aegir-root>/config/server_master/nginx/vhost.d/;
  4. renames the hostname inside the Ægir DB with serialise-safe, targeted statements — identity rows keyed by hosting_context nid, REPLACE() sweeps over plain-varchar columns, and an unserialise/replace/re-serialise pass over the variable table; a plain dump is taken first as a backup only, never sed-patched or re-imported (a blanket dump+sed would 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;
  5. reloads Nginx;
  6. 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;
  7. waits for every renamed site to actually serve — up to _RENAME_SERVE_WAIT seconds 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 either Sites : all N renamed site(s) confirmed serving or NOT 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 because settings.php is 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.

  • 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_server references.
  • Nginx internals — the Nginx generation that hosting_nginx references.
  • Reference appendix — the consolidated variable and command indexes, including the provision-* backend commands this page cites (provision-clone, provision-save).

© 2026 BOA Documentation. All rights reserved.