How BOA wires settings.php (global.inc)
Two forks cooperate to configure a hosted Drupal site's runtime. Provision
(Drush 8 / PHP-5.6-floor) generates the per-site settings.php from a template
and drops a blank local.settings.php. Barracuda (bash) ships and deploys
the BOA-managed global*.inc fragment tree to /data/conf/. settings.php
includes that tree near its tail, so every BOA compiled-in $conf/$config
default flows into Drupal without a single line landing in the site owner's
editable file. This page is the include chain and its override semantics —
which file wins, in what order, and where you extend it in the fork.
The generated members
Per site, in sites/<uri>/:
| File | Who writes it | Mode | Editable by operator? |
|---|---|---|---|
settings.php |
Provision_Config_Drupal_Settings::process() (Settings.php), rendered from provision_drupal_settings_<major>.tpl.php |
0440 (Settings.php:11, applied by the Provision_Config base write()) |
No — regenerated on every Verify |
local.settings.php |
Provision, created blank ("<?php # local settings.php \n") once if absent (Settings.php:96-104) |
0440 (Settings.php:11,111; reverted to 0440 after the backup-writable window, provision_drupal.drush.inc:141) |
Yes — never overwritten once it exists |
Host-wide, in /data/conf/ and /data/conf/global/ (one copy per box, shared
by every site), deployed by Barracuda — see Deployment.
The split is the whole design: settings.php is machine-owned and
disposable; the site's only durable hand-editable hook is local.settings.php,
which is included last and therefore overrides everything above it.
The include cascade in settings.php
Every version template — provision_drupal_settings_{6,7,8,9,10,11}.tpl.php —
ends with the same five-step tail (D7 shown, settings_7.tpl.php:199-224;
the others differ only in the global-<major>.inc basename and the D8+
services.yml line above it):
<?php print $extra_config; ?> // [1] provision_drupal_config hook output
# Additional host wide configuration settings.
if (is_readable('/data/conf/global/global-7.inc')) { // [2a] versioned entry point
include_once('/data/conf/global/global-7.inc');
}
elseif (is_readable('/data/conf/global.inc')) { // [2b] legacy monolith fallback
include_once('/data/conf/global.inc');
}
# Additional platform wide configuration settings.
if (is_readable('<platform_root>/sites/all/platform.settings.php')) { // [3]
include_once(...);
}
if (is_readable('<platform_root>/sites/all/settings.php')) { // [4]
include_once(...);
}
# Additional site configuration settings.
if (is_readable('<site_path>/local.settings.php')) { // [5] site owner's file — wins
include_once(...);
}
Later includes see the $conf/$config/$settings/$databases state left by
earlier ones and override on assignment, so precedence is strictly bottom-wins:
compiled-in template body (DB creds, paths, trusted_host_patterns)
→ [1] $extra_config (drush_command_invoke_all('provision_drupal_config', …))
→ [2] BOA global*.inc tree (host-wide BOA defaults + INI layer)
→ [3] platform.settings.php (platform-wide, e.g. a distribution's own)
→ [4] platform sites/all/settings.php
→ [5] local.settings.php (per-site operator overrides — last word)
$extra_config at step [1] is the module extension seam: process() fills it
with drush_command_invoke_all('provision_drupal_config', d()->uri, $this->data)
(Settings.php:85-86) — a hosting_* module returns config lines from its
hook_provision_drupal_config() and they render inline. That is how an
extension injects settings without patching a template; the hook contract is on
the Aegir backend APIs topic.
[2a] vs [2b] is version-split vs legacy, not preferred-vs-fallback of the same file.
global-<major>.incis the current entry point; the box only falls through to the monolithic/data/conf/global.incwhen the versioned file is unreadable (an old deployment, or a box mid-upgrade before_global_inc_conf_updateran). Both ultimately configure the same$conf, but only the versioned path pulls in the split fragment tree below.
The global.inc fragment tree
Source of truth is boa-private/aegir/conf/global/ (20 *.inc files).
global-<major>.inc is not a big file — it is a ~1.9 KB thin dispatcher
(global-7.inc and global-11.inc differ only in the $drupal_core string,
verified by diff). It sets the core version, then include_onces the real
fragments in a fixed, load-bearing order (global-7.inc):
global-<major>.inc (sets $drupal_core = 'N')
→ global-ini.inc # $boa_ini[] defaults + INI-var init (all FALSE/seed values)
→ global-mode.inc # $is_bot / $is_tmp / $is_dev host+UA detection
→ global-main.inc # early request gating (bot 404 on tmp sites, cron protection); merges $all_ini = $boa_ini + platform/site INI
→ global-settings.inc # forced $conf/$config/$settings keyed on $drupal_core
→ settings.global.inc # /data/conf/settings.global.inc — operator early override (optional)
→ global-front-end.inc # front-end-request (!$is_backend) tuning; hostmaster-specific carve-outs inside
→ global-if-valkey.inc ELSEIF global-if-redis.inc # active-backend probe/decision
→ override.global.inc # /data/conf/override.global.inc — operator override (optional)
→ global-valkey.inc ELSEIF global-redis.inc # cache backend $conf wiring
→ global-newrelic.inc # New Relic integration (gated on $all_ini['enable_newrelic_integration'])
→ global-extra.inc # shared utility tail
Order encodes precedence within the tree. global-ini.inc runs first because
it seeds every BOA INI default in $boa_ini[] (redis_use_modern,
session_cookie_ttl, the redis_* probe-tuning block, …). global-main.inc
then assembles $all_ini = $boa_ini and array_merges the platform/site INI
($usr_plr_ini, $usr_loc_ini) on top (global-main.inc:258,274,277) — so
every later fragment branches on $all_ini, the operator-overridable merged
view, not on the raw seed. The two operator drop-ins bracket the
cache-backend decision:
settings.global.inc lands before the if-valkey/if-redis probe (so an
operator can flip $high_traffic or force a backend choice), override.global.inc
lands after the probe but before the valkey/redis wiring (so an operator
can veto the auto-selected backend by setting $custom_cache = TRUE). Neither
operator file is in the auto-deploy set — see below.
The valkey/redis pairs use if … elseif, never both: Valkey (a Redis drop-in
fork) shadows Redis when present. global-valkey.inc is the same body as
global-redis.inc with the backend name swapped, and valkey.sh.inc seds the
deployed copy to point at the Valkey socket — do not DRY these two together; the
elseif gate depends on them being distinct files.
The legacy monolith
/data/conf/global.inc (source aegir/conf/global/global.inc, ~72 KB) is the
pre-split single-file form reached only by the elseif at step [2b]. It carries
its own copy of the same logic and require_onces the two operator drop-ins
directly — /data/conf/settings.global.inc (global.inc:433) and
/data/conf/override.global.inc (global.inc:1397). Treat it as frozen: fix
behaviour in the split fragments; the monolith exists so a box that has not yet
received the split tree still boots with correct config.
Deployment: when the tree lands
Barracuda copies the source tree to the box in _global_inc_conf_update
(system.sh.inc:741), called from _fix_on_upgrade (system.sh.inc:793) on
every barracuda upgrade. _locCnf resolves to <build>/aegir/conf
(BARRACUDA.sh.txt:186), so ${_locCnf}/global/* is exactly the repo's
aegir/conf/global/. Targets:
/data/conf/global.inc←global.inc(the monolith)/data/conf/global/global-{11,10,9,8,7,6}.inc(loop,system.sh.inc:747-753)/data/conf/global/global-{main,ini,mode,settings,front-end}.inc/data/conf/global/global-{if-valkey,valkey,if-redis,redis,newrelic,extra}.inc
Two backup / overwrite behaviours matter when you change a fragment:
- Only
global.incand theglobal-<major>.incfiles get a-pre-…snapshot before being replaced (system.sh.inc:750,754). Every other fragment (global-main.inc,global-redis.inc,global-ini.inc, …) is overwritten wholesale withcp -af— no snapshot. A hand-edit to a deployed non-versioned fragment is lost silently on the next upgrade with no recoverable copy on the box. - The
_SPEED_VALID_MAXsubstitution rewrites the literal3600inglobal.incandglobal-front-end.incpost-copy (system.sh.inc:756-767) — a deploy-time edit, not something to bake into the source with the tuned value.
Two more deploy touch-points to be aware of:
- Barracuda master render (
master.sh.inc:885, and the equivalent innginx.sh.inc:671) also copiesglobal.incto/data/conf/global.incand symlinks it into the Master Ægir include dir when that dir holds a real file rather than a symlink. - On an Octopus instance,
_satellite_child_b_symlink_global_inc(satellite.sh.inc:6363) symlinks/data/conf/global.incinto the instance'sconfig/includes/global.inc(theln -sfn,satellite.sh.inc:6370).
Because BOA is master/satellite-split, the master (master.sh.inc) and
satellite (satellite.sh.inc) copies of this deployment logic are intentionally
separate — mirror any fix into both, do not merge them.
The operator drop-ins
settings.global.inc and override.global.inc ship in aegir/conf/global/ as
self-documenting examples — each opens with a comment like
// This file should be created as /data/conf/override.global.inc. They are
not in _global_inc_conf_update's copy list and are never auto-deployed to
/data/conf/; the fragment chain simply is_readable()-tests for an
operator-authored copy at /data/conf/settings.global.inc /
/data/conf/override.global.inc and includes it if present. (BOA.sh.txt:1218-1220
even retires a stray override.global.inc on the D6 end-of-life path by moving
it to .prev6.override.global.inc.off.) So the two files at repo root are
templates for the operator, not runtime config — do not treat their default
bodies ($custom_cache = FALSE, the domain.com securepages example) as active
on a box.
DB credentials: BOA turns cloaking OFF (literal creds)
BOA is not stock Aegir here. Stock Aegir "cloaks" DB creds — the
site settings.php reads them from the $_SERVER superglobal (injected per
request by the vhost's fastcgi_param db_*) and holds no literal password.
BOA does the opposite: it writes the creds literally into settings.php.
That is what makes BOA's site-local Drush work without the Nginx request
environment.
Trace the switch: process() sets $this->cloaked from
drush_get_option('provision_db_cloaking', …->cloaked_db_creds())
(Settings.php:72). On BOA the nginx service's cloaked_db_creds() returns
FALSE (http/Provision/Service/http/nginx.php:17-19) and init_server()
sets the provision_db_cloaking server property FALSE
(nginx.php:33) — so $this->cloaked resolves FALSE and the template takes its
else branch (settings_7.tpl.php:95-117), inlining the creds:
$databases['default']['default'] = array(
'driver' => "<db_type>", // $this->creds['db_type'], printed literally
'database' => "<db_name>",
'username' => "<db_user>",
'password' => "<db_passwd>",
'host' => "<db_host>",
'port' => "<db_port>",
...
);
The if ($this->cloaked) branch above it (settings_7.tpl.php:31-93) — the one
that reads creds from $_SERVER, then unsets every db_*/REDIRECT_db_*
key (plus apache_setenv(..., null)) so they cannot leak through phpinfo() —
is the stock-Aegir cloak path that BOA does not emit. Do not describe it as
BOA's default; it is compiled out when $this->cloaked is FALSE.
The vhost still emits the
fastcgi_param db_*lines unconditionally (vhost generator,vhost.tpl.php:136-155) — that is an Aegir/Apache-era carry-over — but with cloaking off, BOA'ssettings.phpnever reads those$_SERVERvalues back. The credentials in force are the literal ones insettings.php, which is0440and web-group-owned, not the$_SERVERkeys.
db_type is normalised mysqli → mysql for D8+ (Settings.php:20,30,40,50);
utf8mb4 charset/collation is emitted only when
$utf8mb4_is_configurable && $utf8mb4_is_supported — configurable is a Drupal
version gate (>= 7.50 for D7, always TRUE for D8+, Settings.php:21,61),
supported comes from the DB server probe ($this->db_server->utf8mb4_is_supported).
Extending the chain in the fork
Choose by scope; higher in this list = narrower, safer:
- Per-site, operator, no fork change —
sites/<uri>/local.settings.php. Included last, overrides everything. This is the answer to "how do I change one site's$conf" and requires no BOA change at all. - Host-wide, operator, no fork change — create
/data/conf/override.global.inc(orsettings.global.incfor a pre-probe early override). Survives upgrades because BOA never overwrites it. - A new host-wide BOA default — add it to the appropriate fragment
(
global-settings.incfor a forced$conf/$config,global-main.incfor request-time gating) and confirm_global_inc_conf_update's copy list already ships that fragment. If you add a new fragment file, you must add acp -afline for it in_global_inc_conf_update(system.sh.inc:760-773) and aninclude_oncein everyglobal-<major>.incentry point — a fragment that is deployed but not included, or included but not deployed, is inert. - A new INI-exposed knob — seed its default in
$boa_ini[]inglobal-ini.inc, then read it as$all_ini['your_key']in the consuming fragment (never$boa_ini— the operator override rides the merge). No per-key reader wiring is needed:global-main.incparse_ini_filesboa_platform_control.ini/boa_site_control.iniandarray_merges any key they contain over$all_ini(global-main.inc:259-277). Surface the knob to operators by adding a commented default line to thedefault.boa_*_control.initemplates. This keeps the operator-facing surface in INI, not in a hand-edited.inc. - A new cache backend — clone the
global-if-<backend>.inc/global-<backend>.incpair from the Valkey/Redis pattern and add itselseifarm to both decision points inglobal-<major>.inc; keep the pair as two distinct files (the elseif depends on it).
PHP-5.6 floor applies to every fragment and every settings template: these run
in the hosted site's PHP, which spans legacy 5.6 boxes. No ??, no arrow
functions, no typed properties, no match — the same constraints as the rest of
the Provision/Barracuda backend.
Never hand-edit a generated member on a live box.
settings.phpis re-rendered on every Verify task and the non-versioned fragments arecp -af-overwritten (no snapshot) on everybarracuda upgrade. An edit to either is lost with no recoverable copy. Route per-site changes tolocal.settings.php, host-wide changes to the operator drop-ins, and BOA defaults to the fork.
Related
- Web & search stack internals — how the vhost this
settings.phppairs with is generated, and where thefastcgi_param db_*creds are injected (BOA writes them intosettings.phpliterally; the vhost params go unread). - Aegir backend APIs — the
provision_drupal_confighook that feeds$extra_configat step [1] of the cascade. - Install & staged-setup internals — how the
Provision codebase and the
/data/conf/global/tree reach a box in the first place. - Variables — the
_VARand$boa_ini[]knobs the fragment tree reads. </content>