Skip to content

Instantly share code, notes, and snippets.

@petskratt
Last active March 12, 2024 23:21
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save petskratt/47618c879fa6ddbcb2b07d46bd1b21e5 to your computer and use it in GitHub Desktop.
Save petskratt/47618c879fa6ddbcb2b07d46bd1b21e5 to your computer and use it in GitHub Desktop.
Use wp-cli to clean up WordPress installs (force core & plugins reinstall, track changes in git allowing easy reverts etc)
#!/usr/bin/env bash
# for debug output, uncomment:
#set -x
function help {
echo "WordPress cleanup -v 0.5 2018-06-26 / peeter@zone.ee
Usage:
safe sensible cleanup: backup, rights, reinstall-core, cleanup, reinstall-plugins, init-htaccess,
harden-uploads, harden-wp-includes, harden-waf, list-oldplugins
forensic refresh versions: backup, rights, refresh-core, cleanup-files, refresh-plugins,
refresh-themes, init-htaccess, list-oldplugins
tempdb for old / crashed WPs - create temporary install with new new prefix, update wp and plugins
backup create full backup
backup-db database only
backup-files files only
reinstall reinstall core and plugins (but not themes)
reinstall-core forced reinstall of latest WP version (incl cleaning of wp-admin and wp-includes)
reinstall-plugins reinstall latest versions of plugins
reinstall-themes reinstall latest versions of themes
reinstall-temp for non-working installs: create temp db
refresh
refresh-core forced reinstall of current WP version
refresh-plugins reinstall current versions of plugins (for diffing etc)
refresh-themes reinstall current versions of inactive themes (for diffing etc)
cleanup all following cleanups:
cleanup-plugins remove unused plugins
cleanup-themes remove unused themes
cleanup-files clean php files from uploads, empty cache, upgrade folders
cleanup-misc transients
cleanup-git remove .git and configs
cleanup-https search-replace for http > https move (keeping same domain)
cleanup-sessions destroy login sessions for all users
harden
harden-uploads
harden-wp-content
harder-wp-includes
harden-waf
reset-passwords set all passwords to random
rights set file & folder rights to 644/755
find-files look for .php and <?php in uploads
find-files-here look for .php and <?php in current folder
list-oldplugins plugins not updted during past 15 minutest
list-sessions active login sessions and tokens (incl. expired) for users
init-git initialise git, do first commit
init-key create keypair for ssh
"
}
function init-state {
if [ ! -f wp-config.php ];
then
echo "wp-config.php not found - please run in the root folder of WordPress install!"
exit 1
fi
# make sure we have wp-cli at hand when needed...
if ! type wp &> /dev/null;
then
echo Installing wp-cli...
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
mkdir -p $HOME/bin
mv wp-cli.phar $HOME/bin/wp
fi
# were we successful? the rest of the world depends on it...
if ! type wp &> /dev/null;
then
echo "Apparently installation did not succeed (maybe $HOME/bin is not in PATH?), exiting..."
exit 1
fi
}
function init-wpurl {
if [ "$WPURL" == "" ]; then
# get site URL for backup names etc
SITEURL=$(wp option get siteurl | tr -d '[:space:]')
if [[ $SITEURL != http* ]]; then
echo "Bad response for wp option get siteurl, possibly unsupported WP or PHP version, quitting ($SITEURL)"
exit 1
fi
WPURL=$(echo "$SITEURL" | sed 's~http[s]*://~~g')
WPURL_FILESAFE=$(echo "$WPURL" | tr ./ --)
fi
}
function init-uploads {
if [ "$UPLOADS" == "" ]; then
# get site URL for backup names etc
UPLOADS=$(wp eval "\$d = wp_upload_dir(); echo \$d['basedir'] . PHP_EOL;" | tr -d '[:space:]')
fi
if [ "$UPLOADS" == "" ]; then
echo "Unable to get correct wp_upload_dir, assuming wp-content/uploads"
UPLOADS="wp-content/uploads"
fi
}
function init-git {
if [ ! -d ".git" ]; then
init-git-settings
init-git-settings-customuploads
init-git-create
else
echo "Git repo already present, did not initialize or configure."
fi
}
function init-git-force {
if [ ! -d ".git" ]; then
init-git-settings
init-git-create
else
echo "Git repo already present, did not initialize or configure."
fi
}
function init-git-create {
git init
git config user.name "WP clinup"
git config user.email "clinup@local.dev"
# ignore chmod differences - usually caused by copying code
git config core.fileMode false
git add .
git commit -q -m "Initial commit"
}
function cleanup-git {
rm -rf .git
rm -f .gitignore
rm -f .gitattributes
}
function init-git-settings {
echo ".DS_Store
.sass-cache
wp-content/uploads/**
wp-content/upgrade/**
wp-content/backup-db/**
wp-content/cache/**
wp-content/w3tc/**
wp-content/cache/supercache/**
sitemap.xml
sitemap.xml.gz
" > .gitignore
if [ -f .exclude ]; then
LINES=$(cat .exclude)
for LINE in $LINES ; do
echo "$LINE/**" >> .gitignore
done
fi
# ignore win/*x EOL consipiracy
echo "*.php text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.js text eol=lf
*.md text eol=lf
*.txt text eol=lf
*.svg text eol=lf
*.xml text eol=lf
*.po text eol=lf
" > .gitattributes
}
function init-git-settings-customuploads {
init-uploads
UPLOADS_RELATIVE=${UPLOADS#$(pwd)/}
echo "$UPLOADS_RELATIVE/**
" >> .gitignore
}
function init-key {
if [ ! -f $HOME/.ssh/id_rsa ]; then
init-wpurl
ssh-keygen -t rsa -b 4096 -C "cleanup@$WPURL" -f $HOME/.ssh/id_rsa -q -N ""
echo "Created new key, public key is:"
cat $HOME/.ssh/id_rsa.pub
else
echo "Found existing key, public key is:"
cat $HOME/.ssh/id_rsa.pub
fi
}
function init-harden {
HARDEN="Options -ExecCGI
RemoveType .php .php3 .phtml .inc
RemoveHandler .php .php3 .phtml .inc
<FilesMatch \"\.(?i:php|php3|phtml|inc)($|\.)\">
Require all denied
</FilesMatch>
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
"
HARDEN_INCLUDES="Options -ExecCGI
RemoveType .php3 .phtml .inc
RemoveHandler .php3 .phtml .inc
<FilesMatch \"\.(?i:php|php3|phtml|inc)($|\.)\">
Require all denied
</FilesMatch>
<Files wp-tinymce.php>
Require all granted
</Files>
<Files ms-files.php>
Require all granted
</Files>
"
INDEXHTML="<?php
// Silence is golden.
"
}
function init-htaccess {
echo "# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
" > .htaccess
# just-in-case subfolders have bad htaccess or index.php:
INDEX="<?php
// Silence is golden.
"
echo $INDEX > wp-content/index.php
echo $INDEX > wp-content/plugins/index.php
echo $INDEX > wp-content/themes/index.php
rm -f wp-content/.htaccess
rm -f wp-content/plugins/.htaccess
rm -f wp-content/themes/.htaccess
}
function commit-if {
if [ -d ".git" ]; then
git add .
git commit -m "$1"
fi
}
function backup-db {
init-wpurl
echo "Creating DB backup..."
wp db export - | gzip > ../db-backup_${WPURL_FILESAFE}_`date +%d-%m-%Y_%H-%M`.sql.gz
}
function backup-files {
init-wpurl
init-uploads
UPLOADS_RELATIVE=${UPLOADS#$(pwd)/}
echo "Creating site files backup, EXCLUDING uploads ($UPLOADS_RELATIVE), cache etc..."
if [ ! -f .exclude ]; then
tar --exclude="$UPLOADS_RELATIVE" --exclude="wp-content/uploads" --exclude="wp-content/cache" --exclude=".git" -czf ../site-backup_${WPURL_FILESAFE}_`date +%d-%m-%Y_%H-%M`.tar.gz .
else
tar --exclude="$UPLOADS_RELATIVE" --exclude="wp-content/uploads" --exclude="wp-content/cache" --exclude=".git" -X .exclude -czf ../site-backup_${WPURL_FILESAFE}_`date +%d-%m-%Y_%H-%M`.tar.gz .
fi
}
function backup {
backup-db
backup-files
}
function rights {
# set file and folder rights recursively, but not for current script
echo "Normalizing file and folder rights..."
find . -type d -print0 | xargs -0 chmod 755
find . -type f -not -path "$0" -print0 | xargs -0 chmod 644
commit-if "Normalized rights"
}
function reinstall-core {
# remove WP admin and includes, re-install core
echo "Re-installing WP..."
rm -rf wp-admin/ wp-includes/
wp core download --version=latest --locale=en_US --force
# db-update - just in case (add --network for multisite)
wp core update-db
# language updates - just in case
wp core language update
# good bye, dolly...
rm -f wp-content/plugins/hello.php
rm -rf wp-content/plugins/hello-dolly
commit-if "Re-installed latest version of WordPress"
}
function refresh-core {
# remove WP admin and includes, re-install core
echo "Re-installing WP..."
VERSION=$(wp core version)
rm -rf wp-admin/ wp-includes/
wp core download --version=$VERSION --locale=en_US --force
# good bye, dolly...
rm -f wp-content/plugins/hello.php
rm -rf wp-content/plugins/hello-dolly
commit-if "Re-installed existing version of WordPress"
}
function list-oldplugins {
# list plugins that have most probably not been reinstalled by this script
echo "Plugins that were NOT installed during past 15 minutes:"
find wp-content/plugins -maxdepth 1 -type d -mmin +15 -exec basename {} \;
}
function reinstall-plugins {
# reinstall all wp plugins that respect standards
echo "Re-installing plugins (latest version)..."
plugins=$(wp plugin list --field=name)
for plugin in $plugins; do
wp plugin install $plugin --force
commit-if "Re-installed plugin: $plugin"
done
# for some reason premium plugins might not update with reinstall
wp plugin update --all
wp rewrite flush
commit-if "Re-installed plugins with latest versions"
}
function refresh-plugins {
# reinstall all wp plugins that respect standards
echo "Re-installing plugins (existing version)..."
plugins=$(wp plugin list --fields=name,version --format=csv | tail -n +2)
for plugin in $plugins; do
wp plugin install $(cut -d, -f1 <<< $plugin) --version=$(cut -d, -f2 <<< $plugin) --force
commit-if "Refreshed plugin: $plugin"
done
wp rewrite flush
}
function reinstall-themes {
# reinstall all wp plugins that respect standards
echo "Re-installing themes (latest version)..."
themes=$(wp theme list --field=name)
for theme in $themes; do
wp theme install $theme --force
commit-if "Re-installed theme: $theme"
done
}
function refresh-themes {
# reinstall inactive themes
echo "Re-installing themes (existing version)..."
themes=$(wp theme list --status=inactive --fields=name,version --format=csv | tail -n +2)
for theme in $themes; do
wp theme install $(cut -d, -f1 <<< $theme) --version=$(cut -d, -f2 <<< $theme) --force
commit-if "Refreshed theme: $theme"
done
}
function cleanup-plugins {
# remove unused themes
echo "Removing unused plugins..."
wp plugin delete $(wp plugin list --field=name --status=inactive)
commit-if "Removed unused plugins"
}
function cleanup-themes {
# remove unused themes
echo "Removing unused themes..."
wp theme delete $(wp theme list --field=name --status=inactive)
commit-if "Removed unused themes"
}
function cleanup-files {
init-uploads
# clean upgrade & cache folder
echo "Cleaning caches..."
rm -rf wp-content/upgrade/*
rm -rf wp-content/cache/*
rm -rf wp-content/w3tc/*
# clean possible php-executabe files from uploads
echo "Cleaning uploads..."
find $UPLOADS \( -name '*.php' -o -name '*.phtlm' -o -name '*.inc' -o -name '*.php3' \) -type f -delete
find $UPLOADS -type d -empty -delete
commit-if "Cleaned wp-content"
}
function find-files {
init-uploads
echo "Searching by extensions in uploads..."
find $UPLOADS \( -name '*.php' -o -name '*.phtlm' -o -name '*.inc' -o -name '*.php3' \) -type f
echo "Grepping by <?php in uploads..."
grep -rnw $UPLOADS -e "<?php"
}
function find-files-here {
echo "Searching by extensions in current folder..."
find . \( -name '*.php' -o -name '*.phtlm' -o -name '*.inc' -o -name '*.php3' \) -type f
echo "Grepping by <?php in in current folder..."
grep -rnw . -e "<?php"
}
function cleanup-misc {
wp transient delete-all
}
function reset-passwords {
NEW_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
wp user update $(wp user list --field=id) --user_pass=$NEW_PASSWORD
echo "All passwords set to $NEW_PASSWORD"
}
function list-sessions {
users=$(wp user list --field=id | sort)
for user in $users; do
echo "Active sessions for user $user:"
wp user session list $user
echo "session_tokens (active and expired) for user $user:"
wp user meta get $user session_tokens
done
}
function cleanup-sessions {
users=$(wp user list --field=id | sort)
for user in $users; do
echo "Destrotying sessions for user $user:"
wp user session destroy $user --all
done
}
function harden-uploads {
init-harden
init-uploads
echo "Adding .htaccess to uploads..."
echo "$HARDEN" > $UPLOADS/.htaccess
commit-if "Added .htaccess to uploads"
}
function unharden-uploads {
init-uploads
echo "Removing.htaccess from uploads..."
rm -f $UPLOADS/.htaccess
commit-if "Removed .htaccess from uploads"
}
function harden-wp-content {
init-harden
echo "Adding .htaccess to wp-content..."
echo "$HARDEN" > wp-content/.htaccess
commit-if "Added .htaccess to wp-content"
}
function unharden-wp-content {
echo "Removing.htaccess from wp-content..."
rm -f wp-content/.htaccess
commit-if "Removed .htaccess from wp-content"
}
function harden-wp-includes {
init-harden
echo "Adding .htaccess to wp-includes..."
echo "$HARDEN_INCLUDES" > wp-includes/.htaccess
commit-if "Added .htaccess to wp-includes"
}
function unharden-wp-includes {
echo "Removing.htaccess from wp-includes..."
rm -f wp-includes/.htaccess
commit-if "Removed .htaccess from wp-content"
}
function unharden-waf {
if grep -q "# 6G FIREWALL/BLACKLIST" .htaccess; then
sed -i.bak '/# 6G FIREWALL/,/# 6G END/d' .htaccess
rm -f .htaccess.bak
fi
}
function harden-waf {
# so it can be used to refresh rules
unharden-waf
init-harden
HARDEN_WAF=$(curl https://gist.githubusercontent.com/petskratt/17fdb56c75800fc38797a7c5bd1d1127/raw/.htaccess)
HTACCESS=$(cat .htaccess)
echo "$HARDEN_WAF
$HTACCESS" > .htaccess
commit-if "Added 6G blacklist to .htaccess"
}
function cleanup {
cleanup-files
if ! $(wp core is-installed --network); then
# network install needs more complex approach...
cleanup-plugins
cleanup-themes
fi
cleanup-misc
}
function cleanup-https {
init-wpurl
wp search-replace http://$WPURL https://$WPURL --skip-columns=guid
wp rewrite flush
}
function tempdb {
# todo - find prefix from conf, ensure it is safe to use without user intervention
if [ -z "$1" ]
then
echo "Please supply current prefix as argument, perhaps:"
grep '$table_prefix' wp-config.php
exit 1
fi
cp wp-config.php wp-config_clinup_temp.php
echo "Original prefix:"
grep '$table_prefix' wp-config.php
sed -i.bak s/$1/clinup_temp_/g wp-config.php
rm -f wp-config.php.bak
echo "Temporary prefix:"
grep '$table_prefix' wp-config.php
read -n1 -r -p 'Does that seem OK as temp prefix? Press Y to continue:' key
if [ "$key" == "Y" ]; then
echo 'OK, trying to reinstall...'
else
echo 'Cancelling & restoring wp-config.php...'
rm -f wp-config.php
mv wp-config_clinup_temp.php wp-config.php
exit 1
fi
rights
# prefix uueks
rm -rf wp-admin/ wp-includes/
wp core download --version=latest --locale=en_US --force
wp core install --url=clinup.local --title=Temporary --admin_user=clinup --admin_email=clinup@clinup.local --skip-email
# language updates - just in case
wp core language update
# good bye, dolly...
rm -f wp-content/plugins/hello.php
rm -rf wp-content/plugins/hello-dolly
reinstall-plugins
init-htaccess
~/bin/wp db query "SET GROUP_CONCAT_MAX_LEN=10000; SET @tbls = (SELECT GROUP_CONCAT(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_NAME LIKE 'clinup_temp_%'); SET @delStmt = CONCAT('DROP TABLE ', @tbls); SELECT @delStmt; PREPARE stmt FROM @delStmt; EXECUTE stmt; DEALLOCATE PREPARE stmt;"
# restore wp-config
rm -f wp-config.php
mv wp-config_clinup_temp.php wp-config.php
list-oldplugins
}
function safe {
backup
rights
reinstall-core
cleanup
reinstall-plugins
init-htaccess
harden-uploads
harden-wp-includes
harden-waf
list-oldplugins
}
function forensic {
backup
rights
refresh-core
cleanup-files
refresh-plugins
refresh-themes
init-htaccess
list-oldplugins
}
if [ "$1" != "help" ] && [ "$1" != "find-files-here" ]; then
init-state
fi
$1 $2
@wurdz
Copy link

wurdz commented Nov 30, 2020

Hi Peeter
This script looks incredible. Do you see any issues with > WP 5.5?
Was looking for a revision tracking option using wp-cli and having control without plugins like wp reset
thanks

@petskratt
Copy link
Author

We have been using it (without major modifications, apparently) for years - and haven't seen any problems with WP version upgrades, yet. Not tried with 5.6 dev yet - if wp-cli works it should work.

I have actually moved it over to https://github.com/zone-eu/clinup/ - should find a way to redirect this gist there.

@wurdz
Copy link

wurdz commented Nov 30, 2020

Excellent. Thanks for the reply!

@danielgutierrezcolorado
Copy link

danielgutierrezcolorado commented Dec 2, 2020

This looks great, but when I try to run it as a bash script, it says
+ '[' '' '!=' help ']'
+ '[' '' '!=' find-files-here ']'
+ init-state
+ '[' '!' -f wp-config.php ']'
+ type wp
+ type wp

What can be wrong?

@petskratt
Copy link
Author

this seems like debug output when you allow set -x - do you have wp-cli available in shelle as wp? this part should check for existence of wp-cli and try to install it. does it show clinup help? dows it run something like clinup list-sessions? while written to run happily on different configurations I mostly run in on our (zone.ee) hosting platform and have not dealt with possible compatibility questions since initial version.

@danielgutierrezcolorado
Copy link

thx a lot for your answer petskratt! this is going to help me to clean a site. Yes I do have wp-cli, I uncomment the set -x to see why the .sh is not running :( I'm under ubuntu, fwiw. clinup help and list-sessions work fine :( thx!

@danielgutierrezcolorado
Copy link

dumb me I was expecting to have some kind of menu, it should work with parameters, plz forgive me

@foldesistudio
Copy link

Hi dear Petskratt,
Would you like to update your project?
There is available G7 firewall (at https://perishablepress.com/7g-firewall/#download).

Best regards

@petskratt
Copy link
Author

Most recent version is at https://github.com/zone-eu/clinup/blob/master/clinup and that's apparently the same as in this gist - but yes, perhaps switching to Jeff's 7G is good idea. I think the only change I made to 6G was removing one HTTP_USER_AGENT that was used by Estonian National Library to archive websites... if only I could recall what it was...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment