Category Archives: Muppet Labs

SSH Key Management

I did some annual SSH key housekeeping this week. Here are a few things you should probably do:

  1. Use the newer ed25519 keys, update them once a year
  2. Generate a key for each general use case / project
  3. Don’t be a muppet and have ssh keys in plain text on your laptop and in your backups
  4. Create a .ssh/config file, which points to your keys on an encrypted filesystem

Below are a few commands and examples to get you started.

Generate an ed25519 key, with your email address as a comment and save it to a key file for a project or company or some use case you might have:

ssh-keygen -t ed25519 -C '' -f swimgeek

Create an ssh config file for your keys and various servers, with user account, port and where the key lives.

Create a .ssh/config file

Host swimgeek-myserver
    Port 2345
    User joe
    IdentityFile /Volumes/crypt-fs/ssh/swimgeek

Host projectabc-prod
    Port 2567
    User root
    IdentityFile /Volumes/crypt-fs/ssh/projectabc

Note that these keys are not in your home directory. They live on an encrypted filesystem, which is not mounted at boot time, and not backed up in plain text.

Install mosh and add an alias to your .zshrc

alias moshprod="mosh projectabc-prod"


WordPress with NGINX Unit, HTTP2, IPv6

How to install WordPress with NGINX Unit PHP, SSL, HTTP2 and IPv6 on Debian 9.

Some quick notes on my recent WordPress hosting upgrade.

First read and follow Installing WordPress with NGINX Unit. Then make WordPress happy to answer nginx proxy requests via https. And then upgrade your SSL certs. Do an SSL test. Easy.

Update: Also look at the Mozilla SSL Config Generator.

To make your site work on IPv6 with http2 you need these lines in your nginx config:

listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;

Do NOT use “listen 80 http2;” – http2 on port 80 is a bad idea it seems. If you enable http2 on port 80 you will be prompted to download something when you do an http request.

If you get a 301 redirect loop, you don’t have “$_SERVER[‘HTTPS’]=’on’;” in your WordPress wp-config.php file. This makes WordPress understand the original request was https and it does not need to rewrite the url with its canonical URL auto redirect feature.

Just for fun, you probably want to install some Firefox plugins which tell you if a site uses IPv6 and HTTP2. Check out HTTP/2 Indicator, SixOrNot and SixIndicator.

Obligatory Muppets Pic

Nginx config example:

upstream muppet-index_php_upstream {
server; # NGINX Unit backend address for index.php with
# 'script' parameter

upstream muppet-direct_php_upstream {
server; # NGINX Unit backend address for generic PHP file handling

server {
listen 80;
listen [::]:80;
return 301;

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
return 301;

ssl on;
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;
ssl_trusted_certificate /etc/letsencrypt/live/;

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

ssl on;
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;
ssl_trusted_certificate /etc/letsencrypt/live/;

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers On;
ssl_session_cache shared:SSL:128m;
add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
ssl_stapling on;
ssl_stapling_verify on;
# Your favorite resolver may be used instead of the Google one below

root /var/www/muppet/;

charset utf-8;

location / {
try_files $uri @index_php;

location @index_php {
proxy_pass http://muppet-index_php_upstream;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;


location /wp-admin {
index index.php;

location ~* \.php$ {
try_files $uri =404;
proxy_pass http://muppet-direct_php_upstream;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;



WordPress Backup Script

My WordPress backup script, which keeps daily, weekly and monthly backups of website files and database dumps.


This should work on your garden variety Debian or Ubuntu server running WordPress and Mysql.

Copy this script to /usr/local/sbin/backup-websites

cat /etc/cron.d/backup-websites
# Backup websites and databases at around 3am
15 03 * * * root /usr/local/sbin/backup-websites

btw. sorry the indentation seems to go missing when I use the /code tag in WordPress.

Obligatory Muppets Photo

The script…


# Backup websites and databases

# Set some variables and paths

# Must contain backup.monthly backup.weekly backup.daily folders

DATABASES=`echo "show databases" | /usr/bin/mysql --defaults-file=/etc/mysql/debian.cnf | grep -v Database | grep -v schema`


# Make appropriate destination directory

# Destination file names
date_daily=`date +"%Y-%m-%d"`

# Get current month and week day number
month_day=`date +"%d"`
week_day=`date +"%u"`

# On first month day do monthly
if [ "$month_day" -eq 1 ] ; then
# On Saturdays do weekly
if [ "$week_day" -eq 6 ] ; then
# On any regular day do daily

mkdir $destination

# latest backup symlink
ln -snf $destination /$BACKUP_DIR/latest

# Create backup files in today's directory

stamp=`date +%Y-%m-%d_%H-%M-%S`

# Dump MySQL tables in incoming dir
for db in $DATABASES; do
/usr/bin/mysqldump --defaults-file=/etc/mysql/debian.cnf $db | bzip2 > \


# Compress files, one tar file per sub directory
for filedir in $FILE_DIRS; do
tar -C / -cjf /$BACKUP_DIR/$destination/$filedir.$stamp.tar.bz2 $FILE_DIRS_BASE/$filedir

# Email backup info

ls -l /$BACKUP_DIR/$destination/ | mail -s "[`uname -n`] Websites Backup $date_daily" $email

# Clean up old backups

# Daily - keep for 7 days
find /$BACKUP_DIR/backup.daily/ -maxdepth 1 -mtime +7 -type d -exec rm -rf {} \;

# Weekly - keep for 30 days
find /$BACKUP_DIR/backup.weekly/ -maxdepth 1 -mtime +30 -type d -exec rm -rf {} \;

# Monthly - keep for 365 days
find /$BACKUP_DIR/backup.monthly/ -maxdepth 1 -mtime +365 -type d -exec rm -rf {} \;


Keep your backups for less time by updating mtime above.

To recover your backups:

tar jxvf your-backup-file.tar.bz2
bunzip2 your-backup-file.sql.bz2


Afrikaans Spell Check for macOS and iOS

How to add Afrikaans spell checking and dictionaries to macOS 10.14 and iOS 12…

I use Telegram Desktop a fair bit and it bugged me that Afrikaans spell checking was missing. Here is my current solution. I’m not sure it’s 100% tip-top, but it’s much better than it was.

For macOS…


System Preferences > Language and Region
Add Afrikaans under Preferred Languages


Download these, unzip and copy into ~/Library/Dictionaries.

Get one Afrikaans-English one and one English-Afrikaans. The bigger file sizes.

Open, look in Preferences, enable the new dictionaries and move them into the order you want them.

Spell Check

Download, unzip, copy to ~/Library/Spelling. I found this on the site a few years back.

Settings > Keyboard > Text > Spelling drop down

Scroll down to “Set Up…” and enable Afrikaans.

Now set that drop down to Automatic by Language.

Copy files from the zip file to the base of ~/Library/Spelling, you might need to restart the Systems Preferences app.

For iOS…


Settings > General > Language and Region
Add Afrikaans under ‘Preferred Language Order’


Install the SwiftKey app and enable Afrikaans.

Install Debian on a Macbook Pro

How to install Debian 9 on an old-ish Macbook Pro.

In the future, everybody will run Debian – the universal operating system. Don’t resist. It’s way cooler than macOS. Install Wayland and Sway.

Re-install macOS. Partition the drive and give macOS about 40GB space. Keep the rest for Linux.

Get the Debian ISO: debian-mac-9.0.0-amd64-netinst.iso Note: you may want to download the one with non-free firmware. I just used the standard one.

Prep USB drive:

hdiutil convert debian.iso -format UDRW -o target.img
diskutil list
diskutil unmountDisk /dev/diskN
sudo dd if=target.img of=/dev/diskN bs=1m
diskutil eject /dev/diskN

Download rEFInd binary zip file. Unzip and run the refind-install script on the Macbook Pro. You should now have a nice boot menu.

With USB drive in the laptop, reboot and hold down the Alt key.

Follow the Debian install process, and make sure you select the Linux partition you created, in my case it was /dev/sda4.

Use the Ethernet port and connect to the internet. You may need to install the wifi firmware:

apt-get install firmware-b43-installer

Django Setup Notes – Ubuntu, Nginx, uWSGI


Notes for getting Django running on Ubuntu 16.04… with Nginx, Supervisord, uWSGI in emperor mode and a bit of Postgres.

Start by setting up your Ubuntu environment.

Newish Python 3.6

add-apt-repository ppa:fkrull/deadsnakes
apt-get update
apt-get install python3.6
apt-get install python3.6-dev
apt-get install python-virtualenv


apt-get install postgresql postgresql-contrib
su - postgres
createdb myapp
createuser -P

You may need to dump a database and import it again…

pg_dump -U myuser myapp > myapp.db
psql myapp < myapp.db

SSL Cert Setup

Grab a free SSL cert…

service nginx stop
apt-get install letsencrypt
letsencrypt certonly --standalone -d -d
service nginx start


apt-get install nginx

This should give you a nice and secure HTTPS setup which also supports HTTP2…


server {
listen 443 ssl http2;
charset utf-8;
client_max_body_size 75M;

ssl on;
ssl_certificate /etc/letsencrypt/live/;
ssl_certificate_key /etc/letsencrypt/live/;

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers On;

ssl_session_cache shared:SSL:128m;

add_header Strict-Transport-Security "max-age=31557600; includeSubDomains";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Xss-Protection "1";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' *";
add_header Referrer-Policy origin-when-cross-origin;

ssl_stapling on;
ssl_stapling_verify on;

access_log on;

location /static {
alias /var/www/;

location / {
uwsgi_pass unix:///var/www/;
include /var/www/;


uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_ADDR $server_addr;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
uwsgi_param UWSGI_SCHEME $scheme;

Django Virtual Environment

cd /var/www
virtualenv -p /usr/bin/python3.6 env

cd /var/www/
source env/bin/activate

git clone api

Note: my Django app name is “api” here.

pip install -r api/requirements.txt

pip install Django
pip install uwsgi

Install uWSGI System-wide

Using the latest Python, install uWSGI in /usr/local/bin…

Make sure you are not in a virtual environment or run “deactivate”.

python3.6 -m pip install --upgrade pip
python3.6 -m pip install uwsgi

Supervisord to Manage uWSGI in Emperor mode

apt-get install supervisor


command=/usr/local/bin/uwsgi --emperor /etc/uwsgi/apps-enabled

Setup /etc/uwsgi

cd /etc
mkdir uwsgi
cd uwsgi
mkdir apps-available
mkdir apps-enabled
cd apps-available


uwsgi-socket = /var/www/myapp/api.sock
pythonpath = /var/www/myapp/api
virtualenv = /var/www/myapp/env
processes = 2
reload-on-rss = 150
module = api.wsgi
uid = www-data
gid = www-data

cd /etc/uwsgi/apps-enabled
ln -s ../apps-available/myapp.ini
service supervisor restart

You can now also edit / touch your myapp.ini to make uwsgi restart the worker processes.

Done. Test your setup.

Go make some tea and start coding.

More Notes

Let me know if something above can be done better.

Tune your Postgres database. Then su – postgres and run “pg_conftool show all” to see that your setting are up to date.

Tune your SSL setup.

You probably want 2x uwsgi processes for every CPU in your server.

Update: use pipenv

apt-get install python-pip
pip3 install pipenv
cd /home/fred/code
mkdir myproject
cd myproject
pipenv --three --python /usr/bin/python3.6
pipenv shell
python --version

to use:

su - www-data (or user which runs uwsgi)
pipenv shell

Ubuntu Server Tweaks 16.04


Things you might want to tweak on your new Ubuntu 16.04 server – because you need a pretty prompt and a text editor.

I figured this is a good place to put some notes for next time I want to make a new server feel a bit more homely.


apt-get install jed
No_Backups = 1;


dpkg-reconfigure locales


edit /etc/hosts and add a hostname for the server
edit /etc/hostname and add a hostname
run hostname -F /etc/hostname

apt via IPv4

echo ‘Acquire::ForceIPv4 “true”;’ | sudo tee /etc/apt/apt.conf.d/99force-ipv4

update packages

apt-get update
apt-get dist-upgrade

Clone some packages from another box, maybe?

sudo apt-get install apt-clone
apt-clone clone foo

sudo apt-get install apt-clone
sudo apt-clone restore foo.apt-clone.tar.gz


apt-get install openssh-server
ssh-keygen -t ed25519
ssh-copy-id -i ~/.ssh/ -p 2211 user@host

Setup ssh keys.
Change the port your server runs on in /etc/ssh/sshd_config


apt-get install mosh

Add something like this in .zshrc (on your laptop):
alias mocms=”mosh -p 2345 –ssh=\”ssh -p 2345 \””


apt-get install update-motd landscape-common update-notifier-common
ln -s /var/run/motd.dynamic /etc/motd


apt-get install postfix
edit /etc/mailname
edit /etc/aliases



Auto Security Updates

apt-get install unattended-upgrades

APT::Periodic::Update-Package-Lists “1”;
APT::Periodic::Download-Upgradeable-Packages “1”;
APT::Periodic::AutocleanInterval “7”;
APT::Periodic::Unattended-Upgrade “1”;

Unattended-Upgrade::Mail “your@email.address”;

sudo apt install apticron



apt-get install zsh
apt-get install git

install oh-my-zsh:
sh -c “$(curl -fsSL”

use this .zshrc if you like.

edit your .zshrc
plugins=(autojump brew pip git django python docker screen)

change prompt colours like so:
base_prompt, play with integer values to change colours


apt-get install emacs aspell

install prelude:
wget –no-check-certificate -O – | sh

edit .emacs.d/prelude-modules.el

You may want to run emacs as a daemon for more than one user on your server.

Add (setq server-use-tcp t) to the top of init.el config files – so emacs starts as a tcp server.

alias emacsinit=”emacs -u joe –daemon=joe”
alias e=”emacsclient –server-file=joe -t”

alias emacsinit=”emacs -u root –daemon=root”
alias e=”emacsclient –server-file=root -t”

Apache HTTP2

apt-get install software-properties-common
add-apt-repository ppa:ondrej/apache2
apt-get update
apt-get dist-upgrade
install apache2
a2enmod http2
service apache2 restart

Add this to your vhosts:
Protocols h2 http/1.1


apt-get install mysql-server
apt-get install php7.0
apt-get install libapache2-mod-php
apt-get install php7.0-mysql

Filter on attachment name with Procmail

After a recent spike in emails with .zip and .doc attachments… a quick way to filter them with procmail:

# Drop mails with lame attachments
:0 B
* .*filename=.*\.(zip|rar|rtf|doc|docm|xls|com|hlp|vbs|wsf|eml|shs|exe|nws|chm|pif|vbe|hta|scr|reg|bat)\"?\;?$

Stick this in your .procmailrc

Updated: 7 April 2016, I’ll keep adding to this as I find things to block.

Make Slack behave like the Telegram Desktop app

This will help you remap some Slack keyboard commands so that Command + Enter sends a message and Enter creates a new line… for OSX.

I think this makes way more sense than using Command Enter to “create a new snippet”.

I use the Telegram Desktop app a lot – so accidentally creating snippets in Slack all the time was a bit painful.

You’ll need to install Karabiner and load the private.xml file below…

<?xml version="1.0"?>
        <name>Slack Customisation</name>
            <name>Map Cmd+Enter to send, Enter to extra line</name>
                KeyCode::RETURN, ModifierFlag::COMMAND_L,
                KeyCode::RETURN, ModifierFlag::SHIFT_L

and just for fun, OSX Messages app…


        <name>OSX Messages Customisation</name>
            <name>Map Enter to extra line</name>
                KeyCode::RETURN, ModifierFlag::COMMAND_L,
                KeyCode::RETURN, ModifierFlag::OPTION_L

and Skype…

        <name>Skype Customisation</name>
            <name>Map Enter to extra line</name>
                KeyCode::RETURN, ModifierFlag::COMMAND_L,
                KeyCode::RETURN, ModifierFlag::SHIFT_L