Hosting API
The frontend extension contract. Where the
Provision API covers the backend Drush layer, this leaf
covers the Hostmaster half — the D7 hosting_* module suite that models
servers/platforms/sites as nodes, queues work as task nodes, and exposes the
drush hosting-* command surface. All of it is Drupal 7 running under the
webserver user, and — since these functions execute inside the Drush 8 fork
during dispatch — every line must stay parse- and runtime-safe on PHP 5.6.
Authoritative source is the *.api.php docblock set: hosting.api.php (root)
plus five per-module files — task/hosting_task.api.php,
alias/hosting_alias.api.php, quota/hosting_quota.api.php,
server/hosting_server.api.php, site/hosting_site.api.php. Trust these plus
the live *.module/*.drush.inc implementations, not older prose: several
hook names in the legacy docs are garbled (see
Hook-name corrections at the end).
Where a task actually runs
One hosting_task node → one backend provision-* invocation. The chain,
verified at source:
1. Frontend saves a hosting_task node (status queued).
2. cron → drush @hostmaster hosting-dispatch (dispatch.hosting.inc:16)
forks each due queue: drush @hostmaster hosting-<queue>
3. hosting-tasks worker (hosting_tasks_queue) forks per queued task node:
drush_invoke_process('@self','hosting-task',<nid>) (task/hosting_task.module:785)
4. drush_hosting_task(): (task.hosting.inc:189)
if task_info['provision_save']: provision-save re-exports the node as a
backend alias, after hosting_<type>_context_options() mutates it
(task.hosting.inc:197-208)
task_command = command | 'provision-' . task_type (task.hosting.inc:222-223)
provision_backend_invoke($alias, task_command, …) (task.hosting.inc:226)
5. Backend result rides the Drush backend channel back; frontend fires
post_hosting_<type>_task() / hosting_<type>_task_rollback()
(task.hosting.inc:259-264)
Every hook below plugs into one box of that chain.
Declaring a task type — hook_hosting_tasks()
This is the hook to add an Aegir UI task. It is not parameterised by object:
the single hook is hook_hosting_tasks()
(task/hosting_task.api.php:42), aggregated by
hosting_available_tasks() through
module_invoke_all('hosting_tasks') + drupal_alter('hosting_tasks', …)
(task/hosting_task.module:885-902). The object token is an array key, not
part of the function name:
/**
* Implements hook_hosting_tasks().
*/
function mymodule_hosting_tasks() {
$tasks = array();
$tasks['site']['mytask'] = array(
'title' => t('My custom task'),
'description' => t('Run the custom thing on a site.'),
'weight' => 5,
// Omit 'command' to default to provision-mytask; set it to override.
'command' => 'provision-mytask',
'dialog' => TRUE, // Show a confirm form before running.
// 'hidden' => TRUE, // Runnable but not shown in the UI.
// 'access callback' => 'user_access', // Default: hosting_task_menu_access.
// 'provision_save' => TRUE, // provision-save before running (see below).
);
return $tasks;
}
The outer key (site) is the entity type the task operates on — site,
platform or server. The inner key (mytask) is the task type. Defined keys
(task/hosting_task.api.php:21-37):
| Key | Meaning |
|---|---|
title |
Required. Human label in task lists. |
command |
Drush command to run. Default: provision-<tasktype> (task.hosting.inc:222). |
description |
Long description on the confirm form. |
weight |
Ordering in the task dropdown. |
dialog |
TRUE → show a confirmation dialog before executing. |
hidden |
TRUE → runnable by the frontend but not shown as a UI button. |
access callback |
Access gate; defaults to hosting_task_menu_access. |
provision_save |
TRUE → run provision-save (re-export the node as a backend context) before the task. Used by install/verify/import. Pair with hook_hosting_TASK_OBJECT_context_options(). |
Adding the task confirm-form fields is a separate hook — hosting_task_<TASKTYPE>_form($node) (and its _form_validate), built as hosting_task_<tasktype>_form at task/hosting_task.module:619. It is keyed by task type, not object (task/hosting_task.api.php:82,94).
Passing state to the backend — provision_save + context options
A task with provision_save => TRUE runs provision-save first, which writes
the node's context_options into the backend alias file that the following
provision-* command reads. To inject options, implement
hook_hosting_TASK_OBJECT_context_options(&$task)
(hosting.api.php:218) — here the object token is part of the name, but
only because the dispatcher resolves it by entity type at runtime:
$hook = 'hosting_' . $task->ref->type . '_context_options'; // task.hosting.inc:202
so for a site task you implement mymodule_hosting_site_context_options(),
for a platform task …_platform_context_options(), etc. It is passed by
reference; only mutate $task->context_options:
/**
* Implements hook_hosting_site_context_options().
*/
function mymodule_hosting_site_context_options(&$task) {
// $task->ref is the site node. Value survives as a backend context property.
$task->context_options['mymodule_enabled'] = (int) $task->ref->mymodule_enabled;
}
That value becomes a property the backend reads via d()->mymodule_enabled
(see Provision API). This is the only data channel
frontend → backend: nothing else in the Hostmaster database is visible to the
Drush backend. If you push context options on save, also implement
hook_drush_context_import() (hosting.api.php:71) so a re-imported context
recreates the node property.
Reacting to task results
Two frontend hooks fire when the backend returns, both resolved by task type at
runtime (str_replace('-', '_', $task->task_type), task.hosting.inc:249,262):
| Hook | Fires | Signature |
|---|---|---|
hook_post_hosting_TASK_TYPE_task($task, $data) |
Task succeeded (hosting.api.php:325) |
e.g. mymodule_post_hosting_backup_task() |
hook_hosting_TASK_TYPE_task_rollback($task, $data) |
Task failed + rolled back (hosting.api.php:246) |
e.g. mymodule_hosting_install_task_rollback() |
$data is the decoded drush_backend_output() array: output, error_status,
log, error_log, context. Use context to read backend-set values — the
in-tree backup handler pulls $data['context']['backup_file'] back into the
frontend (hosting.api.php:327-332).
A broader, status-agnostic hook is hook_hosting_task_update_status($task, $status) (hosting.api.php:392), fired on every status transition —
$task->task_status holds the old value, $status the new one. The status
constants are (task/hosting_task.module:1535-1557):
| Constant | Value |
|---|---|
HOSTING_TASK_PROCESSING |
-1 |
HOSTING_TASK_QUEUED |
0 |
HOSTING_TASK_SUCCESS |
1 |
HOSTING_TASK_ERROR |
2 |
HOSTING_TASK_WARNING |
3 |
There is no HOSTING_TASK_SUCCESSFUL; compare against HOSTING_TASK_SUCCESS.
Guarding destructive tasks
BOA blocks dangerous tasks against protected nodes (the Hostmaster site and platform). Two frontend hooks let an extension extend both lists:
hook_hosting_task_guarded_nodes()/…_alter(&$nids)(hosting.api.php:420,437) — return/mutate the list of protected node IDs.hook_hosting_task_dangerous_tasks()/…_alter(&$tasks)(hosting.api.php:446,464) — the task-type names blocked on guarded nodes (default set:backup-delete,backup,delete,disable,login-reset,restore).
If your task type mutates or destroys data, add it in
…_dangerous_tasks_alter() so it inherits the guard.
The queue system
BOA drives the queue from cron via hosting-dispatch, not the
hosting_queued daemon. hosting-dispatch
(dispatch.hosting.inc:16) reads the registered queues from
hosting_get_queues() (hosting.queues.inc:56) and, for each enabled and due
queue, forks the matching drush hosting-<queue> worker under its own lock.
Registering a queue — hook_hosting_queues()
hook_hosting_queues() (hosting.api.php:138) returns a queue spec keyed by
queue name. This fork ships exactly three implementers:
| Queue key | Module | Type | Worker function |
|---|---|---|---|
tasks |
hosting_task (task/hosting_task.module:475) |
serial |
hosting_tasks_queue() |
cron |
hosting_cron (cron/hosting_cron.module:22) |
batch |
hosting_cron_queue() |
task_gc |
hosting_task_gc (task_gc/hosting_task_gc.module:13) |
batch |
hosting_task_gc_queue() |
The dynamic hosting-<queue> Drush command is generated per registered queue
(next section). Defaults merged into every spec by hosting_get_queues()
(hosting.queues.inc:61-72): type serial, frequency 300s, items 5,
max_threads 6, min_threads 1, threshold 100, enabled TRUE. Per-queue
overrides live in variable_get('hosting_queue_<key>_frequency|items|enabled'),
so operators retune without touching code.
Queue type (hosting.module:22,41,61) controls the thread/frequency maths in
hosting_get_queues() (hosting.queues.inc:91-124):
HOSTING_QUEUE_TYPE_SERIAL('serial') — run once per frequency, fixeditems; use for anything that must not run concurrently (thetasksqueue).HOSTING_QUEUE_TYPE_BATCH('batch') — dividetotal_itemsacrossmin_threads…max_threadsbythreshold; use for parallelisable per-site work (cron,task_gc).HOSTING_QUEUE_TYPE_SPREAD('spread') — amortisetotal_itemsacross the frequency window, at most once a minute.
To add a queue:
/**
* Implements hook_hosting_queues().
*/
function mymodule_hosting_queues() {
$queues['mything'] = array(
'name' => t('My thing'),
'description' => t('Process my-thing items.'),
'type' => HOSTING_QUEUE_TYPE_BATCH,
'frequency' => strtotime('1 minute', 0),
'total_items' => mymodule_pending_count(), // Needed for batch/spread maths.
'items' => 5,
'singular' => t('item'),
'plural' => t('items'),
);
return $queues;
}
The worker is named hosting_<queue>_queue($count) — not a Drupal hook but a
direct callback resolved by name (documented as hosting_QUEUE_TYPE_queue(),
hosting.api.php:355), dispatched by hosting_run_queue() which sets
$func = "hosting_{$queue}_queue" (hosting.queues.inc:165):
/**
* Queue worker for the 'mything' queue — hosting_run_queue() calls this.
*/
function hosting_mything_queue($count = 5) {
foreach (mymodule_get_pending($count) as $item) {
// Fork heavy work, or process inline. Return when the batch is done.
drush_invoke_process('@self', 'mymodule-process', array($item->id), array(), array('fork' => TRUE));
}
}
Two alter hooks reshape queues before/after the item maths:
hook_hosting_queues_alter(&$queues) (hosting.api.php:156, pre-calc — e.g.
zero out total_items) and hook_hosting_processed_queues_alter(&$queues)
(hosting.api.php:179, post-calc — e.g. force enabled => FALSE).
Locking: both dispatch and worker hold a lock_acquire() semaphore with
HOSTING_QUEUE_LOCK_TIMEOUT = 900s (hosting.drush.inc:14) as the crash
recovery bound; the hold itself is sub-minute (fork-and-return). --lock-wait
tunes the acquire wait (HOSTING_QUEUE_DEFAULT_LOCK_WAIT = 30s); --force
bypasses a stuck lock.
hosting_queued's daemon (hosting-queued/hosting-release-lock) is not used by BOA — the cron-drivenhosting-dispatchabove is the queue engine. Do not build against the daemon.
Declaring hosting-* Drush commands
Same hook_drush_command() pattern as Provision, in <module>.drush.inc, with
two differences: bootstrap is DRUSH_BOOTSTRAP_DRUPAL_FULL (you need the
Hostmaster Drupal context to read task nodes), and the alias target is
@hostmaster (Master) or @hm (a per-Octopus Hostmaster, whose alias is
drush_aliases => 'hm'). The static hosting-* roster
(hosting.drush.inc): hosting-dispatch (:20), hosting-setup (:29),
hosting-task (:56), hosting-import (:75), hosting-pause (:83),
hosting-resume (:87).
function mymodule_drush_command() {
$items['hosting-mything'] = array(
'description' => dt('Process the my-thing queue.'),
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
);
return $items;
}
function drush_mymodule_hosting_mything() {
$nids = db_query("SELECT nid FROM {node} WHERE type = 'mything' AND status = 1")
->fetchCol();
foreach (node_load_multiple($nids) as $node) {
// Process.
}
}
Dynamically registered per-queue commands
hosting_drush_command() also registers one command per queue at runtime
(hosting.drush.inc:40-54): for every entry from hosting_get_queues() it
emits $items['hosting-' . $queue] with callback => 'hosting_run_queue' and a
queue => <key> marker. In this fork that yields hosting-tasks,
hosting-cron, hosting-task_gc. These are what hosting-dispatch forks; you
rarely call them by hand. Because the registration is conditional on
hosting_get_queues() being available, the block guards with
function_exists() and bootstraps to max on drush help
(hosting.drush.inc:33-40) — so drush @hostmaster help | grep '^ hosting-'
lists the true set on any given box.
Feature toggles
Hostmaster's Features admin page (?q=admin/hosting/features) enables and
disables optional bundles. A feature is declared by hook_hosting_feature()
(hosting.api.php:110) and — critically — the frontend enables/disables the
declared module when the toggle flips, and can run per-feature callbacks and
grant role permissions (hosting.features.inc:401-472). The declaring hook is
discovered even for disabled modules: it can live in a standalone file named
hosting.feature.<FEATURE_KEY>.inc, picked up by
hosting_get_features() via drupal_system_listing('/hosting\.feature\..*\.inc$/')
and a scan for *_hosting_feature functions
(hosting.features.inc:275-292).
/**
* Implements hook_hosting_feature().
* Lives in hosting.feature.mything.inc (loaded even when the module is off).
*/
function mymodule_hosting_feature() {
$features['mything'] = array(
'title' => t('My thing'),
'description' => t('Adds the my-thing capability.'),
'status' => HOSTING_FEATURE_DISABLED, // Default off.
'module' => 'mymodule', // Enabled/disabled with the toggle.
'group' => 'experimental', // required | optional | advanced | experimental.
// 'node' => 'mything', // Node type provided by the feature.
// 'enable' => 'mymodule_feature_enable', // Callback on enable.
// 'disable' => 'mymodule_feature_disable', // Callback on disable.
// 'role_permissions' => array('aegir client' => array('access mything')),
);
return $features;
}
Status constants (hosting.features.inc:10-20): HOSTING_FEATURE_DISABLED (0),
HOSTING_FEATURE_ENABLED (1), HOSTING_FEATURE_REQUIRED (2). A required
feature cannot be switched off in the UI. Query a feature's live state with
hosting_feature('mything') (hosting.features.inc:32) — which resolves through
module_exists() for module-backed features. On the backend side the mirror is
provision_hosting_feature_enabled() (see Provision API),
reading the hosting_features option the frontend passes down.
group places the checkbox in a fieldset; the four real groups the form renders
are required, optional, advanced, experimental
(hosting.features.inc:83-106). Enabling a feature queues an automatic Verify
of the Hostmaster site unless --no-verify is set
(hosting_feature_rebuild_caches(), hosting.features.inc:534-541).
Permissions
Per-module + per-task. Declare permissions with hook_permission(); feature
enable can auto-grant them via the feature's role_permissions map, and the
aegir administrator role automatically inherits every permission a hosting
module declares (hosting_add_permissions(), hosting.features.inc:565-571):
function mymodule_permission() {
return array(
'access mything ui' => array('title' => t('Access my-thing UI')),
'administer mything' => array('title' => t('Administer my-thing')),
);
}
Wire it to a task via the access callback / access arguments keys in the
task definition (default callback hosting_task_menu_access).
Adding a hosting_* feature module
The end-to-end unit is a paired module: a hosting_* frontend module plus
a backend *.drush.inc commandfile
(Provision API → Extension skeleton). The in-tree
reference pair lives in one http_basic_auth/ directory in the
hosting_tasks_extra satellite repo — the hosting_http_basic_auth frontend
module plus its paired drush/http_basic_auth.drush.inc backend commandfile.
Layout on disk:
http_basic_auth/
hosting_http_basic_auth.info # D7 module, dependencies[] = hosting
hosting_http_basic_auth.module # node hooks, form additions
hosting_http_basic_auth.install # schema
hosting_http_basic_auth.drush.inc # frontend _context_options hook
hosting.feature.http_basic_auth.inc # hook_hosting_feature() (loaded when off)
drush/http_basic_auth.drush.inc # backend half — the paired commandfile
drush/Provision/Service/http/basic/auth.php # backend service class
Its hosting.feature.http_basic_auth.inc is a minimal, faithful template:
function hosting_http_basic_auth_hosting_feature() {
$features['http_basic_auth'] = array(
'title' => t('HTTP Basic Authentication'),
'description' => t('Allows admins to specify HTTP basic authentication for sites.'),
'status' => HOSTING_FEATURE_DISABLED,
'module' => 'hosting_http_basic_auth',
'group' => 'advanced',
);
return $features;
}
BOA's hosting_tasks_extra itself registers as a feature the same way
(hosting.feature.tasks_extra.inc, status => HOSTING_FEATURE_ENABLED,
group => 'advanced') and is cloned from its own repo + enabled on
@hostmaster by the installer — it is the extension that hides obsolete task
buttons to keep the UI in lock-step with the running BOA release. Model a new
feature module on this pair. Checklist:
- Frontend module with
dependencies[] = hosting(plus anyhosting_*siblings you build on). hook_hosting_feature()inhosting.feature.<key>.incso the toggle can enable your module.hook_hosting_tasks()if you add a UI task;_context_options()if that task needsprovision_save.- Backend commandfile implementing
provision-<verb>and anyprovision_*hooks — shipped into.drush/usr/<ext>/, not a tenant-writable path (see Provision API and Drush fork internals). - Result hooks (
post_hosting_<type>_task,…_task_rollback) to record backend outcomes in the frontend.
Rules of the road:
- PHP 5.6 is the floor — syntax and runtime — for every line the Drush fork loads during dispatch.
- State crosses to the backend only through
context_optionsatprovision-savetime; never make the backend read the Hostmaster database. - Log through Drush (
drush_log()/drush_set_error()) so output rides the backend channel into the task log. cache-clear drushafter adding or moving a commandfile.
Hook-name corrections
Older docs garbled several hosting hook names; a copy-paste of the old forms gives dead code. Corrected against source at HEAD:
| Wrong (legacy docs) | Correct |
|---|---|
hook_hosting_TASK_OBJECT_tasks() (e.g. mymodule_hosting_site_tasks) |
hook_hosting_tasks() returning $tasks[OBJECT][TYPE] (task/hosting_task.api.php:42) |
hook_hosting_TASK_OBJECT_form |
hosting_task_<TASKTYPE>_form($node) (task/hosting_task.api.php:82) |
hook_hosting_task_TASK_FORM |
same — hosting_task_<TASKTYPE>_form (task/hosting_task.module:619) |
HOSTING_TASK_SUCCESSFUL |
HOSTING_TASK_SUCCESS (task/hosting_task.module:1545) |
hosting-migrate-batch Drush command |
no such command — migration is a Batch API UI form (migrate/hosting_migrate.batch.inc) |
hook_hosting_queued_process |
does not exist; the daemon is unused by BOA anyway |
@hm-oN / @hm-o1 alias |
@hm (per-Octopus) or @hostmaster (Master) |
Related
- Provision API — the backend half these extensions pair with; the hooks a Task ultimately dispatches to.
- Extending Aegir: contexts, hooks, module pairs — the full paired frontend+backend walkthrough and contexts data model.
- Provision backend internals — the framework core the backend commandfile plugs into.
- Hostmaster frontend internals — install-profile
composition and how the
hosting_*suite is bundled. - Drush fork internals — commandfile discovery
and the deny-filter that governs where a
*.drush.incmay live. - Aegir backend APIs overview — the task round-trip that invokes all of this.
- Commands reference — the consolidated CLI table.