elcreator / amount
aMount — mount cloud and remote storage (S3, Google Drive, Dropbox, OneDrive, SFTP, WebDAV and more, via Flysystem) into the Evolution CMS manager: pick folders, and big files such as backups are uploaded in the background, per manager, within what that manager may see.
Requires
- php: ^8.3
- evolution-cms/evolution: ^3.5.2
- league/flysystem: ^3.10
- league/flysystem-aws-s3-v3: ^3.10
- league/flysystem-sftp-v3: ^3.0.14
- league/flysystem-webdav: ^3.6
Requires (Dev)
- pestphp/pest: ^4.0
Suggests
- alphasnow/aliyun-oss-flysystem: Alibaba Cloud OSS driver
- azure-oss/storage-blob-flysystem: Azure Blob Storage driver (the successor of the abandoned league/flysystem-azure-blob-storage)
- justus/flysystem-onedrive: Microsoft OneDrive driver
- league/flysystem-async-aws-s3: AsyncAws S3 driver (lighter alternative to the AWS SDK)
- league/flysystem-ftp: FTP/FTPS driver (needs ext-ftp, which many PHP builds omit, so it is not a hard requirement)
- league/flysystem-google-cloud-storage: Google Cloud Storage driver
- league/flysystem-gridfs: MongoDB GridFS driver (needs ext-mongodb)
- league/flysystem-ziparchive: Write into a ZIP archive
- masbug/flysystem-google-drive-ext: Google Drive driver (personal accounts, shared drives)
- overtrue/flysystem-cos: Tencent Cloud COS driver
- overtrue/flysystem-qiniu: Qiniu Kodo driver
- platformcommunity/flysystem-bunnycdn: Bunny.net storage zone driver
- seiger/stask: Alternative queue manager: aMount runs its uploads as an sTask worker when it is installed
- spatie/flysystem-dropbox: Dropbox driver
Provides
None
Conflicts
None
Replaces
None
README
Mount cloud and remote storage into the Evolution CMS 3.5+ manager and offload big files to it in the background: database backups, media, anything a manager can see in the file manager.
- Any store Flysystem can talk to. Amazon S3 and compatible (R2, MinIO, Wasabi, Backblaze B2), Google Drive, Dropbox, OneDrive, Google Cloud Storage, Azure Blob, SFTP, FTP/FTPS, WebDAV (Nextcloud, ownCloud), Bunny, Alibaba OSS, Tencent COS, Qiniu, MongoDB GridFS, a ZIP archive, or a folder on the server. Drivers whose package is missing are listed with the exact command to install them.
- Background transfers. Pick a folder, choose copy or move, optionally a schedule. Uploads, downloads back and "how much space does this take?" counts run in a queue; the page shows progress and can cancel.
- Both directions. Bring a file back into the site when you need it (a backup, for example), optionally deleting the remote copy afterwards.
- Public files keep their URL. An offloaded public file that is gone from disk is redirected to, or streamed from, the store when its old URL is requested. Private files (backups) are downloadable only through the manager.
- For AI agents too. With eMCP installed, agents get tools to list mounts,
browse, deliver a file ("put the result on my Google Drive") and fetch one back, with exactly the rights of the
manager they act for. Other packages use the same thing through
Elcreator\aMount\Services\Delivery.
Install
cd core php artisan package:installrequire elcreator/amount "*" php artisan migrate
The module appears in the manager menu as aMount. Run php artisan amount:drivers to see which drivers are
installed; install a missing one with the command it prints. php artisan amount:demo --run seeds a demo on a local
disk (no account needed).
Permissions
| Permission | Lets a manager |
|---|---|
amount |
Use the module: connect their own accounts (a personal Google Drive, Dropbox...) and offload folders they can see in the file manager. |
amount_manage |
Administer: site-wide mounts (shared credentials, optionally limited to chosen user groups), everybody's rules, the server configuration. |
Administrators (role 1) get both. Nobody else gets anything by default: grant amount to a role in
Users -> Roles. Holding amount never widens what a manager may see.
Queue
Uploads need a worker. Two are supported, chosen with queue.driver in core/custom/config/cms/settings/aMount.php
(or AMOUNT_QUEUE):
auto(default): sTask when installed, otherwise the CMS task queue.system: the CMS task queue. Keepphp artisan system:task-workerrunning (or let the scheduler start it).stask: sTask. Keepphp artisan stask:workerrunning.
Scheduled rules and housekeeping are started by amount:tick, which aMount adds to the Laravel scheduler every minute.
That needs the usual one-line cron entry:
* * * * * cd /path/to/core && php artisan schedule:run >> /dev/null 2>&1
The Mounts tab shows which queue is active and whether it looks healthy. A run whose worker died is queued again
automatically after limits.stale_after seconds; every step is safe to repeat.
Serving offloaded public files
When a public rule offloads assets/images and the local copy is removed (mode = move), the browser still asks for
/assets/images/photo.jpg. aMount answers when the CMS is asked, so the web server must hand missing files to the
CMS instead of answering 404 itself. The Server tab (and php artisan amount:server) generates exactly what is needed
for the folders your public rules use; the Probe button checks that it works.
-
nginx (
ng.inx): static-file locations answer 404 for missing files before the CMS is asked. Include the generated file (core/custom/amount/amount.nginx.conf) in the server block after the deny rules and before the static-files location, then reload nginx:location ~ ^/(assets/(?!cache/|backup/|export/|import/)|uploads/) { try_files $uri @rewrite; expires max; access_log off; }
-
Apache (
.htaccess):RewriteRule ^(manager|assets|js|css|images|img)/.*$ - [L]stops friendly URLs for/assets. aMount maintains a block between# BEGIN aMountand# END aMountin front of that line (and keeps a backup,.htaccess.amount.bak):RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(assets/(?!cache/|backup/|export/|import/)|uploads/).+$ index.php?q=$0 [L,QSA]
-
FrankenPHP / Caddy (Salo
Caddyfile): nothing to change, missing paths already go to the CMS.
php artisan amount:server nginx --write writes the file; --probe tests it. Serving can be switched off with
serve.enabled = false; serve.mode chooses redirect, proxy or auto (redirect when the mount has a public URL).
My computer (ai.artur.work)
The driver My computer (ai.artur.work) (ai-computer) mounts the folders a person shares
from their own computer - Windows, macOS or Linux, behind any NAT - with the ai.artur.work
program (mesh-daemon). An agent can then say "upload sqlbackups 2025/08 from my computer mypc
to backups": amount.fetch with mount="mypc", remote="sqlbackups/2025/08", to="backups",
recursive=true - a computer's name needs no prepared mount, the manager's own is made on first
use. amount.computers lists the account's computers, their folders, quotas and this site's
access, and this site's code.
- The computer decides. It shares only folders its person picked, and asks them to allow this site the first time (on the desktop and in its settings page) - they compare the code shown there with the one on the aMount page (My computers). Nothing on the site or on ai.artur.work can grant access.
- Sealed end to end (X25519 + AES-256-GCM,
src/Computer/Seal.php, the daemon's own test vector intests/fixtures/): ai.artur.work relays bytes it cannot read or forge. The computer's key is pinned on first contact; a changed key is refused until an administrator confirms it (Trust its new key). - Fastest path first: straight to the computer on the same network (when its owner allows
direct transfers), the computer connecting to this site (
<site>/amount-mesh/v1/..., answered by theOnPageNotFoundlistener - needs a public or reachable address, seecomputers.public_url), the owner's own relay (relay_url,mesh-daemon --relay), else the gateway's relay. - Conflicts: an existing file is kept unless replacing is allowed - for an agent, only when
eMCP allows destructive tools for the call (
on_conflict=newer; asking for it without that permission is refused); on the computer, only when its folder setting or its person agrees. A newer file replaces an older one, never the reverse. Every file not written is listed with its reason in the run (amount.status): a name this server cannot hold (CON, two names that differ only in case on Windows/macOS), a conflict, a quota. - Quotas, both sides: the computer's folders have quotas (shown, checked before an upload);
files from a computer fill a site folder only up to the mount's
site_quota_mb, checked for the whole batch before anything moves. - Key: the mount's own, else the owner's (or the site's) aIMage key, else
cms.settings.aMount.computers.api_key/AMOUNT_GATEWAY_KEY. On ai.artur.work, allow "Shared folders on your computers" for that key. Needs ext-sodium, or PHP 8.4's OpenSSL. - Which site:
Computer\SiteAddressknows the installation's domains (sMultisite rows,EVO_SITE_HOSTNAMES, a pinnedEVO_SITE_URL) and its public address even in the CLI worker (computers.public_url, sMultisite'sserver_protocol://domain, or the address the aMount page was opened at);amount.fetch/delivertakesiteand refuse a domain this installation does not serve.
Security model, in plain words
- A manager can only offload what they can see in the file manager. Every folder and file is checked against that
manager's file root, the permission-protected folders and, with
use_udperms, the file groups; links are refused. This is checked when a rule is saved and again for every file on every run, as the account stands at that moment: revoke a permission, block the account or narrow their file root, and the next run notices and stops. - Personal mounts are private. Only their owner can open or use them, not even an administrator (who may delete
them). Credentials are encrypted in the database and never sent to the browser: forms show
********and a blank secret field means "keep the stored value". Credentials that must not live in the database can be defined in configuration (mountsinaMount.php); such mounts are read-only in the manager. - Site mounts are usable by managers with
amount(optionally only members of chosen user groups). A manager withoutamount_manageis confined tou<their id>/on a site mount, so one manager's uploads cannot land in or overwrite another's. - Downloads back are cautious. The destination must be a folder the user can write to; nothing is overwritten unless
asked and allowed; executable and server-configuration file names are refused unless the user may edit code; files are
written to
<name>.amount-part, size-checked, then renamed. A database dump downloaded back is restored with the CMS's own Backup manager (there is no queued restore). - Private files are never served by URL. Only public rules answer anonymous requests; backups are downloaded through the authenticated manager endpoint, which re-checks that the manager may see the original.
- Every POST carries the manager's CSRF token; secrets never appear in responses, logs or error messages.
Configuration
Override any key in core/custom/config/cms/settings/aMount.php; defaults are in config/aMount.php (enable, queue,
limits, serve, server, mounts).
Development
The suite borrows an Evolution CMS core's autoloader. Point EVO_CORE_PATH_TEST at a core with dev dependencies
installed and run php $EVO_CORE_PATH_TEST/vendor/pestphp/pest/bin/pest --no-coverage from this directory. See
AGENTS.md for the architecture and ci/README.md for building a full CMS with the plugin inside (Docker).
Licence: see LICENSE.