WooCommerce Nag Notice

We all (mostly) understand updates are important and I’m sure there were only good intentions by setting it but the
‘Connect your store to WooCommerce.com to receive extensions updates and support.’
Nag notice is ridiculous. A non-dismissable notice should never be allowed. I get you dont want people to just quickly click the dismiss button, so why not put an option at the bottom of the connect page ‘Dismiss this alert. I understand what this means’ even if it only dismissed for say 3 months and then you had to do it again. It would still be annoying but at least easier to deal with.

But no, in someones infinite wisdom they’ve decided you absolutely must connect your store and have no other option. Well here’s the code to add to stop that nag notice

add_filter( 'woocommerce_helper_suppress_admin_notices', '__return_true' );

** It is your own responsibility to keep your site upto date.
** Disabling this notice, may disable other woocommerce notices.

There are of course legitimate reasons why would wouldn’t want to connect your store, managing your updates your own way should always be allowed. So dev’s stop trying to dictate how you want/think things should run, choice is the key.

WooCommerce 3.3.0+

Yesterday upgrade a store to WooCommerce 3.3.1 from whatever the hell it was on before.

Today I’ve spent the day putting things right¬†ūüė†¬†All the issues are around the new Orders UI and it seems like petty small stuff but it’s safe to say I’m hating the new UI because I’ve wasted the day dealing with over 50 complaints.
For those unfamiliar here’s the proposed changes (the end result is a little different)¬†https://woocommerce.wordpress.com/2017/11/16/wc-3-3-order-screen-changes-including-a-new-preview-function-ready-for-feedback/#comment-4137

I’ve so far fixed some of the issues, such as rearranging the columns (why the actual fu*k status was moved I’ll never know or understand). The below code is probably not the best way to do it, but it works

// Function to Change the order of the Columns
function woocommerce_myinitials_restore_columns( $columns ) {
    $new_order = array(
       'cb' => '',
       'order_status' => '',
       'csv_export_status' => '', // dont think this ones standard but it's part of a plugin we use.
       'order_number' => '',
       'order_items' => '',
       'billing_address' => '',
       'shipping_address' => '',
       'order_date' => '',
       'order_total' => '',
       'wc_actions' => '',
    foreach($columns as $key => $value) {
       $new_order[$key] = $value;

    return $new_order;
add_filter( 'manage_edit-shop_order_columns', 'woocommerce_myinitials_restore_columns',99 );

So that’s one issue solved. The next was being able to click anywhere in the row opens the order (yeah I’m sure that’s nice, but if you rely on tapping a touchscreen i.e. click and drag to scroll then this causes problems). The following code adds the no-link class to the tr and stops this shitty behaviour

function woocommerce_myinitials_restore_columns_add_nolink_class( $classes ) {
    if ( is_admin() ) {
        $current_screen = get_current_screen();
        if ( $current_screen->base == 'edit' && $current_screen->post_type == 'shop_order' ) {
            $classes[] = 'no-link';
    return $classes;
add_filter( 'post_class', 'woocommerce_myinitials_restore_columns_add_nolink_class' );

Thanks on this one goes to ‘rodrigoprimo’ for the initial fix and others who picked it up and added a bit to it¬†https://github.com/woocommerce/woocommerce/pull/18708.

I’ve added the following as a stylesheet

.post-type-shop_order .wp-list-table td, .post-type-shop_order .wp-list-table th {
   vertical-align: unset;

.post-type-shop_order .wp-list-table td.order_status {
   vertical-align: middle;
   text-align: center;

This places the orders back at the top of the row, and stops the previous restoration of items sold link jumping around. but I’d rather the new status text stays in the middle inline with the checkbox, hopefully we’ll get this back to an icon soon.

All of the above I’ve added to our custom plugin, you could either do this or add them to your functions.php

Outstanding issues:
1. Getting back the Email address. There is some hope this may come back officially, but I’ll be fixing it for us tomorrow.
2. The Status being text not icons. I understand this makes more sense to new users but if you have some long statuses like we do, the text doesn’t fit and we’ve got 10 statuses all looking the same. Having coloured icons worked for us and if you weren’t sure hover the mouse. I’ll be looking to get them back to icons tomorrow.
3) Date column, just why! Why would anyone think not putting the actual date and time of the order here is a good idea. Stupid ‘X mins ago’ is no use at all.

The new preview window looks good but I really dont see it getting much use, we need the important data on the front. If it’s not that important just open the order. WooCommerce dev’s decided to screw with it but I dont think there’s an understanding that if your going as far as opening the preview window then I’m pretty sure you were used to just editing the order which is probably still going to be the case.

So summing up today, I’ve had a shit day of people moaning at me because some developers decided to improve something that really didn’t need touching. Doesn’t sound like every developer I’ve ever known¬†ūüėā. I’m now getting something to eat before I go near everything I’d planned on working on today.

Setting NGINX + PHPLDAPADMIN location & php for subfolder

Spent far too long trying out different ways to get this to work. I need to setup a server listening to the local IP address to restrict things like phpldapadmin to internal requests. But hit problems with nginx appending the location to the root path, and php having no idea where to get the files from.

Here’s the config that ended up working

server {
listen 80;
root /var/www/default/;

index index.html index.htm index.nginx-debian.html;


location = / {
        try_files $uri $uri/ =404;

location /phpldapadmin {
        alias /usr/share/phpldapadmin/htdocs;
        index index.php index.html index.htm;

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;

                # With php7.0-fpm:
                fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
                fastcgi_param SCRIPT_FILENAME $request_filename;
                fastcgi_intercept_errors on;

# pass the PHP scripts to FastCGI server listening on
location ~ \.php$ {
        include snippets/fastcgi-php.conf;

        # With php7.0-cgi alone:
        # fastcgi_pass;
        # With php7.0-fpm:
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $request_filename;
        fastcgi_intercept_errors on;

access_log /var/log/nginx/localip-access.log;
error_log /var/log/nginx/localip-error.log;

dpkg: error processing package libc6:amd64 (–configure):

A nice easy one for 2am ūüôĀ but it took me hours to work out (and it really shouldn’t have).

Trying to update servers using apt-get upgrade. It listed about 6 packages to be upgraded, but kept throwing an error:

Setting up libc6:amd64 (2.23-0ubuntu4) ...
sh: echo: I/O error
sh: echo: I/O error
dpkg: error processing package libc6:amd64 (--configure):
subprocess installed post-installation script returned error exit status 1
Errors were encountered while processing:
E: Sub-process /usr/bin/dpkg returned an error code (1)

I spent a good few hours¬†searching google and trying to work out what had been installed that could be causing an issue with libc6. Though I was fairly certain nothing had been (only because this is 2 days after a migration to new servers, and I’m hitting this problem on 2/4 servers, but they have extremely similar setups).

By chance I noticed:

Filesystem Size Used Avail Use% Mounted on
udev 489M 0 489M 0% /dev
tmpfs 100M 100M 0 100% /run
/dev/vda1 30G 16G 13G 55% /
tmpfs 497M 0 497M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 497M 0 497M 0% /sys/fs/cgroup
tmpfs 100M 0 100M 0% /run/user/1000

I’ll give you a clue “look at /run” ūüôĀ

Something is eating all the space. So I tried to run ncdu to look for large files (I know there’s other ways, but I like ncdu). But I hadn’t installed it on this new server and I can’t install it with apt-get broken.

Thinking /run is bound to be causing some issues (still not sure if it’s causing this particular issue), I reboot the server (bad move!). It locked up and had to be power cycled. Thankfully it’s a droplet and with DigitalOcean I can power cycle easily (I did try the console but it wouldn’t connect).

Anyway after a reboot, /run started at 5% used, but quickly grew to 70%. but I did managed to install ncdu, and with that I knew the problems I had with apt-get were being caused by a full /run.

After a quick (very quick) look at ncdu /run I could see hhvm.hhbc taking up approx 85Mb+

A quick check of the config and I can see hhvm is configured¬†to do so. So I adjusted the config to put it in /var/cache/hhvm/hhvm.hhbc instead and update the systemd service script to create /var/cache/hhvm and set it’s owner.

Another reboot, everything seems fine and /run is now at 3% used.

And I’ve run apt-get upgrade successfully.

I’m thankful that I noticed, I really thought I’d screwed something on these 2 servers while migrating, and I could see another night of rebuilding new servers ahead of me.

Morale of the story: Check your not out of space when you get weird errors (yes the I/O should have rang some bells, but hey it is 2am).

Nginx + WordPress + Fail2Ban + CloudFlare

I hate being woken at 2am, to be told “we’re under attack!”. Well that’s pretty much this morning ūüôĀ

Now to be fair, it wouldn’t have even been noticed. Our servers and setup handled it very well and it was only noticed during a switch over to a backup server with less resources during maintenance.

On checking the logs I could see lots of attempts like

X.Y.X.Y - - [28/Aug/2016:03:12:16 +0100] "POST /wp-login.php HTTP/1.0" 200 5649 "-" "-"

We’re walking¬†114,893 requests to just the one server. My first instinct is to add the offending IP address to our IPTables BLOCK list and just drop the traffic altogether. Except this no longer works with CloudFlare since the IP addresses the firewall will see is theirs not the offender!

No problem, CloudFlare deals with this(!?) I can just turn on ‘Under Attack’ mode and let them handle it. This is where you quickly learn it doesn’t really do much. Personally I got a lovely 5 second delay when accessing our websites with ‘Under Attack’ activated. but¬†our servers were still being bombarded with requests. So I added the offending IP addresses to the firewall on CloudFlare. Surely that will block them from even reaching our servers!¬†While I can’t say it had no¬†effect, out of¬†a number of IP addresses I had¬†added some were still hitting our servers quite heavily.

So the question becomes ‘How can I drop the traffic at nginx level?’. Nginx is configured to restore the real IP addresses, so I should be able to block¬†the real offenders here not CloudFlare.

Turns out to be pretty easy. Add:

### Block spammers and other unwanted visitors ###
include blockips.conf;

Into the http section in /etc/nginx/nginx.conf

http {

### Block spammers and other unwanted visitors ###
include blockips.conf;



Then create the /etc/nginx/blockips.conf:

deny X.Y.X.Y;

Just add a deny line for each offending IP. Then reload nginx (service nginx reload). I’d recommend testing your new config first (nginx -t)

Now with that done of both servers I’m still seeing requests but they are now getting 403 errors and not allowed to hit any of the websites on¬†our servers ūüôā¬†after another 5 minutes of attack they clearly gave up as all the requests stopped.

But we’re not done. What if they’re just changing IP addresses? we need this to be automatic. Enter Fail2Ban. I haven’t used this in some time but I know what I need it to do.

  1. Check all the websites log files for wp-login.php attempts
  2. Write to /etc/nginx/blockips.conf
  3. Reload nginx

Should be quite simple. Turns out it is, but it took hours trying to figure out how since all the guides seem to think you’ll be using Fail2Ban to configure IPTables.

Here’s the files/configuration I added for Fail2Ban

# WordPress brute force auth filter
# Block IPs trying to auth wp wordpress
# Matches e.g.
# X.Y.X.Y - - [28/Aug/2016:03:12:16 +0100] "POST /wp-login.php HTTP/1.0" 200 5649 "-" "-"

failregex = ^<HOST> .* "POST /wp-login.php.*200
ignoreregex =</pre>

<!--?prettify linenums=true?-->
<pre class="prettyprint">[wordpress-auth]
enabled = true
filter = wordpress-auth
action = wordpress-auth
logpath = /var/log/nginx/*access*.log
bantime = 1200
maxretry = 8
# Fail2Ban configuration file based on dummy.conf
# Author: JD


# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
actionstart = touch /etc/nginx/blockips.conf
service nginx reload

# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
# dont do this actionstop = echo "" &gt; /etc/nginx/blockips.conf
# service nginx reload
actionstop =

# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
actioncheck =

# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
actionban = echo "deny &lt;ip&gt;;" &gt;&gt; /etc/nginx/blockips.conf
service nginx reload

# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
actionunban = sed -i "/&lt;ip&gt;/d" /etc/nginx/blockips.conf
service nginx reload


init = 123

It’s pretty simple and will need some tweaking. I’m not so sure 8 requests in 20 mins is¬†very good. We do have customers on some of our sites who regularly forget their password.¬†The regex does look at the 200 code, I read that a successful auth would actually give a 304. Not sure if this is correct so will need some testing. I also found other information on how to change the code to a 403 for failed login attempt. I think this would be a huge plus, but I’m not looking into that tonight.

A few tests using curl and I can see Fail2Ban has done it’s job, added the IP to the nginx blockips file and reload nginx ūüėÄ I’m not too worried about syncing this across servers,¬†as shown tonight they will balance well and I’m confident that within a few minutes of being bombarded they would both independently block the IP.

So there’s my morning of working around using CloudFlare but still keeping some form of block list. Hope this helps someone. Please comment¬†to let me know if you found any of this useful/worth reading.

WooCommerce API: No order data specified to edit order

Yesterday I received my Barcode scanner, after a little playing scanning everything in sight I got to work on programming a Raspberry PI in Python to use the barcode scanner to update orders in WooCommerce.

I’m not going to go into full details in this post on how. but I will write it up soon.

A BIG problem I hit though was updating orders using the WooCommerce API. I kept getting error code 400 ‘No order data specified to edit order’. I’d followed the example but changed it to fit my code i.e dont print the output but return it for error checking.

Searching google for that exact phrase gave just 1 result, and it’s someone commenting they are getting the error with no response (and it’s 4 months old on a 2 year old post).

After looking through the API code and trying to follow along (I’m not the best programmer but I can generally follow what it’s trying). I found it looking for ‘order’

So after looking at how the bulk update is done and with a bit of playing I found that instead of

data = {'status': 'completed'}

you need

data = {"order":{"status":"completed"}}

Hey Presto it works. My specific code is now

def WooCommerceUpdateOrder(order, data):
   global wcapi
   putstring = "orders/%s" % order
   result = wcapi.put(putstring, data)
   return result

which is called using

data = { 'order': { "status": "completed" } }
updateresult = WooCommerceUpdateOrder(order, data)

Hope this helps someone. ¬†I’ll post the full python program for the barcode reader later in the week. Basically it uses a PI, PI LCD Screen with 4 buttons, Barcode Reader, Speakers. ūüôā

APT not automatically updating DigitalOcean

I’ve recently noticed a problem on 3 of my Digital Ocean Servers. The APT package lists are not automatically updating every day.¬†I try to keep all servers upto date, and rely on Nagios to inform me when there’s packages needed to be updated and that’s the main reason I noticed something was broken.

The 3 servers in particular are newer builds to the rest of the system, and they dont have near as much installed as the others, so at first I didn’t pay too much attention when other servers were going into warning state on nagios indicating updates but these 3 weren’t. However¬†I would still connect to these servers and run my normal command:-

apt-get update &amp;&amp; apt-get upgrade &amp;&amp; apt-get dist-upgrade &amp;&amp; apt-get autoremove

A few times these servers did install updates and I just thought it must have been my timing, that the package lists hadn’t yet been updated by the cron.daily.

But after this happening a few times, I decided to not run the above and see how long these servers would take for nagios to throw an alert. It never did and that got me a little worried.

Over the last few days I’ve been diagnosing what’s wrong. I started out with making sure cron is working properly. Then¬†kept an eye on the file timestamps

ls -ltrh /var/lib/apt/lists/

Eventually getting to /etc/cron.daily/apt and checking through what was was doing on the working servers compared to the broken ones. I turned on VERBOSE and got a bit of info when running /etc/cron.daily/apt but it seemed to exist quite quicky.

Comparing it to a working server the important bit seemed to be around

+ apt-config shell Debdelta APT::Periodic::Download-Upgradeable-Packages-Debdelta
+ eval
+ [ 1 -eq 0 ]
+ do_cache_backup 0</pre>
On the broken servers I was getting
<pre>+ [ 0 -eq 0 ]
+ [ 0 -eq 0 ]
+ [ 0 -eq 0 ]
+ [ 0 -eq 0 ]

Then it would exit. Further investigating was showing a few settings were being populated on the working servers but not on the broken ones.

So I compared the directory /etc/apt/apt.conf.d/ on both servers an found the following files missing from the broken servers


Aptitude::Get-Root-Command "sudo:/usr/bin/sudo";


APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";


APT::Update::Post-Invoke-Success {"touch /var/lib/apt/periodic/update-success-stamp 2&gt;/dev/null || true";};


APT::Archives::MaxAge "30";
APT::Archives::MinAge "2";
APT::Archives::MaxSize "500";


DPkg::Post-Invoke {"if [ -d /var/lib/update-notifier ]; then touch /var/lib/update-notifier/dpkg-run-stamp; fi; if [ -e /var/lib/update-notifier/updates-available ]; then echo &gt; /var/lib/update-notifier/updates-available; fi "; };
(reverse-i-search)`apt-': apt-get update &amp;&amp; apt-get upgrade &amp;&amp; apt-get dist-upgrade &amp;&amp; apt-get autoremove

I think the main one being¬†10periodic but I didn’t fancy spending days/weeks adding each and waiting to see what happened, so I added them all in one go.

Then run /etc/cron.daily/apt again, and this time it’s taken a lot longer to run i.e I’ve written this entire post and it’s still running, as opposed to exiting within a few seconds earlier.

I’m pretty confident that this has now solved my APT package list not automatically updating problem, and providing it¬†has Nagios will start warning on these 3 servers now.

Redis Sentinel PHP Session Handler

In the last few weeks I’ve been¬†rebuilding servers & all services. I like to do this every so often to clear out any old junk,¬†and take the opportunity to improve/upgrade systems and documentation.

This time around it’s kind of a big hit though.¬†While I’ve had some services¬†running with HA in mind, most would require some manual intervention to recover. So the idea this time was to complete what I’d previously started.

So far there’s 2 main changes I’ve made:

  1. Move from MySQL Master/Master setup to MariaDB cluster.
  2. Get redis working as HA (which is why your here).

I’ll bore everyone in another post with the issues on MariaDB cluster. But this one concentrates on Redis.

The original setup was 2 redis servers running, 1 master 1 slave. With php session handler configured against a hostname which was listed in /etc/hosts. However this time as I’m running a MariaDB cluster, it kind of made sense to try out a redis cluster (dont stop reading yet). After reading lots, I decided on 3 Servers each running a master and 2 slaves. A picture here would probably help but you’ll just have to visualize. 1, 2 & 3 are Servers, Mx is Master of x, Sx is Slave of x. So I had 1M1, 1S2, 1S3, 2S1, 2M2, 2S3, 3S1, 3S2, 3M3.

This worked, in that if server 1 died, it’s master role was taken over by either 2 or 3. And some nagios checks and handlers brought it back as the master once it was back online. Now I have no idea if this was really a good setup (I didn’t test it for long enough), but 1 of the problems I encountered was¬†where the PHP sessions ended up. I (wrongly) thought the php session would work with the cluster to PUT and GET the data, so I gave it all 3 master addresses. Reading info on redis, if the server your asking doesn’t have the data it will tell you which does, so I thought it’s not really a problem if 1M1 goes down and the master becomes 2M1 because the other 2 masters will know so will say the data is over there. In manual testing this worked. but PHP sessions doesn’t seem to work with being redirected (and this is also a problem later).

So after seeing this as a problem, I thought maybe a cluster is a bit overkill anyway and simplifying it to 1 Master and 2 Slaves would be fine anyway.

I wiped out the cluster configuration and started again, but I knew I was also going to need sentinel this time to manage which is the master (I’d looked at it before, but went for cluster instead. Time to read¬†up again).

After getting a master up and running and then adding 2 slaves. I pointed PHP Sessions to all 3 servers (again a mistake). I was hoping (again) that the handler would be sensible enough to connect to each and if it’s a slave (read only) detect that it can’t write and move to the next. It doesn’t. It happily writes errors in the logs for UNKNOWN.

So I need a way for the session handlers to also know which is the current master, and just use this.

My setup is currently 3 MariaDB/redis servers S1, S2 & S3 and 2 Nginx servers N1 & N2.

I decided to install redis-sentinel on all 5, with a quorum of 3. The important bit in my sentinel config is:

sentinel client-reconfig-script mymaster /root/SCRIPTS/redis-reconfigure.sh

and the redis-reconfigure.sh script:

adddate() {
	while IFS= read -r line; do
		echo "$(date) $line"

addrecord() {
	echo "## Auto Added REDIS-MASTER ##" &gt;&gt; /etc/hosts
	echo "$1 REDIS-MASTER" &gt;&gt; /etc/hosts

deleterecord() {
	sed -i '/REDIS-MASTER/d' /etc/hosts

# &lt;master-name&gt; &lt;role&gt; &lt;state&gt; &lt;from-ip&gt; &lt;from-port&gt; &lt;to-ip&gt; &lt;to-port&gt;
# $1 $2 $3 $4 $5 $6 $7

if [ "$#" -eq "7" ]; then
	if grep -q "REDIS-MASTER" /etc/hosts; then
		echo "Delete and Re-Add $6 REDIS-MASTER" | adddate &gt;&gt; /var/log/redis/reconfigure.log
		addrecord "$6"
		echo "Add $6 REDIS-MASTER" | adddate &gt;&gt; /var/log/redis/reconfigure.log
		addrecord "$6"

Basically this script is run whenever the master changes (I need to add some further checks to make sure <role> and <state> are valid, but this is being done for quick testing.

I’ve then changed the session path:

session.save_path = "tcp://REDIS-MASTER:6379"

and again in the pool.d/www.conf:

php_admin_value[session.save_path] = "tcp://REDIS-MASTER:6379"

Quite simply I’m back to using a hosts entry which points at the master. but the good thing (I wasn’t expecting) was I dont have to restart php-fpm for it to detect the change in IP address.

It’s probably not the most elegant way of¬†handling sessions, but it works for me. The whole point in this is to be able to handle spikes in traffic by adding more servers N3, N4, etc. and providing they are sentinels with the script they will each continue to point to the master.

I did think about writing this out as a step by step guide with each configuration I use, but the configs are pretty standard and as¬†it’s a bit more advanced than just installing a single redis to handle sessions, I think the¬†above info should¬†really only be¬†needed by anyone with an idea of what’s where anyway.

I still dont really get why the redis stuff for PHP doesn’t follow what the redis server tells it i.e the data is over there. I suppose it will evolve. If your programming up your own use of redis like the big boys then you’ll be programming¬†for that. I feel like I’m doing something wrong or missing something,¬†I’m certain I’m not the only one who uses redis for PHP sessions across multiple servers behind a load balancer, but there is very little I could find googling beyond those that just drop a single redis instance¬†into place. As this becomes single point of failure it’s pretty bizarre to me that every guide seems to go this way.

If anyone does read this and actually wants a step by step, let me know in the comments.

Just to finish off I’ll give an idea of my current (it’s always changing) topology.

2 Nginx Load Balancers (1 active, 1 standby)

2 Nginx Web Servers with PHP (running syncthing to keep files in sync, I tried Gluster FS which everyone raves about, but I found it crippling so I’m still looking for a better solution).

3 Database Servers (running MariaDB in cluster setup and Redis in 1 Master, 2 Slaves configuration).

1 Monitoring Server (running nagios, checking pings, disk space, processes, ports, users, ram, cpu, MariaDB cluster status, Redis, DB & redis is accisble from Web Servers, vpns, dns and probably a load more).

Rasberry 1-Wire Resolution

More of a pastebin post.


Is an important URL for changing the resolution on a DS18B20 via the Raspberry PI. Pay attention to the note re RPi2 and changing the code.

I also changed the GPIO pin to 4 to save rewiring but disabled the config.txt option for 1wire and disabled the gpio and thrm in /etc/modules just in case while running this program.

It seems to be working, now have the resolution set at 12bits on a new sensor I received that was working in .5c steps.

Ansible Part 1

I already have a Droplet for management so I’m going to be using this for Ansible and a new droplet to test some deployments. I’ve done the following on the server:

add-apt-repository ppa:rquillo/ansible
apt-get update
apt-get install ansible

I have no doubt that¬†I will learn I’ve done bits wrong as I get further in, but¬†I’m going to start with the following hosts config






So the first thing I’m going to tackle is setting up new users. I’ve been playing with it for about an hour, although I got the user created very quickly I hit a problem with my setup. I need to send multiple ssh keys for some users (we use different keys on PC’s, Laptops, Mobiles).¬†Every example I found seemed to a) want to pull a key from a file, b) just use one key.

After quite some playing, and trying different things I found a way. This in turn meant I had to slightly change the create user bit of the playbook.

Here’s the users.yml playbook so far

- hosts: all

    - name: Add Users from group_vars file
      action: user name={{ item.name }} password={{ item.password }} shell={{ item.shell }} state={{ item.state }} update_password=always
      with_items: users

    - name: Add SSH User Keys from group_vars files
      authorized_key: user={{ item.0.name }} key='{{ item.1 }}'
        - users
        - authorized

and uses a group_vars file (group_vars/initial)


  - name: NAME
    password: HASHED_PASSWD
      - ssh-rsa SSH_KEY_1
      - ssh-rsa SSH_KEY_2
      - ssh-rsa SSH_KEY_3
      - ssh-rsa SSH_KEY_4
    shell: /bin/bash
    state: present

I did think about pulling the keys in from the authorized_keys files, but¬†not all users are allowed on the management, so I’d have to keep files for them and if I’m going that far, I may as well just keep them in group_vars. It doesn’t look as nice if you cat the file but it’s structured and makes sense.

The last thing I want to do is set out add the user to some groups.

This was nice and easy, add the groups to the group_vars file

    groups: sudo,www-data

Then change the users.yml to add the groups

    action: user name={{ item.name }} password={{ item.password }} shell={{ item.shell }} state={{ item.state }} groups={{ item.groups }} update_password=always

I was worried that it may screw up the users own group as the manual says it delete’s all other groups except the primary, since I haven’t told it a primary using ‘group’ I thought it may be a problem, but thankfully it’s not this just worked.

Well I thought at this point I was pretty finished. Ha not a chance. I added a user for ansible to be able to connect as since I’ll be removing root ssh access. This means I’m going to need to let ansible sudo so I’d best sort that now.¬†Here’s a bit of code I found and changed slightly. Make sure you change the username in the sudoers.d/ansible file

Appended to the end of users.yml

  - name: Ensure /etc/sudoers.d directory is present
    file: path=/etc/sudoers.d state=directory

  - name: Ensure /etc/sudoers.d is scanned by sudo
    action: lineinfile dest=/etc/sudoers regexp="#includedir\s+/etc/sudoers.d" line="#includedir /etc/sudoers.d"

  - name: Add ansible user to the sudoers
    action: 'lineinfile dest=/etc/sudoers.d/ansible state=present create=yes regexp="ansible .*" line="USERNAME ALL=(ALL) NOPASSWD: ALL"'

  - name: Ensure /etc/sudoers.d/ansible file has correct permissions
    action: file path=/etc/sudoers.d/ansible mode=0440 state=file owner=root group=root

On first run¬†you’d use root to connect up. Then you would use

ansible-playbook users.yml -u USERNAME -s

All working. It’s taken about 2 hours to get to the point of deploying a couple of users automatically. I’m not so sure this has saved me time in the long run ūüôā but it’s the first step in a much bigger project. I’m kind of glad it wasn’t just copy and paste others code and stuff broke, it gave me a chance to understand a bit more.

Part 2 will be coming soon. There we’ll lock down SSHd and apply the¬†default firewall rules.