Docs CLI

Remote Scripts

What remote scripts are, how they are piped over SSH, and a catalogue of every script that ships in lib/remote-scripts.

Remote scripts are bash scripts that live on the CaptainCore server and run on a managed WordPress site. They are the layer that does the actual work on a site: collecting data, scanning for malware, applying updates, deploying integrations.

They ship in lib/remote-scripts/ in the captaincore repository and are read from ~/.captaincore/lib/remote-scripts/ at run time.

How they are executed

A remote script is never copied to the site. The local file is redirected into the SSH session’s standard input and read by bash -s:

ssh <options> user@host "cd public/ && bash -s -- --site=<site> <extra flags>" < ~/.captaincore/lib/remote-scripts/<name>

Three consequences follow from that:

  • Editing a script takes effect on the very next run. There is nothing to rebuild and nothing to deploy.
  • Nothing is written to the customer’s server. The script exists only for the life of the SSH session.
  • The script gets the site slug as --site=<slug> automatically, and any flags you pass through are shell-quoted and appended after it.

You invoke one with captaincore ssh:

captaincore ssh mysite-production --script=detect-seo-spam
captaincore ssh mysite --script=plugin-diff --plugin=woocommerce --stat
captaincore ssh @production --script=find-nulled-components --parallel=20

--script accepts a path first. If the path does not exist on disk, the CLI falls back to ~/.captaincore/lib/remote-scripts/<name>, so you can point at a one-off script anywhere on the server without adding it to the repository.

Most scripts parse their own flags using the shared arguments helper, which turns --foo=bar into $foo and --flag into $flag=true.

To see what is installed with a one-line description parsed from each script’s header:

captaincore script list

Data fetching

These collect information and print it back. They are read-only.

Script Description
arguments Arguments helper. Parses CLI flags and arguments, sourced by other scripts rather than run on its own
archive-logs Enumerates rotated access and error log files for archival
check-security-log-size Reports the size of the captaincore_security_log table
component-hashes Generates per-component SHA256 content hashes for plugins, themes and mu-plugins
fetch-database-tables Fetch database tables
fetch-error-log-size Fetches total error log size in bytes
fetch-folder-size Calculate folder size in bytes
fetch-log-file Fetches a server log file
fetch-log-files Fetches list of server logs
fetch-site-data Fetches site data. The comprehensive sync used by captaincore sync-data
fetch-token Fetch token
fetch-users-logins Fetches WordPress user info for security analysis
file-manager List directories and read files, locked to the site’s home directory. Backs the Manager’s Files tab. Supports --action=list|view|delete with a base64 path
master-db-query Export select ids from the CaptainCore CLI master. Runs against the local master data directory, not a customer site
quicksave-fingerprint Generates a content fingerprint for quicksave change detection

Backups and database

Script Description
db-backup Backup database to a private directory
db-import Import database
db-convert-to-innodb Convert MyISAM database tables to InnoDB
db-code-audit Database-stored executable code audit
migrate Migrate a site from a backup snapshot. Takes --url=<backup-url> and an optional --update-urls
plugins-zip Generates plugin zips on the site
vault Manages secure, full site snapshots in a remote Restic repository backed by Backblaze B2. Forked from the CaptainCore do utility

Security detection

All of these are read-only scanners. Most emit pipe-delimited findings so they can be aggregated across a fleet sweep.

Script Description
detect-binary-payloads Detect native binary and reverse-shell payloads in a WordPress web tree
detect-database-triggers Detect WordPress database-layer persistence mechanisms
detect-elevated-permissions Detect WordPress roles and users with elevated permissions they should not have
detect-empty-homepage Detect empty or hollow homepages
detect-fake-dates Detect files with forged timestamps, where mtime is older than birth time
detect-forged-registrations Detect WordPress users with forged user_registered dates
detect-malformed-passwords Detect WordPress users with malformed or legacy password hashes
detect-sc-timestomp Detect the SC / sleek-router-kit toolkit family by timestomp modulus
detect-seo-cloaking Detect Googlebot-cloaking SEO spam, the doorway and full-page-swap variant
detect-seo-spam Detect SEO spam injection in WordPress content
detect-upload-probes Detect attacker upload-probe files
detect-user-enumeration Detect user enumeration leaks on a public WordPress site
detect-web3-injection Hunt for EtherHiding and blockchain-hosted malware loaders, and the service-worker delivery shape used to install them
find-fake-plugin Detect the “updatecore” fake WordPress plugin and variants
find-nulled-components Detect nulled and pirated WordPress plugins and themes
inventory-compromised-passwords Every CaptainCore Helper breached-password event. Emits ACTION|USER_LOGIN|ROLE|IP|TIMESTAMP, where BLOCKED means the login was refused and WARNED means it was allowed and only recorded
malware-hunt WordPress malware hunter, a standalone scanner
php-in-uploads Scans for PHP files and suspicious patterns in wp-content/uploads
plugin-diff Show a unified diff between an installed plugin and a clean copy from wordpress.org. Takes --plugin=<slug> plus optional --version=, --stat and --keep
report-compromised-passwords Reports compromised-password login attempts blocked by CaptainCore Helper
sals-detector Detect WordPress malware families catalogued by Sal Aguilar
verify-google-analytics-takeover Add a verification file for a Google Analytics takeover request

Maintenance

Script Description
activate Activate. Removes the deactivate mu-plugin
deactivate Deactivate. Installs the holding-page mu-plugin
email-health-check Sends an email health check
launch Launches a site. Updates the URL from dev to live, enables search engine indexing and clears cache
performance-monitor-deploy Deploys performance monitor scripts and a */10 * * * * crontab entry
performance-monitor-remove Removes performance monitor scripts and the crontab entry
reset-permissions Reset file and folder permissions to defaults
restic-cache-check Checks for a Restic cache on the remote server
restic-cache-purge Safely removes the Restic cache directory from a remote server
rewrite-prep Preps permalinks if needed
update Update themes and plugins
update-core Sideload a WordPress core build, boot the live site against it with the same wp-config, database and wp-content, probe for PHP fatals, then optionally apply the update. A failed probe aborts before any live core file changes

update-core is the script behind the fleet core-update sweep. When it is run through captaincore ssh @all --script=update-core, the bulk runner switches to a one-line-per-site mode, prints a summary, stores the run and emails a failure digest. See running commands.

Deployment and setup

Script Description
apply-https Apply SSL using the domain without www
apply-https-with-www Apply SSL using the domain with www
deploy-fathom Deploy the Fathom Analytics tracker
deploy-helper Deploy the CaptainCore Helper plugin
deploy-mailgun Deploys Mailgun
kickstart Server setup notes. A Markdown checklist for provisioning a CaptainCore server, not an executable script
prepare-wordpress Prepares a new WordPress site

Writing your own

A remote script only needs to read its flags and print results. Because it arrives on stdin, it cannot read stdin itself, and because the shell has already changed into the site’s home directory, paths can be relative to the WordPress root.

Two conventions make a script fit the fleet tooling:

  • Put a # Description: <text> line or a leading comment description in the first twenty lines, so captaincore script list can describe it.
  • Emit findings one per line with a stable delimiter, typically SEVERITY|TYPE|PATH|DETAIL, so results can be aggregated across a @all sweep.

Then run it without adding it to the repository:

captaincore ssh mysite --script=/home/user/scratch/my-check