Hostmaster frontend internals

Hostmaster is the Drupal 7 install profile that is Aegir's web UI — the omega8cc fork ships it as its own repo on the public branch model (5.x-dev / 5.x-pro / 5.x-lts plus their -base/-edge variants). A BOA box runs it N+1 times: one Master frontend under /var/aegir (the aegir user's controlling instance) and one Satellite frontend per Octopus instance under /data/disk/<user> — each a complete, independent Drupal 7 site with its own database, its own codebase generation and its own Drush alias. This page is the profile's anatomy; the staged scripts that run the build are on the staged setup engine page.

The repo root is small and every file is load-bearing:

File Lines Role
hostmaster.info 57 Profile metadata + the module manifest Drupal enables at install
hostmaster.install 327 hook_install() — builds the initial Aegir node graph and site config
hostmaster.profile 16 One hook: un-hides the profile from update status
hostmaster.make 63 BOA build entry — pins Aegir projects to /opt/tmp/make_local/* copies
drupal-org.make 158 Shared include — placement rules + the full contrib/library list
build-hostmaster.make 11 Standalone build entry (core tarball from files.aegir.cc)
drupal-org-core.make 12 Historic drupal.org-packager core pin; inert in BOA builds

hostmaster.info — the module manifest

Standard D7 install-profile metadata (core = 7.x, distribution_name = "Hostmaster (Aegir)") with one structural switch: exclusive = TRUE (hostmaster.info:6), which makes the D7 installer auto-select the profile — no profile-picker step on a Hostmaster platform.

The dependencies[] list is the canonical "what is enabled at install" manifest, 40 active entries in four commented tiers:

  • Drupal core (:10-20): block book color comment filter help menu node overlay system user.
  • Aegir core (:23-37): hosting plus 14 hosting_* submodules — alias client clone cron db_server le migrate package platform server site task tasks_extra web_server. These resolve from the monolithic hosting repo (which carries the submodule directories) and from the separately-bundled hosting_le and hosting_tasks_extra projects.
  • Aegir golden contrib (:40-41): hosting_platform_composer_git, hosting_platform_git.
  • Drupal contrib (:44-55): actions_permissions admin_menu ctools environment_indicator fe_block features overlay_paths revision_deletion strongarm userprotect views views_bulk_operations. betterlogin and r4032login are present but commented out (:56-57) — still built into the platform by the make chain, not enabled at install.

Adding a bundled module is therefore always a two-file change minimum: the .info line only tells Drupal to enable it; the make chain (below) is what puts the code on disk. A module that should be enabled on existing instances too also needs an enable call in the BOA upgrade path — see what BOA layers on top.

hostmaster.install — the initial node graph

hostmaster_install() (hostmaster.install:8) runs once, inside the D7 installer that provision-install triggers for the @hostmaster site. Three phases:

  1. Nginx wiring (:13-17): if the platform's server context has http_service_type === 'nginx' — always true on BOA — it enables hosting_nginx, hosting_ssl, hosting_nginx_ssl. Note the context read: d()->platform->server works here because the installer runs under Provision with the backend contexts already saved.
  2. hostmaster_bootstrap() (:27) — creates the entire initial Aegir content graph as nodes, in order: default client (from --client_name, default admin; hosting_default_client / hosting_admin_client variables), the server_master server node with its http service, the DB service on the same node — or a separate server_<host> node when the DB host differs from the web host (:68-92) — the Drupal platform package node, the hostmaster platform node with publish_path = d()->root (:112-125, hosting_own_platform), the hostmaster profile package node with package-instance rows, and finally the @hostmaster site node itself (:164-182, nid saved as aegir_hostmaster_site_nid). It then enables the default hosting features (hosting_features_enable, :187-198), sets site_frontpage = hosting/sites (:200), disables open registration (user_register = 0, :203) and records install_url (:206). The frontend registering itself as platform + site is what lets Aegir manage its own future migrations.
  3. hostmaster_task_finalize() (:209) — theme switch to eldir (below), block placement, empties menu_options_* for the four Aegir node types so they never offer menu placement (:223-226), and node_access_rebuild().

hostmaster.install runs only at INIT. Anything you change here reaches existing instances only through hostmaster-migrate update hooks or through BOA's upgrade-path drush calls — never assume a .install edit is fleet-wide.

hostmaster.profile — one hook

The file exists mostly so Drupal recognises the profile; its single substantive hook is hostmaster_update_projects_alter() (hostmaster.profile:8-16), which clones the module-data entry, flips info['hidden'] = FALSE and re-feeds it to _update_process_info_list() — D7 hides install profiles from update-status by default, and this un-hides Hostmaster so a new release shows up as an available update in the UI.

The make-file chain

drush hostmaster-install does not fetch anything from the network on a BOA box. The chain, from the top:

  1. Provision's install.hostmaster.inc defaults the platform root to <aegir_root>/hostmaster-<version> (install.hostmaster.inc:71) and the makefile to provision/aegir.make (:89).
  2. aegir.make is two copy projects: Drupal core from /opt/tmp/make_local/drupal and the hostmaster profile from /opt/tmp/make_local/hostmaster.
  3. Drush make recurses into the copied profile's own hostmaster.make, which includes[hostmaster] = "drupal-org.make" (hostmaster.make:7) and pins the ten Aegir projects — eldir, hosting, then the golden tier aegir_objects hosting_civicrm hosting_deploy hosting_git hosting_le hosting_remote_import hosting_site_backup_manager hosting_tasks_extra — to copy downloads from /opt/tmp/make_local/<project> (hostmaster.make:11-51). Commented git-mode blocks for hosting/eldir dev work sit at :53-63.
  4. drupal-org.make sets the placement rules — defaults[projects][subdir] = "contrib", with the Aegir tier overridden to subdir "aegir" and eldir typed as a theme (drupal-org.make:7-25) — and pins the whole contrib set as copy projects from /opt/tmp/make_local/*: the D7 contrib modules, the TFA stack (libraries + tfa + tfa_basic, :71-82), three raw JS libraries (qrcodejs, timeago/timeagojs, vuejs, :85-103), the BOA contrib tier (features features_extra idna_convert revision_deletion strongarm userprotect environment_indicator tag1_d7es, :107-137) and BOA's own hosting_custom_settings (:142-143). The tail (:145-158) is a changelog of projects removed or folded into Aegir core — check it before re-adding anything.
  5. build-hostmaster.make is the non-BOA entry point: it pulls a pinned Drupal core tarball from files.aegir.cc/core/ and includes the same drupal-org.make (build-hostmaster.make:7-11).

The resulting platform tree keeps almost everything inside the profile: profiles/hostmaster/modules/aegir/ (Aegir tier), profiles/hostmaster/modules/contrib/ (everything else), profiles/hostmaster/themes/aegir/eldir/, plus top-level libraries/ for the JS payloads. BOA's build code re-asserts those two module dirs exist at satellite.sh.inc:6127.

Who fills /opt/tmp/make_local

_master_download_for_local_build() (master.sh.inc:547) and _satellite_download_for_local_build() (satellite.sh.inc:3125) are deliberate master/satellite copies of the same logic. Presence test first — the cache is complete when hostmaster/hostmaster.make, hosting/hosting.module and drupal/modules/system/system.module all exist (master.sh.inc:553-555) — then either:

  • _DL_MODE=BATCH (default): one make_local.tar.gz fetched via _get_dev_ext (master.sh.inc:561-568), or
  • GIT mode: Drupal core tarball via _get_core_ext, then ${_gCb} = git clone --branch (OCTOPUS.sh.txt:183) of hostmaster.git, hosting.git, eldir.git and the golden repos at branch 5.x-${_tRee} from ${_gitHub} (master.sh.inc:576-588), plus every pinned contrib tarball via _get_dev_stc (:591-617) — the version pins in this fetch list must match the .make comments.

Everything is normalized to 0755/0644 afterwards. Because the make chain and the fetch functions both name every project, adding a bundled project touches four places: hostmaster.make or drupal-org.make, both _download_for_local_build copies, and the mirror-side make_local.tar.gz build. The make files ride the whole-release transport (boa.tar.gz / git tree), not a per-file serial — see the serial & fetch pipeline.

The hostmaster-install sequence

drush hostmaster-install <domain> (Provision command, install.hostmaster.inc) is the single entry point both instance types use:

provision-save @server_master            (+ @server_<dbhost> if DB is remote)
provision-verify @server_master                    (:242-243)
provision-save @platform_hostmaster  root + makefile      (:260-266)
provision-verify @platform_hostmaster  → drush make builds the tree  (:275)
provision-save @hostmaster  site context, drush_aliases: 'hm'        (:288-302)
provision-install @hostmaster  → D7 installer → hostmaster_install()  (:305)
provision-verify @hostmaster                        (:306)
hosting-setup   → queues/cron wiring, prints one-time login link      (:318)

The 'drush_aliases' => 'hm' line (:296) is why the Master's canonical alias file is /var/aegir/.drush/hm.alias.drushrc.php while the full hostmaster.alias.drushrc.php name is what BOA parses on Satellites — BOA extracts root', site_path' and uri' from these files with grep|cut|awk|sed chains all over the codebase (master.sh.inc:795, satellite.sh.inc:1380, AegirSetupC.sh.txt:265-269). Treat the alias files as the runtime source of truth for "where is this frontend"; never hardcode a generation path.

Per-host and per-Octopus instances

Master Satellite (one per Octopus instance)
Unix user aegir ${_USER} (o1, o2, …)
$HOME / aegir root /var/aegir /data/disk/${_USER}
Platform root (INIT) /var/aegir/hostmaster-${_AEGIR_VERSION} (AegirSetupM.sh.txt:145) ${_ROOT}/aegir/distro/001
Platform root (upgrades) /var/aegir/host_master/NNN (AegirUpgrade.sh.txt:478) ${_ROOT}/aegir/distro/NNN (AegirSetupA.sh.txt:127)
Frontend domain _THIS_FRONT = _MY_FRONT or the host FQDN (dns.sh.inc:728-732) _DOMAIN = ${_USER}.<hostname> default (OCTOPUS.sh.txt:167)
Alias parsed by BOA /var/aegir/.drush/hm.alias.drushrc.php ${_ROOT}/.drush/hostmaster.alias.drushrc.php
Installer stage AegirSetupM via _aegir_master_install_upgrade() (master.sh.inc:981) AegirSetupB via _satellite_child_b_aegir_build() (satellite.sh.inc:5687)
DB user ${_AEGIR_DB_USER} (aegir) ${_USER}, or aegir_root for remote DB hosts (satellite.sh.inc:5711-5714)

_AEGIR_VERSION defaults to AUTO in the settings files (barracuda.sh.cnf:1311, octopus.sh.cnf:801) and is rewritten to the tree name by the wrappers (sed -i "s/^_AEGIR_VERSION.*/_AEGIR_VERSION=${_tRee}/g", barracuda:874, boa:977) — so a Master INIT root looks like hostmaster-lts, never an upstream-style hostmaster-7.x-3.x; that naming does not exist anywhere in BOA.

Generation numbering. Satellite frontends live in numbered codebase generations under ${_ROOT}/aegir/distro/. _HM_DISTRO=001 is the INIT default (OCTOPUS.sh.txt:307); on upgrades _satellite_if_init_or_upgrade globs the existing dirs and sets _HM_DISTRO to zero-padded last+1 (satellite.sh.inc:4853-4867), and _satellite_make() exports it into the per-instance settings snapshot (:4290) that AegirSetup{A,B,C} re-derive _HM_ROOT="${_ROOT}/aegir/distro/${_HM_DISTRO}" from (AegirSetupA.sh.txt:127, AegirSetupB.sh.txt:127-129, AegirSetupC.sh.txt:130-132). Stage B's UPGRADE path additionally detects "zombie" generations — a previous root without sites/${_DOMAIN}/settings.php — moves them to backups/system/empty-host-master-* and re-numbers (satellite.sh.inc: 5817-5860). The Master analog is the host_master/NNN scheme managed by the hostmaster upgrade orchestrator, seeded with a 000 placeholder (AegirUpgrade.sh.txt:392-396).

After a Satellite install the active generation is symlinked twice: ${_ROOT}/u/${_DOMAIN} → ${_HM_ROOT} and /data/u/${_DOMAIN} → ${_HM_ROOT} (satellite.sh.inc:5768-5773) — stable paths that survive re-numbering.

Each Satellite also gets its own queue driver: stage B writes ${_ROOT}/aegir.sh (a drush @hostmaster hosting-dispatch wrapper, satellite.sh.inc:5466-5474) and stage A installs the per-instance runner at /var/xdrago/run-${_USER} (satellite.sh.inc:4371-4374). The frontends never share state — same code lineage, disjoint databases, users, queues and vhosts.

The eldir theme

eldir is Aegir's admin theme, maintained as its own omega8cc fork and pulled into the build like any other project: staged at /opt/tmp/make_local/eldir (GIT mode clones eldir.git at 5.x-${_tRee}master.sh.inc:578, satellite.sh.inc:3157), typed as a theme and placed under the profile by drupal-org.make:12-13profiles/hostmaster/themes/aegir/eldir/.

hostmaster_task_finalize() makes it the site: theme_enable(array('eldir')), theme_default = eldir, Bartik disabled (hostmaster.install:213-217), then hostmaster_place_blocks('eldir') (:220, defined :235-327) inserts seven Views blocks — the task list into sidebar_first, and the site/platform/package listing blocks into content_bottom — via a direct db_insert('block') (:320-325), not the block API. Two consequences for maintainers: the region names hard-couple .install to eldir's region set, and the placement happens exactly once at INIT — a theme swap or region rename on a live instance means re-placing blocks yourself. The stock D7 themes are stripped from the platform after install (bluemarine chameleon pushbutton, satellite.sh.inc:3349-3351), so eldir plus the D7 admin theme is the entire theme surface.

What BOA layers on top after hostmaster-install

A stock Aegir frontend and a BOA frontend diverge immediately after the profile install, via two stage-B functions (Satellite side; the Master gets the equivalent treatment in _aegir_master_install_upgrade()):

_satellite_child_b_aegir_build() (satellite.sh.inc:5687) resolves the DB topology — localhost / 127.0.0.1 / ProxySQL / the host's own FQDN keep _USE_DB_USER=${_USER}, a genuinely remote DB host switches it to aegir_root (:5697-5715) — writes the instance credential snippet ${_ROOT}/.${_USER}.pass.php (0600, :5720-5728), runs hostmaster-install with the full BOA flag set — --aegir_root=${_ROOT} --root=${_HM_ROOT} --script_user=${_USER} --web_group=${_WEBG} --http_service_type=nginx (:5734-5762) — then strips the stock profiles (rm -rf ${_HM_ROOT}/profiles/{default,standard,minimal,testing}, :5764), creates the (kept-empty) sites/all/{modules,themes,libraries} scaffolding, sets up sites/${_DOMAIN}/files with 02775/${_WEBG}, plants the u/ symlinks and drains the first task backlog with forced hosting-dispatch/hosting-tasks rounds (:5778-5791).

_satellite_child_b_aegir_ui_enhance() (satellite.sh.inc:6120) is the BOA-policy pass, all through ${_DRUSHCMD} @hostmaster:

  • enables ~20 modules beyond the .info manifest (:6139-6160): aegir_objects, fix_ownership, fix_permissions, hosting_http_basic_auth (those three ship inside the hosting_tasks_extra project tree), hosting_civicrm(_cron), hosting_deploy, hosting_platform_composer(_git), hosting_platform_git, hosting_site_backup_manager, environment_indicator, overlay(_paths), revision_deletion, timeago, userprotect, libraries, …; later hosting_le, hosting_le_vhost and hosting_custom_settings (+ features-revert) (:6283-6300);
  • sets the operating policy via _vSet = variable-set --always-set (AegirSetupB.sh.txt:51): queue frequencies and hosting_queue_tasks_items 1, cron interval, platform/upload base paths under ${_ROOT}/static, backup-export path, hosting_alias_automatic_* (:6161-6202);
  • rewrites UI state directly in SQL where no API exists: deletes the hosting/platforms + hosting/sites menu links, hides the user menu, truncates the filter tables, resets the task-log display map via variable_set in drush ev (:6203-6224, :6276-6296);
  • brands the instance (site_name "Octopus System powered by Barracuda") and builds the welcome content and client records with throwaway helper scripts make_home.php / make_client.php copied from /opt/tmp/boa/aegir/helpers/*.php.txt, executed with drush php-script, then deleted (:6227-6253);
  • on UPGRADE, re-points platform-node status so only the current aegir/distro/ generation stays active (status=-2 for old roots, :6255-6263);
  • one-time SSL flip: sets the http service type to nginx_ssl and force-verifies @server_master, witnessed by ${_ROOT}/log/hosting_le_enable.txt (:6330-6337).

The same platform-registration trick is used by stage C for the other platforms: make_platform.php from the same helpers dir creates platform nodes in this frontend (AegirSetupC.sh.txt:289-351).

How this differs from a plain Drupal 7 site

  • Profile-centric layout. Effectively all modules and the theme live under profiles/hostmaster/; sites/all/* exists but is intentionally empty. Extending the frontend means changing the profile/make chain, not dropping code into sites/all.
  • Built offline, from pinned copies. No drupal.org fetches at build time; every component copy-resolves from /opt/tmp/make_local, itself a mirror artifact or a set of 5.x-<tree> git clones.
  • Self-registered. The site's own platform and site nodes exist in its own database (hosting_own_platform, aegir_hostmaster_site_nid) — the frontend is a managed Aegir citizen, which is what makes hostmaster-migrate upgrades possible.
  • Generation-numbered roots, alias-resolved. The docroot moves on every upgrade (aegir/distro/NNN, host_master/NNN); nothing may assume a stable path except the drush alias files and the u/ symlinks.
  • Trimmed and policy-locked. Stock profiles/default, the D7 themes, the scripts dir and root *.txt removed (satellite.sh.inc:3348-3354, :5764); registration closed; admin protected via a raw userprotect seed (:6308-6327); permissions normalized to 0755/0644 with 0711 traverse-only parents.
  • Unprivileged, per-instance runtime. Each frontend runs and executes Drush as its own system user, and all of its PHP must stay parse- and runtime-safe on the PHP 5.6 floor that applies to the whole Aegir layer (see the topic chapter).

Maintainer contract

  • New bundled module = .info dependencies[] line + make-chain entry (hostmaster.make for Aegir-tier, drupal-org.make for contrib) + both _download_for_local_build() copies + the mirror make_local artifact. For existing instances, add the en call to _satellite_child_b_aegir_ui_enhance() (and the Master path) — the .info manifest only fires at INIT.
  • hostmaster.install edits touch new installs only. Fleet-wide behaviour changes belong in the upgrade path or in the modules themselves, not in the profile installer.
  • Never hardcode a frontend path. Parse the alias file (hm.alias.drushrc.php on the Master, hostmaster.alias.drushrc.php on Satellites) or use the u/ symlinks; generation numbers advance on every upgrade and zombie-recovery can skip numbers.
  • Block/theme coupling. hostmaster_place_blocks() writes raw block rows against eldir's region names once; changing eldir regions or the default theme requires a matching block strategy.
  • Master/satellite duplication is deliberate. The build/staging logic exists twice (master.sh.inc / satellite.sh.inc); fix both copies, don't DRY them — see code style & conventions.
  • Transport awareness. The hostmaster repo, make files and helper scripts all ride the whole-release transport; there is no per-file serial for any of this — plan release timing per the serial & fetch pipeline.

Related