-
-
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)/(\$(\&)?|\*|\"|\.|,|&|&?)/?$ | |
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 |
- 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.
- 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
- 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 - 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).
wow, that was............fast! Bueno!
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.
Be my guest @bgallagh3r - it was your script that got this going in the first place after all!
great @emirpprime! thanks!
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?
@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)
@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.
@emirpprime Nice job. I'm wondering, what are all you hardening ? Looks good, but I like to understand what I'm executing. :-)
Thanks.
@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.
Great script! Works on my Synology (XPEnology) DSM 5.2
Thank you both @emirpprime and @bgallagh3r
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 :) )
same here, wordpress is downloaded & installed but tables are missing in the DB
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!