@black-magic/wp-site-maintenance
v0.0.10
Published
CLI tool to backup and update WordPress sites on SiteGround
Keywords
Readme
wp-site-maintenance
CLI tool to automate WordPress maintenance on SiteGround-hosted sites. Connects via SSH, creates backups (DB + wp-config.php + per-plugin compressed tars), updates plugins one by one and WordPress core, and automatically rolls back on failure.
Installation
# Run without installing
pnpm dlx @black-magic/wp-site-maintenance
# Or install globally
pnpm add -g @black-magic/wp-site-maintenanceSetup
Create a .wp-maintenance.json file in the directory where you'll run the command, or in ~/ for global use:
{
"localBackupDir": "~/wp-backups",
"sites": [
{
"sshAlias": "my-wp-site",
"siteDir": "example.com"
}
]
}localBackupDir is optional — defaults to ~/wp-backups. Backups are saved as {localBackupDir}/{siteDir}/{timestamp}.tar.gz.
You can also specify a config file explicitly:
wp-maintenance --config /path/to/config.jsonNon-standard paths
Some WordPress installations (e.g. Bedrock/Roots) keep plugins and wp-config.php outside the standard locations. Use pluginsDir and wpConfigPath to override:
{
"sites": [
{
"sshAlias": "my-bedrock-site",
"siteDir": "example.com",
"pluginsDir": "public_html/app/plugins",
"wpConfigPath": "../wp-config.php"
}
]
}Both are relative to the site root ($HOME/www/{siteDir}/) and optional:
pluginsDirdefaults towp-content/pluginswpConfigPathdefaults towp-config.php
SSH connection options
Option A — SSH config alias (recommended)
{ "sshAlias": "my-wp-site", "siteDir": "example.com" }~/.ssh/config entry:
Host my-wp-site
HostName ssh.example.siteground.com
User u1234-abc12def34gh
Port 18765
IdentityFile ~/.ssh/id_rsa_sitegroundIf IdentityFile is omitted, the SSH agent (SSH_AUTH_SOCK) is used automatically.
Option B — explicit credentials
{
"ssh": {
"host": "ssh.example.siteground.com",
"username": "u1234-abc12def34gh",
"port": 18765,
"privateKeyPath": "~/.ssh/id_rsa_siteground"
},
"siteDir": "example.com"
}Usage
wp-maintenance # interactive update
wp-maintenance --dry-run # preview pending updates without making changes
wp-maintenance --status # check last run status for all configured sites
wp-maintenance --version # print the installed version
wp-maintenance --config /path/to/config.jsonDuring development:
pnpm dev
pnpm dev -- --dry-run
pnpm dev -- --statusNormal mode
- Select which sites to update (multi-select)
- Confirm
- For each site, sequentially:
- Shows working directory on the remote server
- Creates a timestamped backup: DB,
wp-config.php, and a.tar.gzper updated plugin - Compresses the entire backup and downloads it locally via SCP
- Deletes the remote backup folder only after all updates succeed
- Updates plugins one by one, then WordPress core
- On failure: automatically rolls back to the pre-update state
- Shows a summary with local backup path per site
Dry-run mode (--dry-run)
Connects via SSH, checks for pending plugin and core updates, and prints what would be updated — without making any changes or requiring confirmation.
Status mode (--status)
Connects to each configured site and reads .wp-maintenance-status.json to show the result of the last maintenance run. No changes are made.
✓ example.com success 2026-04-03 10:15 Yoast SEO 22.1→22.2, WordPress 6.4.1→6.5.0
⚠ other.com failed_update 2026-03-28 09:12 Failed on: woocommerce
✗ broken.com failed_rollback 2026-03-15 09:12 ← REQUIRES MANUAL INTERVENTION
– new.com never runWhat gets backed up
Before any updates, the tool creates a backup on the remote server at $HOME/www/{siteDir}/.wp-backups/{timestamp}/:
| File | Description |
|------|-------------|
| db.sql | Full database dump via wp db export |
| wp-config.php | Copy of the site's config file |
| plugins/{slug}.tar.gz | Compressed backup of each plugin being updated |
Only plugins that are actually being updated are backed up — not the entire wp-content/plugins/ directory.
The backup folder is then compressed into a .tar.gz and downloaded to your local machine at {localBackupDir}/{siteDir}/{timestamp}.tar.gz. The remote folder is deleted after a successful download — if the download fails, the remote copy is kept.
After a successful run, each site also has a status file at $HOME/www/{siteDir}/.wp-maintenance-status.json with the result of the last operation.
License errors
If a plugin update fails because the license is expired or invalid (WP-CLI outputs "Check your license details first"), the plugin is skipped — no rollback is triggered and the remaining updates continue. The summary shows:
⚠ Advanced Custom Fields Pro 6.2.0 skipped — license errorAuto-rollback
If any plugin or core update fails for any other reason, the tool immediately rolls back:
Plugin failure:
- Restore each backed-up plugin directory from its
.tar.gz - Restore the database via
wp db import
Core failure:
- Restore all backed-up plugin directories
- Re-download the previous core version via
wp core download --version={old} --force - Restore the database
The final summary distinguishes three states:
| State | Meaning | |-------|---------| | ✓ Updated | All updates succeeded | | ⚠ Rolled back | An update failed, site restored to pre-update state | | ✗ Critical | An update failed and rollback also failed — manual intervention required |
Update order
Plugins are updated first, then WordPress core. If a plugin fails, core is still at the old version and the rollback is clean.
When multiple core updates are available (e.g. a minor 6.8.5 and a major 6.9.4), the tool always applies the latest available version. Dry-run lists all of them with their type so you have full visibility before committing.
Build
pnpm build # Compiles to dist/index.js (ESM bundle)Commit conventions
This project uses Conventional Commits, enforced via commitlint on every commit:
feat: add dry-run flag
fix: handle SSH timeout gracefully
chore: update dependenciesTo regenerate CHANGELOG.md from commit history:
pnpm changelog # from last tag to HEAD
pnpm changelog:all # entire history
pnpm changelog v1.0.0 # from a specific tagSiteGround SSH notes
- SSH port is
18765, not22 - Home directory:
/home/{username}/, sites under/home/{username}/www/{domain}/ - WP-CLI is available as
wp— no PHP binary prefix needed - WordPress files at
$HOME/www/{domain}/, database connection fromwp-config.php
