WordPress PHP-FPM and Apache (with variables)

Sample setup for Apache virtual host and PHP-FPM pool with variables instead hard coded user details.
This could be handy if your are scripting your deploy (e.g. with Ansible).

In our example we are using the following:

Domain: example.com
SFTP user: example.com
Document root: /var/www/vhosts/example.com/httpdocs/

<VirtualHost *:80>
        ServerName example.com
        ServerAlias www.example.com

        DocumentRoot /var/www/vhosts/example.com/httpdocs
        <Directory /var/www/vhosts/example.com/httpdocs>
                require all granted
                Options Indexes
                AllowOverride All
        </Directory>

        ErrorLog "logs/example.com-error_log"
        CustomLog "logs/example.com-access_log" combined env=!forwarded
        CustomLog "logs/example.com-access_log" proxy env=forwarded

	# Configure proxy connector
	<Proxy "unix:/dev/shm/example.com-php5-fpm.sock|fcgi://php-fpm">
    		# we must declare a parameter in here (doesn't matter which) or it'll not register the proxy ahead of time
	    	ProxySet disablereuse=off
	</Proxy>
	#
	# Redirect to the proxy connector
	<FilesMatch \.php$>
    		SetHandler proxy:fcgi://php-fpm
	</FilesMatch>


        <Directory /var/www/vhosts/example.com/httpdocs/wp-content/uploads>
            # Important for security, prevents someone from
            # uploading a malicious .htaccess
            AllowOverride None

            SetHandler none
            SetHandler default-handler

            Options -ExecCGI
            php_flag engine off
            RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
            <Files *>
                    AllowOverride None

                    SetHandler none
                    SetHandler default-handler

                    Options -ExecCGI
                    php_flag engine off
                    RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo
            </Files>
        </Directory>
</VirtualHost>

Create a file called example.com.conf in /etc/php-fpm/pool/

[example.com]

listen = /dev/shm/$pool-php5-fpm.sock
user = $pool
group = $pool
listen.owner = $pool
listen.group = apache
listen.mode = 0666

pm = dynamic
pm.max_children = 35
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 25
slowlog = /var/log/php-fpm/$pool-slow.log
php_admin_value[error_log] = /var/log/php-fpm/$pool-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session/$pool

Apache 2.4 + PHP-FPM Ubuntu 14.04

apt-get install php5-mysql php5-fpm php5-gd php5-cli apache2-mpm-worker libapache2-mod-fastcgi

Create a php-fpm pool /etc/php5/fpm/pool.d/$USER.cnf

[$USER]
user = $USER
group = www-data
listen = 127.0.0.1:9000
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 100
pm.start_servers = 35
pm.min_spare_servers = 15
pm.max_spare_servers = 35
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session/$USER
chdir = /

In Vhost:

ProxyPassMatch ^/(.*\.php(/.*)?)$ fcgi://127.0.0.1:9000/var/www/vhosts/MYDOMAIN.COM/public_html/$1

Enable/disable modules and restart services

a2dismod mpm_prefork php5 && a2enmod actions fastcgi alias mpm_event proxy_fcgi proxy

service php5-fpm restart
service apache2 restart

Don’t forget to enable “index.php” in your DirectoryIndex directive, if using PHP-FPM! Also, remove any PHP directives from Apache, and configure in PHP-FPM instead.

WordPress Useful commands

Reset Admin Password
UPDATE wp_users SET user_pass=MD5(‘newpassword123’) WHERE ID = 1;

Create New Admin account
mysql> INSERT INTO `wp_users` (`user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_status`, `display_name`) VALUES (‘username’, MD5(‘password’), ‘friendly-name’, ‘[email protected]’, ‘http://example.com’, ‘0’, ‘Your Name’);
mysql> SELECT LAST_INSERT_ID() INTO @userid;INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALUES (NULL, @userid, ‘wp_capabilities’, ‘a:1:{s:13:”administrator”;s:1:”1″;}’), (NULL, @userid, ‘wp_user_level’, ’10’);

Show error in case white screen appears
Try adding this line to wp-config.php to see the errors on the page:
define( ‘WP_DEBUG’, true );

Change the site URL
mysql> SELECT * FROM wp_options WHERE option_name = ‘siteurl’ OR option_name = ‘home’ ;
mysql> UPDATE wp_options SET option_value = ‘http://staging.walacea.com’ WHERE option_name = ‘siteurl’ OR option_name = ‘home’ ;

Disable all plugins
mysql> UPDATE wp_options SET option_value = ‘a:0:{}’ WHERE option_name = ‘active_plugins’;

Show users and Privileges
mysql> SELECT user_login,user_registered,meta_value FROM wp_users INNER JOIN wp_usermeta ON wp_users.id = wp_usermeta.user_id and meta_key = ‘wp_capabilities’;
+—————+———————+———————————+
| user_login    | user_registered     | meta_value                      |
+—————+———————+———————————+
| administrator | 2013-12-21 10:36:30 | a:1:{s:13:”administrator”;b:1;} |
| author        | 2014-11-25 15:50:34 | a:1:{s:6:”author”;b:1;}         |
| editor        | 2014-11-25 15:51:18 | a:1:{s:6:”editor”;b:1;}         |
| contributor   | 2014-11-25 15:51:48 | a:1:{s:11:”contributor”;b:1;}   |
| subscriber    | 2014-11-25 15:52:11 | a:1:{s:10:”subscriber”;b:1;}    |
+—————+———————+———————————+
5 rows in set (0.01 sec)

Update theme to Twenty Fourteen
mysql> UPDATE wp_options SET option_value = ‘twentyfourteen’ WHERE option_name = ‘template’ OR option_name = ‘stylesheet’;
mysql> UPDATE wp_options SET option_value = ‘Twenty Fourteen’ WHERE option_name = ‘current_theme’;

Administration Over SSL
Add the below lines to the wp-config.php file above the ‘/* That’s all, stop editing! Happy blogging. */’ line
define(‘FORCE_SSL_ADMIN’, true);
define(‘FORCE_SSL_LOGIN’, true);
This ensures the login AND the administration is done over SSL

You could also use the below htaccess:
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /(.*)\ HTTP/ [NC]
RewriteCond %{HTTPS} !=on [NC]
RewriteRule ^/?(wp-admin/|wp-login\.php) https://mysite.com%{REQUEST_URI}%{QUERY_STRING} [R=301,QSA,L]

Find out how many SQL queries are executed every time a page is loaded.
Add the below to one of the theme files, I usually add to footer.php
if ( current_user_can( ‘manage_options’ ) ) {
echo $wpdb->num_queries . ” SQL queries performed.”;
} else {
// Uncomment the below line to show SQL queries to everybody
// echo $wpdb->num_queries . ” SQL queries performed.”;
}

e.g. on my site when I’m logged in…

Here are some configuration parameters you can add to your wp-config.php file for FTP.
define(‘FS_METHOD’, ‘direct’);
/*
forces the filesystem method. It should only be “direct”, “ssh2”, “ftpext”, or “ftpsockets”. Generally, you should only change this if you are experiencing update problems. If you change it and it doesn’t help, change it back/remove it. Under most circumstances, setting it to ‘ftpsockets’ will work if the automatically chosen method does not.

(Primary Preference) “direct” forces it to use Direct File I/O requests from within PHP, this is fraught with opening up security issues on poorly configured hosts, This is chosen automatically when appropriate.
(Secondary Preference) “ssh2” is to force the usage of the SSH PHP Extension if installed
(3rd Preference) “ftpext” is to force the usage of the FTP PHP Extension for FTP Access, and finally
(4th Preference) “ftpsockets” utilises the PHP Sockets Class for FTP Access.
*/
define(‘FTP_BASE’, ‘/var/www/vhosts/example.com/httpdocs/’); // is the full path to the “base”(ABSPATH) folder of the WordPress installation.
define(‘FTP_CONTENT_DIR’, ‘/var/www/vhosts/example.com/httpdocs/wp-content/’); // is the full path to the wp-content folder of the WordPress installation.
define(‘FTP_PLUGIN_DIR ‘, ‘/var/www/vhosts/example.com/httpdocs/plugins/’); // is the full path to the plugins folder of the WordPress installation.
define(‘FTP_PUBKEY’, ‘/var/www/vhosts/example.com/httpdocs/.ssh/id_rsa.pub’); // is the full path to your SSH public key.
define(‘FTP_PRIKEY’, ‘/var/www/vhosts/example.com/httpdocs/.ssh/id_rsa’); // is the full path to your SSH private key.
define(‘FTP_USER’, ‘FTPusername’); // is the FTP username
define(‘FTP_PASS’, ‘FTPpassword’); // is the password for the FTP User
define(‘FTP_HOST’, ‘localhost’); // FTP Host – usually localhost.
define(‘FTP_SSL’, false); // This is for “Secure FTP” not for  SFTP.

Apache 2.2 + PHP-FPM (Centos 6)

WITHOUT disabling MOD_PHP in Apache

Compile module:

yum -y install httpd-devel gcc
mkdir /home/rack/fastcgi
cd /home/rack/fastcgi
wget https://github.com/whyneus/magneto-ponies/raw/master/mod_fastcgi-SNAP-0910052141.tar.gz
tar -zxf mod_fastcgi*
cd mod_fastcgi-*
make -f Makefile.AP2 top_dir=/usr/lib64/httpd
cp .libs/mod_fastcgi.so /usr/lib64/httpd/modules/

Enable the module:

echo "LoadModule fastcgi_module /usr/lib64/httpd/modules/mod_fastcgi.so" >> /etc/httpd/conf.d/fastcgi.conf

Install php-fpm and create pools like this:

[$USER]
listen = /dev/shm/$USER-php5-fpm.sock
user = $USER
group = $USER
listen.owner = $USER
listen.group = apache
listen.mode = 0666
pm = dynamic
pm.max_children = 35
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 25
slowlog = /var/log/php-fpm/$USER-slow.log
php_admin_value[error_log] = /var/log/php-fpm/$USER-error.log
php_admin_flag[log_errors] = on
php_value[session.save_handler] = files
php_value[session.save_path] = /var/lib/php/session/$USER/

NOTE: Make sure the path /var/lib/php/session/$USER/ exists and its owned by $USER!

Add this in the VHOST configuration (before the end of </VirtualHost>) )

FastCGIExternalServer /dev/shm/$USER-php.fcgi -socket /dev/shm/$USER-php5-fpm.sock -flush -idle-timeout 1800
AddHandler php-fpm .php
Action php-fpm /php.fcgi
Alias /php.fcgi /dev/shm/$USER-php.fcgi
DirectoryIndex index.php
<FilesMatch "\.php$">
    SetHandler php-fpm
</FilesMatch>

PHP Sessions in Memcache

Putting PHP session files in a single pool can be done using memcached which has the benefit of being faster to look up and no longer relying on a slow filesystem to save session files to (memcached stores its data in memory) but you need to have your developers check that your code won’t have any issues using this over session files.

Install the package and set the configuration file:

PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="128"
OPTIONS="-l 127.0.0.1"

Make sure that memcache php plugin is there – or install it

Change this in php.ini

session.save_handler = memcached
session.save_path = "tcp://127.0.0.1:11211?persistent=1&weight=1&timeout=1&retry_interval=15"

Check memcache stats

echo "stats" | nc 127.0.0.1 11211
echo "stats" | nc -w 1 127.0.0.1 11211 | awk '$2 == "bytes" { print $2" "$3 }'

Letsencrypt Free SSL Certificate

git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
./letsencrypt-auto

If this command doesn’t work, you can try the following manual way

./letsencrypt-auto certonly --email [email protected] --webroot -w /var/www/vhosts/yourdomain.com -d yourdomain.com -d www.yourdomain.com

You can add multiple domains under the same certificate

./letsencrypt-auto certonly --email [email protected] --webroot -w /var/www/vhosts/yourdomain.com -d yourdomain.com -d www.yourdomain.com -w /var/www/vhosts/yourdomain2.com -d yourdomain2.com -d www.yourdomain2.com

Update your Apache configuration to use the new certificate:

SSLEngine on
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/yourdomain.com/chain.pem

Check domain:
https://crt.sh/?q=%25yourdomain.com

Renew the certificate with a script set in a cron (set every 2 months – certificate expires every 3)

# run on the 1st of Odd Months at midnight | Renew certificates
0 0 1 1-11/2 * root /root/letsencrypt/renew.sh >/dev/null 2>&1

renew.sh script:

#!/bin/bash

[email protected]

LOG=/var/log/letsencrypt/renew.log
SENDMAIL=/usr/sbin/sendmail
CMD="renew"

/root/letsencrypt/letsencrypt-auto $CMD > $LOG 2>&1

if [ $? -eq 0 ] ; then
    service httpd graceful
else
    echo -e "Subject: [letsencrypt] SSL automatic renewal FAILED\n$(cat $LOG)" | $SENDMAIL $MAIL
    exit 1
fi

Sources:
https://letsencrypt.org/getting-started/
https://www.a2hosting.com/kb/security/ssl/securing-your-site-with-a-lets-encrypt-ssl-certificate

BASH conditional expressions

-a file

True if file exists.

-b file

True if file exists and is a block special file.

-c file

True if file exists and is a character special file.

-d file

True if file exists and is a directory.

-e file

True if file exists.

-f file

True if file exists and is a regular file.

-g file

True if file exists and its set-group-id bit is set.

-h file

True if file exists and is a symbolic link.

-k file

True if file exists and its “sticky” bit is set.

-p file

True if file exists and is a named pipe (FIFO).

-r file

True if file exists and is readable.

-s file

True if file exists and has a size greater than zero.

-t fd

True if file descriptor fd is open and refers to a terminal.

-u file

True if file exists and its set-user-id bit is set.

-w file

True if file exists and is writable.

-x file

True if file exists and is executable.

-G file

True if file exists and is owned by the effective group id.

-L file

True if file exists and is a symbolic link.

-N file

True if file exists and has been modified since it was last read.

-O file

True if file exists and is owned by the effective user id.

-S file

True if file exists and is a socket.

file1 -ef file2

True if file1 and file2 refer to the same device and inode numbers.

file1 -nt file2

True if file1 is newer (according to modification date) than file2, or if file1 exists and file2 does not.

file1 -ot file2

True if file1 is older than file2, or if file2 exists and file1 does not.

-o optname

True if the shell option optname is enabled. The list of options appears in the description of the -o option to the set builtin (see The Set Builtin).

-v varname

True if the shell variable varname is set (has been assigned a value).

-R varname

True if the shell variable varname is set and is a name reference.

-z string

True if the length of string is zero.

-n stringstring

True if the length of string is non-zero.

string1 == string2string1 = string2

True if the strings are equal. When used with the [[ command, this performs pattern matching as described above (see Conditional Constructs).

=’ should be used with the test command for POSIX conformance.

string1 != string2

True if the strings are not equal.

string1 < string2

True if string1 sorts before string2 lexicographically.

string1 > string2

True if string1 sorts after string2 lexicographically.

arg1 OP arg2

OP is one of ‘-eq’, ‘-ne’, ‘-lt’, ‘-le’, ‘-gt’, or ‘-ge’. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively.  Arg1 and arg2 may be positive or negative integers.

 

 

Source http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html

Create an encrypted LUKS sparse file

For an updated version of this article, check this post!

I’m going to create a 2.7TB of file (sparse file) on my 3TB disk.
This file is a ‘thin provisionig‘ file, which means that it will expand only when/if used. FYI – it won’t shrink after usage. So a 2.7TB sparse file, once created, will be seen as 2.7T but it’s actually using a very little space (almost not noticeable). It will grow using it.
I will also encrypt it using cryptosetup LUKS format, to protect the content.

Why a sparse file?
Well, it’s practical, and it’s very handy if you want to move your files from a disk which is quite full already. In fact, I have my backups on this USB drive, EXT4 formatted. A 3TB drive half full.
A sparse file can be actually bigger than the available space on the disk. Of course, if you fill it up, it will error out. But while you want to move files, it’s… your saviour! 🙂

Using my example, I have 1.2TB used on this 3TB disk. I want to move all this data in an encrypted container that can potentially use the whole disk. So? Sparse file is the solution!
Theoretically I could also have had like… 2.2/2.5TB of data (on a 2.7TB available disk). As long as you free up space moving the files, things should work 🙂

First of all… if you haven’t done it yet, just install the package:

# apt-get install cryptsetup

Create the sparsefile

Let’s create a 2.7TB sparse file in /3TB.
[/3TB is the mount point of my /dev/sdb1 USB device]

# dd of=/3TB/file_container.img bs=1 count=0 seek=2700G

Create an encrypted LUKS container

We mount the sparse file just created and set the encrypted password. NOTE: you can change/add/remove this password (key) [later on explained how 😉 ]

# losetup /dev/loop0 /3TB/file_container.img 
# cryptsetup -y luksFormat /dev/loop0 
WARNING! ======== This will overwrite data on /dev/loop0 irrevocably. 
Are you sure? (Type uppercase yes): YES 
Enter passphrase: 
Verify passphrase: 
#

Open the Vault

Now, it’s time to ‘turn on’ this encrypted volume device and check that all went well

# cryptsetup luksOpen /dev/loop0 myarchive
Enter passphrase for /3TB/file_container.img:

# cryptsetup status myarchive
/dev/mapper/myarchive is active.
type: LUKS1
cipher: aes-xts-plain64
keysize: 256 bits
device: /dev/loop0
loop: /file_container.img
offset: 4096 sectors
size: 5662306304 sectors
mode: read/write

And of course, we need to format the device. I’ll use EXT4.

# mkfs.ext4 -L cryptarchive /dev/mapper/myarchive
mke2fs 1.42.12 (29-Aug-2014)
Creating filesystem with 707788288 4k blocks and 176947200 inodes
[...]
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

At this point, you can mount this device /dev/mapper/myarchive as usual:

# mkdir /cryptoarchive
# mount -t ext4 /dev/mapper/myarchive /cryptoarchive

You are now able to use your encrypted sparse file! All files can be now copied/moved into/cryptoarchive

Close the Vault

To unmount the vault, you need to follow these steps. This is important when you turn off your server!

# umount /cryptoarchive
# cryptsetup luksClose /dev/mapper/myarchive
# losetup -d /dev/loop0

Add key to the container

LUKS allows for up to 8 passwords to each partition/vault.
You can add other 7 basically and use all of these 8 passwords to access your vault.

To add keys, vault needs to be ‘open’

# losetup /dev/loop0 /3TB/file_container.img
# cryptsetup luksOpen /dev/loop0 myarchive
# cryptsetup luksAddKey /dev/loop0
< enter any current phrase - and add new phrase >

Change the key of the container

To change the key, the vault/container needs to be ‘close’

# cryptsetup luksClose /dev/mapper/myarchive
# cryptsetup luksChangeKey /dev/mapper/myarchive 
< verify and change your passphrase here>

Setup auto mount

This will generate a new file as a key, added to the vault and set to be used to auto mount at boot:

# dd if=/dev/urandom of=/root/keyfile bs=1024 count=4
# chmod 0400 /root/keyfile
# cryptsetup luksAddKey /dev/loop0 /root/keyfile
Enter any passphrase:
~#

Than we need to setup /etc/crypttab and /etc/fstab

# cat /etc/crypttab
# <target name>	<source device>		<key file>	<options>
myarchive /dev/loop0 /root/keyfile luks

# tail -n1 /etc/fstab
/dev/mapper/myarchive /cryptoarchive ext4 defaults,noauto 0 2

After that, I have personally created a specific init script. I couldn’t find the right way to run losetup /dev/loop0 /3TB/file_container.img before activating the archive, so…
First of all, I’ve disables cryptdisks via /etc/default/cryptdisks

CRYPTDISKS_ENABLE=No

Than, I’ve created this init script, saved as /etc/init.d/crypt-myvault

#! /bin/sh
### BEGIN INIT INFO
# Provides:          crypt-myvault
# Required-Start:    $remote_fs $syslog mountall netatalk
# Required-Stop:     $remote_fs $syslog mountall netatalk
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Manage encrypted vault
# Description:
### END INIT INFO

CONFFILE=/etc/default/crypt-myvault

. $CONFFILE > /dev/null 2>&1

if [ -z ${SPARSEFILE} ] || [ -z ${VAULT} ]; then
    echo "Required parameters missing.
	  Please configure $CONFFILE

	  # Example #
	  ENABLE=true
	  SPARSEFILE=/<path>/<file.img>
	  VAULT=<vault_name>
          "
    exit 1
fi


if [ ! $ENABLE ]; then
   echo "$0 disabled in $CONFFILE. Please set ENABLE=true and retry"
   exit 1
fi

checkconfig () {
if grep -q "^/dev/mapper/$VAULT" /etc/fstab && grep -q "^$VAULT" /etc/crypttab ; then
    CHK=0
else
    CHK=1
    echo "$VAULT doesn't seem configured in /etc/fstab or /etc/crypttab.\nUnble to continue."
fi
}

mount_myarchive () {
 /sbin/losetup /dev/loop0 $SPARSEFILE
 /sbin/cryptdisks_start $VAULT
 /bin/mount /dev/mapper/$VAULT
}

umount_myarchive () {
 /bin/umount /dev/mapper/$VAULT
 /sbin/cryptdisks_stop $VAULT
 /sbin/losetup -d /dev/loop0
}



case "$1" in
    start)
        echo "Enabling and mounting $VAULT"
	checkconfig
        mount_myarchive
        ;;
    stop)
        echo "Umounting and disabling $VAULT"
	checkconfig
        umount_myarchive
        ;;
    check)
	checkconfig
	[ $CHK -eq 0 ] && echo "Check OK" || echo "Check FAILED"
	;;
    *)
        echo "Usage: $0 start|stop|check"
        exit 1
        ;;
esac

exit 0

This script requires also a /etc/defaults/crypt-myvault configuration file:

# Conf file for /etc/init.d/crypt-myvault
ENABLE=true
SPARSEFILE=/3TB/file_container.img
VAULT=myarchive
VAULT_MOUNT_POINT=/cryptoarchive

To finish, we need to enable the script:

# update-rc.d crypt-myvault defaults

We need to be sure that this script runs AFTER the USB drive is mounted. So… I’ve added ‘netatalk’ as required-start in the header of the init script, as I’ve noticed that once netatalk starts, the USB disk is already mounted.
I’m sure there is a better/nicer way, but this seems to work well for me 🙂

Sources:

http://serverfault.com/questions/696554/creating-a-grow-on-demand-encrypted-volume-with-luks

LUKS passphrases: Changing, adding, removing

https://www.howtoforge.com/automatically-unlock-luks-encrypted-drives-with-a-keyfile

Dynamic MOTD on Debian

Here a simple script that setup a dynamic MOTD message ‘ubuntu-like’ on Debian servers:

#!/bin/bash

# Script to install Dynamic MOTD on Debian servers

# This version uses figlet

if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root or using sudo"
   exit 1
fi

if [ ! -f /etc/debian_version ] ; then
   echo "This script can run ONLY on Debian OS"
   exit 1
fi

# Install required packages:
# - figlet: ASCII art for hostname
# - lsb-release: get distro details (fallback)
apt-get install figlet lsb-release

# Backup MOTD file
mv -f /etc/motd{,.ORIG}

# Symlink dynamic MOTD file
ln -s /var/run/motd /etc/motd

# Create dynamic motd environment
mkdir /etc/update-motd.d/
cd /etc/update-motd.d/

cat <<'EOF' > 00-header
#!/bin/sh
#
#    00-header - create the header of the MOTD
#
[ -r /etc/os-release ] && . /etc/os-release
OS=$PRETTY_NAME

if [ -z "$OS" ] && [ -x /usr/bin/lsb_release ]; then
        OS=$(lsb_release -s -d)
fi

figlet -f slant $(hostname)
printf "\n"
printf "\t- %s\n\t- OS version %s\n\t- Kernel %s\n" "$OS" "$(cat /etc/debian_version)" "$(uname -r)"
printf "\n"
EOF

cat <<'EOF' > 10-sysinfo
#!/bin/bash
#
#    10-sysinfo - generate the system information
#
date=`date`
load=`cat /proc/loadavg | awk '{print $1}'`
root_usage=`df -h / | awk '/\// {print $(NF-1)}'`
home_usage=`df -h /home | awk '/\// {print $(NF-1)}'`
memory_usage=`free -m | awk 'NR==2{printf "%.2f%%\n", $3*100/$2 }'`
swap_usage=`free -m | awk '/Swap/ { printf("%3.1f%%", "exit !$2;$3/$2*100") }'`
users=`users | wc -w`
time=`uptime | grep -ohe 'up .*' | sed 's/,/\ hours/g' | awk '{ printf $2" "$3 }'`
processes=`ps aux | wc -l`
ip=`ifconfig $(route -n | grep '^0.0.0.0' | awk '{ print $8 }') | grep "inet addr" | awk -F: '{print $2}' | awk '{print $1}'`


printf "System information as of %s\n\n" "$date"
printf "IP Address:\t%s\tSystem uptime:\t%s\n" "$ip" "$time"
printf "System load:\t%s\tProcesses:\t%s\n" "$load" "$processes"
printf "RAM used:\t%s\tSwap used:\t%s\n" "$memory_usage" "$swap_usage"
printf "Usage on /:\t%s\tUsage on /home:\t%s\n" "$root_usage" "$home_usage"
printf "Local Users:\t%s\tProcesses:\t%s\n" "$users" "$processes"
EOF


cat <<'EOF' > 90-footer
#!/bin/sh
#
#    90-footer - write the admin's footer to the MOTD
#
[ -f /etc/motd.tail ] && cat /etc/motd.tail || true
EOF


chmod +x /etc/update-motd.d/*

clear
echo "Installation completed. Please logout and login again to test."

 

If you are running CentOS7, here the how to for that OS.