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
autosymlinkapply — 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/privateare 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 reference —
autosymlinkmodes and flags,fix-drupal-site-symlinks.sh,symlinkinfo, verification commands, logs, deployment. - Nightly automation —
updatesymlinks --auto-fix/--orphan-report, cron cadence, queue pause,--debug. - Configuration —
_AUTOSYMLINK_NIGHTLYand 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-exportsonto a separate static filesystem. - Reference appendix — consolidated control-file and command tables.