Skip to content

Instantly share code, notes, and snippets.

@phlbnks
Forked from bgallagh3r/wp.sh
Last active June 6, 2022 09:07
Show Gist options
  • Save phlbnks/37ef1f355ec5a7ecbb8f to your computer and use it in GitHub Desktop.
Save phlbnks/37ef1f355ec5a7ecbb8f to your computer and use it in GitHub Desktop.
#!/bin/bash -e
clear
echo "============================================"
echo "WordPress Install Script"
echo "============================================"
echo "Do you need to setup new MySQL database? (y/n)"
read -e setupmysql
if [ "$setupmysql" == y ] ; then
echo "MySQL Admin User: "
read -e mysqluser
echo "MySQL Admin Password: "
read -s mysqlpass
echo "MySQL Host (Enter for default 'localhost'): "
read -e mysqlhost
mysqlhost=${mysqlhost:-localhost}
fi
echo "WP Database Name: "
read -e dbname
echo "WP Database User: "
read -e dbuser
echo "WP Database Password: "
read -s dbpass
echo "WP Database Table Prefix [numbers, letters, and underscores only] (Enter for default 'wp_'): "
read -e dbtable
dbtable=${dbtable:-wp_}
echo "Do basic hardening of wp-config and htaccess? (y/n)"
read -e harden
if [ "$harden" == y ] ; then
echo "Key for updating: "
read -e hardenkey
fi
echo "Last chance - sure you want to run the install? (y/n)"
read -e run
if [ "$run" == y ] ; then
if [ "$setupmysql" == y ] ; then
echo "============================================"
echo "Setting up the database."
echo "============================================"
#login to MySQL, add database, add user and grant permissions
dbsetup="create database $dbname;GRANT ALL PRIVILEGES ON $dbname.* TO $dbuser@$mysqlhost IDENTIFIED BY '$dbpass';FLUSH PRIVILEGES;"
mysql -u $mysqluser -p$mysqlpass -e "$dbsetup"
if [ $? != "0" ]; then
echo "============================================"
echo "[Error]: Database creation failed. Aborting."
echo "============================================"
exit 1
fi
fi
echo "============================================"
echo "Installing WordPress for you."
echo "============================================"
#download wordpress
echo "Downloading..."
curl -O https://wordpress.org/latest.tar.gz
#unzip wordpress
echo "Unpacking..."
tar -zxf latest.tar.gz
#move /wordpress/* files to this dir
echo "Moving..."
mv wordpress/* ./
echo "Configuring..."
#create wp config
mv wp-config-sample.php wp-config.php
#set database details with perl find and replace
perl -pi -e "s'database_name_here'"$dbname"'g" wp-config.php
perl -pi -e "s'username_here'"$dbuser"'g" wp-config.php
perl -pi -e "s'password_here'"$dbpass"'g" wp-config.php
perl -pi -e "s/\'wp_\'/\'$dbtable\'/g" wp-config.php
#set WP salts
perl -i -pe'
BEGIN {
@chars = ("a" .. "z", "A" .. "Z", 0 .. 9);
push @chars, split //, "!@#$%^&*()-_ []{}<>~\`+=,.;:/?|";
sub salt { join "", map $chars[ rand @chars ], 1 .. 64 }
}
s/put your unique phrase here/salt()/ge
' wp-config.php
#create uploads folder and set permissions
mkdir wp-content/uploads
chmod 775 wp-content/uploads
if [ "$harden" == y ] ; then
echo "============================================"
echo "Hardening."
echo "============================================"
#remove readme.html
rm readme.html
#debug extras
perl -pi -e "s/define\('WP_DEBUG', false\);/define('WP_DEBUG', false);\n\/** Useful extras *\/ \nif (WP_DEBUG) { \n\tdefine('WP_DEBUG_LOG', true); \n\tdefine('WP_DEBUG_DISPLAY', false); \n\t\@ini_set('display_errors',0);\n}/" wp-config.php
# key access to mods
find="/* That's all, stop editing! Happy blogging. */"
replace="/** Disallow theme and plugin editor in admin. Updates only with query var */\ndefine( 'DISALLOW_FILE_EDIT', true );\nif ( \\$\_REQUEST['key'] == '$hardenkey' ) {\n\tsetcookie( 'updatebypass', 1 );\n} elseif ( ! \\$\_COOKIE['updatebypass'] ) {\n\tdefine( 'DISALLOW_FILE_MODS', true );\n}\n\n/* That's all, stop editing! Happy blogging. */"
perl -pi -e "s{\Q$find\E}{$replace}" wp-config.php
#create root .htaccess with some useful starters
cat > .htaccess <<'EOL'
# Protect this file
<Files ~ "^\.ht">
Order allow,deny
Deny from all
</Files>
# Prevent directory listing
Options -Indexes
## BEGIN 6G Firewall from https://perishablepress.com/6g/
# 6G:[QUERY STRINGS]
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} (eval\() [NC,OR]
RewriteCond %{QUERY_STRING} (127\.0\.0\.1) [NC,OR]
RewriteCond %{QUERY_STRING} ([a-z0-9]{2000}) [NC,OR]
RewriteCond %{QUERY_STRING} (javascript:)(.*)(;) [NC,OR]
RewriteCond %{QUERY_STRING} (base64_encode)(.*)(\() [NC,OR]
RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC,OR]
RewriteCond %{QUERY_STRING} (<|%3C)(.*)script(.*)(>|%3) [NC,OR]
RewriteCond %{QUERY_STRING} (\\|\.\.\.|\.\./|~|`|<|>|\|) [NC,OR]
RewriteCond %{QUERY_STRING} (boot\.ini|etc/passwd|self/environ) [NC,OR]
RewriteCond %{QUERY_STRING} (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
RewriteCond %{QUERY_STRING} (\'|\")(.*)(drop|insert|md5|select|union) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REQUEST METHOD]
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_METHOD} ^(connect|debug|delete|move|put|trace|track) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REFERRERS]
<IfModule mod_rewrite.c>
RewriteCond %{HTTP_REFERER} ([a-z0-9]{2000}) [NC,OR]
RewriteCond %{HTTP_REFERER} (semalt.com|todaperfeita) [NC]
RewriteRule .* - [F]
</IfModule>
# 6G:[REQUEST STRINGS]
<IfModule mod_alias.c>
RedirectMatch 403 (?i)([a-z0-9]{2000})
RedirectMatch 403 (?i)(https?|ftp|php):/
RedirectMatch 403 (?i)(base64_encode)(.*)(\()
RedirectMatch 403 (?i)(=\\\'|=\\%27|/\\\'/?)\.
RedirectMatch 403 (?i)/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$
RedirectMatch 403 (?i)(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")
RedirectMatch 403 (?i)(~|`|<|>|:|;|,|%|\\|\s|\{|\}|\[|\]|\|)
RedirectMatch 403 (?i)/(=|\$&|_mm|cgi-|etc/passwd|muieblack)
RedirectMatch 403 (?i)(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)
RedirectMatch 403 (?i)\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$
RedirectMatch 403 (?i)/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php
</IfModule>
# 6G:[USER AGENTS]
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent ([a-z0-9]{2000}) bad_bot
SetEnvIfNoCase User-Agent (archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune) bad_bot
<limit GET POST PUT>
Order Allow,Deny
Allow from All
Deny from env=bad_bot
</limit>
</IfModule>
# 6G:[BAD IPS]
<Limit GET HEAD OPTIONS POST PUT>
Order Allow,Deny
Allow from All
# uncomment/edit/repeat next line to block IPs
# Deny from 123.456.789
</Limit>
## END 6G Firewall
## BEGIN htauth basic authentication
# STAGING
Require all denied
AuthType Basic
AuthUserFile /etc/apache2/wp-login
AuthName "Please Authenticate"
Require valid-user
# LIVE - prevent wp-login brute force attacks from causing load
#<FilesMatch "^(wp-login|xmlrpc)\.php$">
# AuthType Basic
# AuthUserFile /etc/apache2/wp-login
# AuthName "Please Authenticate"
# Require valid-user
#</FilesMatch>
# Exclude the file upload and WP CRON scripts from authentication
#<FilesMatch "(async-upload\.php|wp-cron\.php)$">
# Satisfy Any
# Order allow,deny
# Allow from all
# Deny from none
#</FilesMatch>
## END htauth
## BEGIN WP file protection
<Files wp-config.php>
order allow,deny
deny from all
</Files>
# WP includes directories
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
# note - comment out next line on multisite
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
## END WP file protection
# Prevent author enumeration
RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} author=\d
RewriteRule ^ /? [L,R=301]
EOL
#create .htaccess to protect uploads directory
cat > wp-content/uploads/.htaccess <<'EOL'
# Protect this file
<Files .htaccess>
Order Deny,Allow
Deny from All
</Files>
# whitelist file extensions to prevent executables being
# accessed if they get uploaded
order deny,allow
deny from all
<Files ~ ".(docx?|xlsx?|pptx?|txt|pdf|xml|css|jpe?g|png|gif)$">
allow from all
</Files>
EOL
fi
echo "Cleaning..."
#remove wordpress/ dir
rmdir wordpress
#remove zip file
rm latest.tar.gz
#remove bash script if it exists in this dir
[[ -f "wp.sh" ]] && rm "wp.sh"
echo "========================="
echo "[Success]: Installation is complete."
echo "========================="
else
exit
fi
@thewzrd
Copy link

thewzrd commented May 20, 2015

I haven't tested this yet, but it looks great if it all works out.

Just out of curiosity - forgive my ignorance here if I'm way off base on any of these questions/comments...

1.) Wouldn't the uploads directory get created automatically... and why would you want it chmod 777? I know there is some debate on the best practices of that, but it seems like 775 would be the ideal directory permissions.

2.) If I made something like this on my own, couldn't I just hardcode in the MySQL admin details, assuming that I made the code private? I'm looking to streamline this whole process as much as possible.

3.) Could you basically create a conditional code to name the db name and db user something like the first 5 letters of the directory that you're in (presumably the first five letters of the site name) and generate a random password... Thereby shaving off three more steps?

4.) Any easy way to email all of the details at completion?

Thanks for your time!

@phlbnks
Copy link
Author

phlbnks commented Jun 3, 2015

@thewzrd

  1. Creating the folder - depends on your server setup, I had included it as a "safe" option; but you right about the permissions. I'm writing an update that will also generate the Keys/Salts for wp-config, and I'll include this change then too.
  2. Yes totally. You could even set them up in a non-web accessible server directory, then hardcode the path into the script, and import it. That way if something fails in the script and it doesn't delete itself then there isnt' a copy of your details left in a public folder - see http://stackoverflow.com/questions/5228345/bash-script-how-to-reference-a-file-for-variables
  3. Yes - something like:
    dir=${PWD##/}
    short=${long:0:2}
    gives you a short name you can then re-use
    Similarly for passwords - something like:
    function random() {
    randomPass=$(cat /dev/urandom | env LC_CTYPE=C tr -dc "a-zA-Z0-9!@#$%^&
    ()-_ []{}<>~`+=,.;:/?|" | fold -w 16 | head -n 1)
    }
    random #this calls the function to generate password, now stored in $randomPass for use
  4. Nope. It can be done - but there might be a lot of setup, and it really varies from server to server. Might be easiest to save a log file instead somewhere you can remote access (behind some password protection perhaps).

@dixonge
Copy link

dixonge commented Jun 21, 2015

wow, that was............fast! Bueno!

@bgallagh3r
Copy link

Mind if I snipe the salt part for my script @emirpprime? Unfortunately I can't use the MySQL portion in my script because the server I manage uses WHM/cPanel to manage the databases.

@phlbnks
Copy link
Author

phlbnks commented Jul 27, 2015

Be my guest @bgallagh3r - it was your script that got this going in the first place after all!

@jonatasrenan
Copy link

great @emirpprime! thanks!

@maiorano84
Copy link

This is a fantastic script. One oddity I've noticed and this occurs on both your version and bgallagh3r/wp.sh:

It seems like the password field being written to wp-config.php is stripping out meta characters. So if I enter a password that looks like: wordp355u$3r, the dollar sign and the character following it are stripped out becoming:

wordp355ur

I've tried something like:

perl -pi -e "s/password_here/\\Q${dbpass}/g" wp-config.php

But that doesn't solve the issue, and my Perl isn't very good. Any thoughts?

@phlbnks
Copy link
Author

phlbnks commented Sep 16, 2015

@maiorano84 - I haven't had time to test it yet, but this might do it: perl -pi -e "s/password_here/\Q$dbpass\E/g" wp-config.php or perhaps perl -pi -e "s{password_here}{\Q$dbpass\E}g" wp-config.php (they should be identical in function but the latter is a bit easier to read)

@phlbnks
Copy link
Author

phlbnks commented Dec 9, 2015

@maiorano84 @bgallagh3r If you're still using this / something similar and getting the 'special characters in passwords' problems, the latest revision should have that fixed now. Apparently using quotes as sed delimiters prevents interpretation of special characters. I'm still not preventing breaking characters being used in the user input, but it's a lot better than it was.

@GabrielDancause
Copy link

@emirpprime Nice job. I'm wondering, what are all you hardening ? Looks good, but I like to understand what I'm executing. :-)

Thanks.

@phlbnks
Copy link
Author

phlbnks commented Jul 28, 2016

@GabrielDancause sorry i missed your comment. Hardening will:

  • disallow directly editing plugin / theme files in the admin area
  • only allow plugin and core updates to be done if you supply the key (?key=YOUR_KEY_FOR_UPDATING); e.g. go to sites.com/wp-admin/?key=YOUR_KEY_FOR_UPDATING - then you will be able to update plugins etc.
  • protect the .htaccess / .htpasswd and wp-config.php files
  • remove a couple of files that tell people what version of WordPress you are running
  • setup the 6G 'firewall' from Perishable Press
  • prevent author enumeration
  • stop people directly accessing the wp-includes directory
  • only allow people to access certain file extensions in /uploads

It definitely needs some configuration for your setup. But it would run ok if you use it straight off as long as you either create a htpasswd file called wp-login and put it at /etc/apache2/ on your server, or just comment out the Staging section of the htauth config in the .htaccess file in the wordpress root directory.

@bakmanhans
Copy link

Great script! Works on my Synology (XPEnology) DSM 5.2
Thank you both @emirpprime and @bgallagh3r

@ShahrozeAli
Copy link

I am using this script to create a web app ...
To download and install wordpress..
i am new to shell..
(Problem 1) one thing i don't understand why you set db pass and local pass to null..

at first it works fine but now
(Problem 2) it does not read harden key and won't download and install wordpress ..

i am using this script to access localhost and after few tries my phpmyadmin won't allow me to access it.
(Problem 3) i have to uninstall and reinstall my server..

(Problem 4) though i managed to download wordpress and create database but it does not install wordpress and do not create tables in database..

(ignore grammar mistakes -- thank You :) )

@ethosteam
Copy link

same here, wordpress is downloaded & installed but tables are missing in the DB

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