Platform developer account
Available on every BOA server, Drupal and Backdrop platforms only, off by default. An operator turns it on per Client, in a root-owned file; nothing in the control panel creates, changes or removes it, and no tenant file can.
The ordinary per-Client sub-account (oN.<client>, see
shell extras) reaches its Client's site directories
and nothing above them: files and themes, no Composer, no Drush. Some Clients
have a developer who maintains the whole codebase — they need the platform
root, composer and the platform's own Drush. This account is that: a second
login for the same Client, oN.<client>-dev, reaching every platform where
every site is that Client's — read there, and writing exactly where the
main oN.ftp login writes — and refused on any platform that also carries
someone else's site. Root decides from a root-owned setting, and the worker
re-checks the decision on every pass.
Before you start
-
The Client exists and owns at least one Drupal or Backdrop site — the Client field on the site's form, in the control panel. That ownership is what creates
/data/disk/<oN>/clients/<client>/with one symlink per site, and it is what gives the Client its ordinary sub-account. The directory name underclients/is the name you will put in the setting:SHls /data/disk/o1/clients/ -
Plan the platforms. A platform qualifies only when every site directory on its docroot (every
sites/*/settings.phpexceptallanddefault) is one of this Client's sites. Your own sites, another Client's sites, a site owned by nobody — any one of them keeps the whole platform out. So a developer who should reach a codebase needs that codebase to hold only their Client's sites; on a shared platform, move the other sites elsewhere first, or accept that the platform stays out. -
Drupal and Backdrop only. Grav and Textpattern platforms are never enumerated, exactly as their sites are never linked into a sub-account.
Step 1 — enable it
Add the Client's directory name to _LTD_PLATFORM_CLIENTS in the instance's
Octopus control file:
# /root/.o1.octopus.cnf
_LTD_PLATFORM_CLIENTS="acmecorp"
Several Clients are space-separated: _LTD_PLATFORM_CLIENTS="acmecorp widgetco".
Use the directory name exactly as ls shows it. Edit the line that is already
there: every instance created since the feature landed carries
_LTD_PLATFORM_CLIENTS="" in its control file; an older instance has no such
line and needs one added. The file is sourced, so with two lines the last one
wins — a second line placed above the empty one is a silent no-op. There is
nothing to run: the worker reads the file on every pass, so no octopus or
barracuda run is needed, and the line survives upgrades (an upgrade pass
rewrites the control file only when it is missing or hand-seeded, and lays the
kept lines back).
Step 2 — what the next pass builds
manage_ltd_users.sh runs from cron every three minutes (it defers while a
barracuda or octopus run holds the box). On the first pass after the edit it
creates, for each name in the list:
- The account
o1.acmecorp-dev: the directory name reduced to lowercase letters and digits, then-dev. The full name must fit in 32 characters; a longer one is reported and nothing is granted (shorten the Client's name). - Groups: the instance's own group as primary, plus
www-data,users,lshellgandltd-shell— the same set as the ordinary sub-account. The login shell is MySecureShell, which hands interactive sessions to lshell, exactly as for every tenant login. - The password, generated fresh and written to
/home/o1.ftp/users/o1.acmecorp-dev— the tenant sees it as~/users/o1.acmecorp-devnext to the ordinary sub-account's file. Nothing is emailed anywhere; the tenant forwards it. - The home
/home/o1.acmecorp-dev:~/sites→ the Client's directory (the same view the ordinary sub-account has),~/.tmp, and a root-owned~/platforms/with one link per qualifying codebase, named<revision>-<codebase>(for example002-drupal-10.4.10-prod), plus aREADME.txtrepeating the Drush-window instructions. The links are placed by root so the SFTP side sees them too; the home is immutable between passes. - The lshell section
[o1.acmecorp-dev]in/etc/lshell.conf: itspathcarries the Client's site directories and their file stores as usual and the app root of every qualifying platform (the composer root wherevendor/lives, the docroot otherwise); and, once at least one platform is granted, two extra lines re-addcomposer,drush,drush8,drush10,drush11,vdrushandvendor/drush/drush/drush.phponallowedandallowed_shell_escape. lshell applies the per-user section last, after theltd-shellgroup's removals, so the re-additions win. Theoversshlist — what a one-shotssh user@host commandmay run — is not widened.
A platform that fails the test is simply not granted, and the pass writes why:
... platform /data/disk/o1/distro/002/drupal-10.4.10-prod refused for o1.acmecorp-dev: not single-client: /data/disk/o1/distro/002/drupal-10.4.10-prod/sites/blog.example.com
in /var/log/boa/manage_ltd.incident.log, naming the site directory that is not
the Client's (or one of three fixed reasons: no docroot, no sites directory,
not a Drupal or Backdrop platform); the other platforms are still granted. The
same text is mailed to _MY_EMAIL once a day per platform unless
_INCIDENT_REPORT is OFF (or NO) in /root/.barracuda.cnf. A Client that
owns no Drupal or Backdrop site gets no account at all — the ordinary
sub-account rule: no site, no account. That case is not silent, though: the
sub-account step drops a Client's Grav and Textpattern links and removes the
emptied client directory, and the platform step, which runs straight after it
in the same pass, then finds no directory for the name and logs
platform account for o1: no client directory /data/disk/o1/clients/acmecorp: listed in _LTD_PLATFORM_CLIENTS; nothing granted
on every pass (mailed once a day) until the name leaves the list or the Client
owns a Drupal or Backdrop site.
Step 3 — verify it
getent passwd o1.acmecorp-dev
id -nG o1.acmecorp-dev
ls -l /home/o1.acmecorp-dev/platforms/
sed -n '/^\[o1.acmecorp-dev\]/,/^\[/p' /etc/lshell.conf
grep acmecorp-dev /var/log/boa/manage_ltd.incident.log
Expect the account in ltd-shell, one link per platform you planned, a path
line that ends with those app roots and the two allowed ... : + [...] lines
under it, and no refused for line for a platform you expected. Then check the
shell itself as root, through the account's own login shell (MySecureShell hands
the session to lshell exactly as an SSH login would), without the password:
su - o1.acmecorp-dev -c 'composer -V'
su - o1.acmecorp-dev -c 'cd ~/platforms/<a Drupal 7 or Backdrop one> && drush8 status'
su - o1.acmecorp-dev -c 'cd ~/platforms/<a Drupal 8+ one> && vdrush --version'
The first two answer; the third is refused with lshell: command not found: "vendor/drush/drush/drush.php" until a window is open — next section.
Step 4 — hand it over
The tenant reads ~/users/o1.acmecorp-dev and passes the login on; the
developer's own key goes into /home/o1.acmecorp-dev/.ssh/authorized_keys, the
same as for any sub-account. The tenant-facing instructions — connecting, what
they see, the window, the limits — are on
extra accounts for your developers;
point them there.
The Drush window
Drupal 8+ platforms are kept locked for Ægir's own Drush 8 between uses
(how the lock state is derived):
the lock de-types the console classes and overlays the psr/log version Drush 8
needs, and a modern Drush is typed against the current psr/log, so under PHP 8
the two cannot load in one codebase — the developer's vdrush needs the
platform unlocked while they work. The
account asks for a window and the worker opens and closes it:
- The developer runs
touch ~/.tmp/drush-window.request. A regular file at that name is a request; a symlink there is ignored. - On its next pass the worker removes the marker, starts a root-owned clock
(
/var/backups/ltd/window/o1.acmecorp-dev), records each granted composer platform's current state aslockedorunlockedin/var/backups/ltd/window/o1.acmecorp-dev.prev, and runsprovision-dunlockas the instance user on every granted platform that is locked (Drupal 7 and Backdrop platforms have no lock and need no window). The incident log getsdrush window opened for 60 min; the unlock task's own output lands in a dated file under/var/backups/ltd/log/, a directory every BOA upgrade pass clears, so the incident log is the durable record. - Every pass inside the window re-unlocks any granted platform that a task locked again meanwhile (a site Verify re-locks its platform).
- Touching the marker again restarts the clock; the recorded states stay.
- When the clock is older than 60 minutes the next pass runs
provision-dlockon every platform it had recorded aslocked, removes the state files and logsdrush window closed. A platform an operator had unlocked before the window stays unlocked.
The length is a constant in the worker, not a control-file key. ls /var/backups/ltd/window/ shows which accounts hold an open window (one clock
file each, plus its .prev). To close a window early, age the clock — it is
root's file, nothing a tenant can reach — and let the next pass do the rest:
touch -d '2 hours ago' /var/backups/ltd/window/o1.acmecorp-dev
While a window is open: control-panel tasks on that platform's sites can fail, because Ægir's Drush 8 cannot drive an unlocked platform; a site Verify re-locks it and the next pass unlocks it again; and a compiled container built while unlocked may need a Verify once the platform is locked again. Tell the tenant to time their panel work around it.
Retiring the account
Remove the name from _LTD_PLATFORM_CLIENTS — or let the Client lose its last
site, or delete the Client, which removes its directory — and the next pass:
- closes an open window (the platforms it had found locked are locked again),
- deletes the account, backing the home up under
/var/backups/zombie/deleted/<timestamp>/(that is an archival copy, not a reactivatable home), - removes the password store file and the lshell section, and logs
LTD account o1.acmecorp-dev removed: platform account retired (not in _LTD_PLATFORM_CLIENTS of o1).
Only the first route is complete on its own: after deleting the Client or
taking its last site away, the name is still in _LTD_PLATFORM_CLIENTS, and
every later pass reports no client directory for it (logged, mailed once a
day) until you remove the name as well. Nothing the developer wrote is touched:
the code and the sites belong to the tenant account, the login only reached
them. An account is retired the moment it leaves the list — deliberately, on the spot: a section vanishing while the
login stayed would drop it to the group policy, which is wider than the
account ever was.
Password rotation is the sub-account rule: delete
/home/o1.ftp/users/o1.acmecorp-dev (the tenant can do it from their own
login) and the next pass rebuilds the account with a fresh password in the same
file; the home, including ~/.ssh/authorized_keys, goes with the rebuild. The
90-day expiry applies as to every tenant login.
When something is missing
| You see | Why | What to do |
|---|---|---|
No account after a few minutes, incident log says no client directory, and the control panel shows no such Client (or one owning no site) |
The name is not a directory under clients/: a typo, or a Client that owns no site yet |
Compare with ls /data/disk/o1/clients/; make the Client the owner of a Drupal or Backdrop site |
No account, incident log says no client directory, but the control panel shows the Client with Grav or Textpattern sites only |
Those links are dropped and the emptied clients/ directory removed by the sub-account step, before the platform step runs in the same pass (the pass log reads Skipping non-Drupal site ..., then Empty ... - deleting now) |
Give the Client a Drupal or Backdrop site, or take the name out of _LTD_PLATFORM_CLIENTS |
| No account, nothing logged at all | A barracuda or octopus run is holding the box (the worker defers), or the line sits above an existing _LTD_PLATFORM_CLIENTS="" line, which wins |
Wait for the run to end; edit the existing line instead of adding one |
Incident log says longer than 32 characters |
The account name would not fit | Rename the Client (a shorter directory name) |
A platform is not under ~/platforms |
It was refused — the incident log names the site directory that is not the Client's, or one of the fixed reasons (no docroot, no sites directory, not a Drupal or Backdrop platform) |
Move that site off the platform (onto one of its own, or one of its Client's), or accept the refusal; a non-Drupal, non-Backdrop platform is never granted |
vdrush is refused on a Drupal 8+ platform |
No window is open (the request was never made, or was a symlink), or the platform has no vendor/drush |
Have the developer touch ~/.tmp/drush-window.request; wait one pass |
Incident log says no platform alias found |
The window cannot unlock a platform whose docroot no platform_* alias of the instance names |
Verify the platform from the control panel so its alias exists |
| The developer cannot write to a platform root, or into a site directory itself | Platforms installed by the control panel under distro/ are 0755 oN:oN (their vendor/, web/ and web/modules/ 2755), and a site directory with its settings.php and drushrc.php is 0755 oN:oN too — read-only for every tenant login, this one included |
Expected; the writable places are a site's files/, private/, modules/, themes/ and libraries/ (the 2775 parts, exactly as for the main oN.ftp login) and the whole tree of a platform the tenant deployed under static/ (measured: 2775 oN:oN after its Verify) |
| A panel task on the platform failed | A window was open | Re-run after the window closes, or close it early (above) |
Security model
- Authority is root's. The list lives in
/root/.<oN>.octopus.cnf; the platform set is enumerated by root from the Client's site links and the platforms' ownsites/directories; the only thing the account can do is drop a request marker, and a marker can only ever ask for a window, never for a path. - Fail-closed from the platform side. A site directory is accepted only when its real path is one of the Client's links; a site the operator owns appears in no Client directory at all, so "not in the farm" refuses the platform rather than passing it.
- The grant is policy, not ownership. lshell's
pathand the Landlock confinement of the per-instance group decide what the shell may reach; Unix permissions are unchanged, which is why a control-panel platform stays read-only, a site directory takes writes only in itsfiles/,private/,modules/,themes/andlibraries/, and a tenant-deployed platform understatic/takes them throughout — for this login exactly as for the main one. Composer therefore changes code only on the tenant's own platforms; on a control-panel platform it reads (show,outdated,why). - One-shot SSH stays narrow. The
oversshlist is the sub-account's; a script driving the account from outside gets the same short list as before. - Refusals are reported, once a day per platform, through the ordinary
incident channel (
_INCIDENT_REPORT,_MY_EMAIL).
Related
- Extra accounts for your developers — the tenant-facing page, including the developer's steps.
- Shell extras — the ordinary per-Client sub-account this account is built on.
- lshell + manage_ltd_users — the restricted shell and the worker that runs every pass described here.
- octopus.cnf — the control file
reference,
_LTD_PLATFORM_CLIENTSamong the instance keys.