Native files symlinking — overview & safety model

BOA stores each site's writable files and private directories outside the platform docroot, in the owning Octopus account's static store, and links them back into the site with symlinks:

/data/disk/<account>/.../sites/<url>/files    ->  /data/disk/<account>/static/files/<url>/files
/data/disk/<account>/.../sites/<url>/private  ->  /data/disk/<account>/static/files/<url>/private

Keeping the real data in static/files/<url>/ — with only a symlink in the platform — means a site's uploads survive platform rebuilds and code redeploys untouched, can live on attached storage, and are cheap to back up and migrate. This layout is native: new sites are symlinked as part of installation, clones and migrated/renamed sites are re-homed into their own store, and the tools plus opt-in nightly automation keep the layout correct and report stray data.

This page covers the behaviour and safety model — when the conversion runs, under what privilege, and how to switch it off. The account-level static/files directory may itself be a symlink onto attached storage; that lower layer is a separate concern, handled by migratefs.

When it runs

Event Behaviour
Install (new site) Provision's post_provision_install hook calls the sudo wrapper with an explicit --account — the nginx vhost is not written until the first verify, so autosymlink trusts the Drush alias alone. The root-privileged narrow single-site mode moves the freshly created files/private into the account store and symlinks them back. Success shows in the task log as [native-symlink] <site>: files/private moved into the per-account store and symlinked.
Clone A clone deploys from a backup that preserves symlinks, so it initially points its links at the source site's store. After the post-deploy verify, the clone task runs the narrow mode with --force-unshare: it copies the source's files into the clone's own static/files/<clone>/ store and repoints the links. --force-unshare breaks the inherited link even when a share.files control file exists — a fresh clone never opted into sharing.
Migrate / rename Same as clone: Provision calls _provision_drupal_native_symlink($uri, TRUE) right after the target verify, re-homing the renamed site's files/private into its own store (a pre-existing store at the target name is archived aside first). The old-name store becomes a normal reported orphan — archived on the next nightly sweep, never auto-deleted. See Orphans & archiving.

If the unshare cannot complete — low disk, a missing source store, a failed copy — the step logs [ALERT] Clone unshare for <site> not completed ... manual review required and the clone still succeeds (the same [ALERT] appears when the run is deferred by the nightly maintenance pause, or when the per-site dry-run is not clean). Any other failure of the step surfaces only as a [native-symlink] warning — warn-not-fail, the clone is never aborted. Recovery, once the blocker (usually disk space) is cleared:

sudo /usr/local/bin/fix-drupal-site-symlinks.sh --site=<clone> --force-unshare

The Clone and Migrate task pages describe where these lines land in the task log — see Site cloning & in-host migration and Cross-host migration.

Privilege model

The per-account store under static/files is root-managed, and Provision runs as the unprivileged account user — so Provision never touches the store itself. It delegates through exactly one narrow door:

  • Provision calls sudo /usr/local/bin/fix-drupal-site-symlinks.sh --site=<url> … (NOPASSWD, fail-closed argument validation).
  • The wrapper forwards only the narrow single-site autosymlink apply — it cannot reach the global batch/live modes.
  • autosymlink (root) performs the move-and-symlink; the narrow run does its own per-site clean dry-run first, never touches the global batch state, and defers while the nightly maintenance pause is active so it cannot race the batch sweep.

Wrapper hardening, sudoers and AppArmor wiring are detailed on Tools reference and AppArmor.

Scope

  • Octopus accounts only (/data/disk/<account>). The master hostmaster account keeps plain real directories.
  • Existing sites are not auto-converted. A site whose files/private are still real directories stays that way through normal installs and verifies — moving data at an unexpected time is deliberately avoided. Conversion of the back-catalogue happens only via the opt-in nightly auto-fix or a manual tools run.

Safety properties

The subsystem is fail-open, warn-not-fail by design: any failure at any stage leaves plain real directories in place — never a dangling symlink, never an aborted install/clone/migrate task. The conversion itself is designed to be crash-safe (the move is rc-checked before any symlink is created, and an interrupted run is recovered by self-heal); the mechanics live on the Tools reference page.

Kill-switches

Native symlinking is on by default and can be disabled without a code change:

Control file Scope
/data/conf/disable_native_files_symlink.cnf box-wide — all accounts on this host
/data/disk/<account>/static/control/no_native_files_symlink.info one Octopus account

While present, new sites get plain real files/private directories and the clone/migrate re-homing step is skipped. Remove the file to re-enable. The full control-file and cnf-variable catalogue is on the Configuration leaf.

Deliberate file sharing

Two sites can intentionally share one store (e.g. a staging copy reading the live site's uploads). Mark the intent per shared site:

touch /data/disk/<account>/static/control/share.files.<site>.info

While that file exists the tools treat a cross-site symlink for <site> as intentional and leave it alone — except during cloning, where --force-unshare always gives the new site its own copy, because the new name never opted in.

Known caveat

A cross-account clone under low disk may keep its link pointing into the source account's store until the manual --force-unshare rerun above — that is what the [ALERT] line flags, and it needs operator follow-up. A same-account shared link is harmless in the meantime: same owner, same store tree, no privilege boundary crossed.

Related pages

  • Tools referenceautosymlink modes and flags, fix-drupal-site-symlinks.sh, symlinkinfo, verification commands, logs, deployment.
  • Nightly automationupdatesymlinks --auto-fix / --orphan-report, cron cadence, queue pause, --debug.
  • Configuration_AUTOSYMLINK_NIGHTLY and friends, defaults by system class, every control file.
  • Orphans & archiving — ghost detection, the .archived/ layout, the accumulation alert, operator-only pruning.
  • Backups on the static filesystem — relocating backups/backup-exports onto a separate static filesystem.
  • Reference appendix — consolidated control-file and command tables.