Fun with console.log() πŸ’Ώ

If you've ever developed a web app, you'll be familiar with console.log(...), the method which prints data to the developer console; useful for debugging, logging and testing.

Run console.log(console), and you'll see that there's much more to the console object.
This post briefly outlines the top 10 neat tricks you can use to level up your logging experience.



The console.table() method prints objects/ arrays as a neatly formatted tables.

  'Time Stamp': new Date().getTime(),
  'OS': navigator['platform'],
  'Browser': navigator['appCodeName'],
  'Language': navigator['language'],

Screenshot showing an example output of console.table


Group related console statements together with collapsible sections, using

You can optionally give a section a title, by passing a string as the parameter. Sections can be collapsed and expanded in the console, but you can also have a section collapsed by default, by using groupCollapsed instead of group. You can also nest sub-sections within sections but be sure to remember to close out each group with groupEnd.

The following example will output an open section, containing some info'URL Info');
  console.log('Protocol', window.location.protocol);
  console.log('Host', window.origin);
  console.log('Path', window.location.pathname);
  console.groupCollapsed('Meta Info');
    console.log('Date Fetched', new Date().getTime());
    console.log('OS', navigator['platform']);
    console.log('Browser', navigator['appCodeName']);
    console.log('Language', navigator['language']);

Screenshot showing an example output of

Styled Logs

It's possible to style your log outputs with some basic CSS, such as colors, fonts, text styles and sizes. Note that browser support for this is quite variable.

For example, try running the following:

  '%cHello World!',
  'color: #f709bb; font-family: sans-serif; text-decoration: underline;'

You should get the following output:

Screenshot showing an example using CSS in the console

Pretty cool, huh? Well there's a lot more you can do too!
Maybe change the font, style, background color, add some shadows and some curves...

Screenshot showing an example using CSS in the console

Here's something similar I'm using in a developer dashboard, the code is here

Screenshot showing an example using CSS in the console


Another common debugging technique is measuring execution time, to track how long an operation takes. This can be achieved by starting a timer using console.time() and passing in a label, then ending the timer using console.timeEnd(), using the same label. You can also add markers within a long running operation using console.timeLog()

let output = "";
for (var i = 1; i <= 1e6; i++) {
  output += i;
concatenation: 1206ms - timer ended

Screenshot showing an example output of console.time

There's also a non-standard method, console.timeStamp() which adds markers within the performance tab, so you can correlate points in your code with the other events recorded in the timeline like paint and layout events.


You may only want to log to the console if an error occurs, or a certain condition is true or false. This can be done using console.assert(), which won't log anything to the console unless the first parameter is false.

The first parameter is a boolean condition to check, followed by 0 or more data points you'd like to print, and the last parameter is a message to output. So console.assert(false, 'Value was false') will output the message, since the first parameter is false.

const errorMsg = 'the # is not even';
for (let num = 2; num <= 5; num++) {
  console.log(`the # is ${num}`);
  console.assert(num % 2 === 0, { num }, errorMsg);

Screenshot showing an example output of console.assert


Ever find yourself manually incrementing a number for logging? console.count() is helpful for keeping track how many times something was executed, or how often a block of code was entered.

You can optionally give your counter a label, which will let you manage multiple counters and make the output clearer.
Counters will always start from 1. You can reset a counter at anytime with console.countReset(), which also takes an optional label parameter.

The following code will increment the counter for each item in the array, the final value will be 8.

const numbers = [1, 2, 3, 30, 69, 120, 240, 420];
numbers.forEach((name) => {

The following is an example output of labelled counters.

Screenshot showing an example output of console.count

Instead of passing in a label, if you use a value, then you'll have a separate counter for each conditions value. For example:

console.count(NaN);         // NaN: 1
console.count(NaN+3);       // NaN: 2
console.count(1/0);         // Infinity: 1
console.count(String(1/0)); // Infinity: 2


In JavaScript, we're often working with deeply nested methods and objects. You can use console.trace() to traverse through the stack trace, and output which methods were called to get to a certain point.

Screenshot showing an example output of console.trace

You can optionally pass data to also be outputted along with the stacktrace.


If your logging a large object to the console, it may become hard to read. The console.dir() method will format it in an expandable tree structure.

The following is an example of a directory-style console output:

Screenshot showing an example output of console.dir

You can also print XML or HTML based trees in a similar way, by using console.dirxml().


You may have some logging set up within your app, that you rely on during development, but don't wish the user to see. Replacing log statements with console.debug() will do just this, it functions in exactly the same way as console.log but will be stripped out by most build systems, or disabled when running in production mode.

Log Levels

You may have noticed that there's several filters in the browser console (info, warnings and error), they allow you to change the verbosity of logged data. To make use of these filters, just switch out log statements with one of the following:

  • - Informational messages for logging purposes, commonly includes a small "i" and / or a blue background
  • console.warn() - Warnings / non-critical errors, commonly includes a triangular exclamation mark and / or yellow background
  • console.error() - Errors which may affect the functionality, commonly includes a circular exclamation mark and / or red background

In Node.js different log levels get written to different streams when running in production, for example error() will write to stderr, whereas log outputs to stdout, but during development they will all appear in the console as normal.

Multi-Value Logs

Most functions on the console object will accept multiple parameters, so you can add labels to your output, or print multiple data points at a time, for example: console.log('User: ',;

But an easier approach for printing multiple, labelled values, is to make use of object deconstructing. For example, if you had three variables (e.g. x, y and z), you could log them as an object by surrounding them in curly braces, so that each variables name and value is outputted - like console.log( { x, y, z } );

Screenshot showing an example output of console deconstructing

Log String Formats

If you need to build formatted strings to output, you can do this with C-style printf using format specifiers.

The following specifiers are supported:

  • %s - String or any other type converted to string
  • %d / %i - Integer
  • %f - Float
  • %o - Use optimal formatting
  • %O - Use default formatting
  • %c - Use custom formatting (more info)

For example

console.log("Hello %s, welcome to the year %d!", "Alicia", new Date().getFullYear());
// Hello Alicia, welcome to the year 2022!

Of course, you could also use template literals to achieve the same thing, which might be easier to read for shorter strings.


Finally, when you're looking for an output from an event, you might want to get rid of everything logged to the console when the page first loaded. This can be done with console.clear(), which will clear all content, but nor reset any data.

It's usually also possible to clear the console by clicking the Bin icon, as well as to search through it using the Filter text input.

And some more...

There's so much more that you can do with logging to the console! For more info, check out the MDN console Documentation or the Chrome Dev Console Docs.

Just a quick note about best practices...

  • Define a lint rule, to prevent any console.log statements from being merged into your main branch
  • Write a wrapper function to handle logging, that way you can enable / disable debug logs based on environment, as well as use appropriate log levels, and apply any formatting. This can also be used to integrate with a third-party logging service with code updates only needed in a single place
  • Never log any sensitive info, the browser logs can be captured by any installed extensions, so should not be considered secure
  • Always use the correct log levels (like info, warn, error) to make filtering and disabling easier
  • Follow a consistent format, so logs can be parsed by a machine if needed
  • Write short, meaningful log messages always in English
  • Include the context or category within logs
  • Don't overdo it, only log useful info

GitHub Markdown Tricks πŸ™

A collection of 15 things you can do with GitHub-flavoured markdown to spice-up your projects readme.

Centre an image or other content

Make the title, sub-title and / or logo image centred (see example)

<p align="center">
  <img width="500" src="---" alt="---">

Include Notes and Warnings

> **Note**
> This is a note

> **Warning**
> This is a warning

Will render:

Note that this feature is still in beta.

Insert Keyboard Keys

Great for visually showing which keys to press, or drawing attention to something (see example)


Collapsible Sections

Useful for including long blocks of text (like logs or API response), without clogging up your page (see example)

    <summary>Collapsable Title</summary>
    <p>Put Content Here</p>

Note that to include markdown within the <p></p> content block, you must pad it with blank lines, both top and bottom

Embedding a Video

You can embed an MP4, simply by including the URL to the raw video in your markdown file (see example)

Adding Title to Links

You can add a title to a link, which will be displayed when the user hovers over it. This is done by surrounding it in quotes immediately after the URL

[DuckDuckGo]( "A great search engine for privacy").

GitHub-Specific Emojies

In GitHub flavoured markdown, emojis can be specified by their shortcode (e.g. :nerd_face:), here's a cheatsheet

But there are also some GitHub-specific emojis, including:

  • Octocat :octocat: β†’ octocat
  • Bowtie :bowtie: β†’ bowtie
  • Neck Beard :neckbeard: β†’ neckbeard
  • Troll Face :trollface: β†’ trollface
  • Ship It :shipit: β†’ shipit
  • Suspect :suspect: β†’ suspect

And a few more...

  • :rage1: β†’ rage1 :rage2: β†’ rage2 :rage3: β†’ rage3 :rage4: β†’ rage4
  • :hurtrealbad: β†’ hurtrealbad :goberserk: β†’ goberserk :finnadie: β†’ finnadie :feelsgood: β†’ feelsgood
  • :atom: β†’ atom :basecamp: β†’ basecamp :basecampy: β†’ basecampy :electron: β†’ electron

Moving Readme

If it bothers you having your file cluttering up your projects root directory, then you can place it in .github/ (along with any other GH stuff or assets). It will still be rendered as normal in your repositories home, but now you've got one less file in your project's base directory. (see example)

Including the code quote symbol (`) in a code block

Sometimes you may need to use the ` symbol within a code block.

Just wrap the code with double quotes ``, and ensure there is a space around the quote symbol

(here, I'm including quotes inside quotes inside quotes!)

Single-Cell Tables

This is a great way to draw attention to an important message

Hello World!

Creating Home Page .md

You can display a short markdown document at the top of your GitHub profile. To do so, just create a public repository with the same name as your GitHub username, and populate it with a non-empty For more info, see the Profile Readme Docs, or (see example)

Syntax Highlighting in Code Blocks

Code blocks become much easier to read with syntax highlighting. To use this, you must specify the language immediatley after the first ```.

For example:

const name = 'Alicia';

You can view a full list of supported languages, here

Showing Code Additions (or Deletions)

Set the language type to diff, and then precede each line which indicates an addition with +, or a - if it's a deletion. Note that the sign must be the first character of the line (watch out for white space), and should be followed by at least one blank space/ tab.

Create Buttons

In GitHub-flavoured markdown, the <kbd> tags also make great buttons. for your readme header.

For example:


**[<kbd> <br> Install <br> </kbd>][Install]** 
**[<kbd> <br> Quick Start <br> </kbd>][Quick Start]** 
**[<kbd> <br> Configure <br> </kbd>][Configure]** 
**[<kbd> <br> Contribute <br> </kbd>][Contribute]**


Small Text

Both superscript (top of line, like this) and subscript (bottom of line, and this) are supported.
Just use the <sup></sup> or <sub></sub> tags.

You can wrap these elements with non-HTML markers (like bullet lists, tables or titles) too. (see example)

Side-Aligned Images

In a similar way to centering images, you can also right align them, where the text will flow down the left-side. An example of this can be seen in the tuxi repo. (See example)

<img src="" alt="Video Preview Gif" align="right" width="500px"/>

Merging Cells in Tables

It is possible to merge cells in tables (so that a given cell spans multiple rows or columns), but only if you define your table in HTML (see example).

This is done by adding the colspan or rowspan attribute, with the number of cells horizontally or vertically that it should span.

For example:

 <td colspan="2">I take up two columns!</td>

You can also set the text align property here too, using align="center" to centre text.

Further Links

Comparison of Private / Secure Emai Providers πŸ“¬

The following table is a quick, undetailed comparison of security-focused email providers.

Update: Since the markdown table isn't very clear, here's is a web version:

Name Jurisdiction Encryption Open Source Onion Site Pricing Domain Support Additional Aliases or Catch-All POP, IMAP, STMP External Security Audit Accepts Crypto Personal Info Requiements
ProtonMail 🟒 Switzerland 🟒 PGP 🟒 Yes 🟒 Yes 🟒 Free Plan - 500MB, 1 address, no custom domain. Paid plans start from €5 and allow for additional features, custom domain, and increased message volume 🟠 Yes (Pluss Plan, €5.00/m) 🟠 Yes (Professional Plan, €8.00) 🟠 Yes, but through Bridge 🟒 Yes (2021, by Securitum) 🟒 Yes (BTC only) 🟒 Recovery Email Only
Tutanota 🟒 Germany (14 Eyes) 🟠 Hybrid AES + RSA 🟠 Client Apps Only πŸ”΄ No 🟒 Free Plan - 1GB, 1 address, no custom domain. Paid plans start from €1 / month, and allow for a custom domain, 5 alias addresses and improved search and filters 🟠 Yes (Premium Plan, €1.00/m) 🟠 €1 / month per 20 aliases. No catch-all πŸ”΄ No 🟠 Apparently, but not published 🟠 No (But ProxyStore gift cards accepted 🟒 None
CriptText βšͺ (Decentralized) 🟠 Signal Protocol 🟒 Yes βšͺ N/A (no webmail) 🟒 Free 🟒 Yes πŸ”΄ No πŸ”΄ No πŸ”΄ No βšͺ N/A (no payment required) 🟒 None
Mailfence 🟒 Belgium (14 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟒 Free Plan - 500MB, 1 address, no custom domain. Paid plans start from €2.50 and allow for custom domain, 10 aliases, mail client access and increased message volume 🟠 Yes (Entry Plan, €2.50/m) 🟠 Yes (Entry Plan, €2.50) 🟒 Yes (IMAP, POP3, SMTP) πŸ”΄ No πŸ”΄ No (Card, PayPal) 🟒 Recovery Email Only 🟒 Germany (14 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟠 No free plan. Starts at €1 / month. Increases to €9 for all features, and increased storage 🟠 Yes (Standard Plan, €3.00/m) 🟠 25 aliases (Standard Plan). No catch-all 🟒 Yes (IMAP, POP3) πŸ”΄ No πŸ”΄ No πŸ”΄ Requires full name, location, mobile number and a recovery email
Soverin 🟠 Netherlands (9 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟠 No free plan, but flat fee of €3.25 / month for everything 🟠 Yes (Paid Plan, €3.25) 🟠 Yes (Paid Plan, €3.25) 🟒 Yes (IMAP) πŸ”΄ No πŸ”΄ No πŸ”΄ Requires phone number
Posteo 🟒 Germany (14 Eyes) 🟒 PGP 🟒 Yes πŸ”΄ No 🟠 No free plan. Pricing is €1 / month πŸ”΄ No 🟠 Yes (€0.10/m per alias). No catch-all 🟒 Yes (IMAP, POP3) πŸ”΄ No πŸ”΄ No (Card, PayPal, Bank Transfer, Cash) 🟒 Payment Info Only
Runbox 🟒 Norway (14 Eyes) 🟒 PGP 🟠 Client Apps Only πŸ”΄ No 🟠 No free plan. Starts at €14.95 / year for a single domain, going up to €69.95 / year for more storage and domains 🟠 Yes (Micro Plan, €1.25/m) 🟠 €3.95 / year per 5 aliases. No catch-all 🟒 Yes (IMAP, POP, SMTP) πŸ”΄ No 🟒 Yes (BTC only) 🟠 Full name, recovery email
Kolab Now 🟒 Switzerland 🟒 PGP 🟒 Yes πŸ”΄ No 🟠 No free plan. Starts at CHF 5.00 / mo, basic features only. Groupware account is CHF 9.90 🟠 Yes (Group Plan, CHF 9.90/m) 🟠 Yes (Group Plan Only (CHF 9.90/ m) 🟒 Yes (IMAP, POP, SMTP) πŸ”΄ No πŸ”΄ No (Card, PayPal, Bank Transfer) 🟠 Full name, recovery email
CounterMail 🟒 Sweden (14 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟠 No free plan. Pricing is $4.83 / month 🟠 Yes (Pain Plan + $15 setup fee) 🟠 Yes, max 10 text aliases. No catch-all 🟒 Yes (IMAP) πŸ”΄ No 🟒 Yes (BTC only) 🟒 None
StartMail 🟠 Netherlands (9 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟠 No free plan. Starts at $5 / month, but without custom domain support 🟠 Yes (Custom Domain Plan, $5.85) 🟠 Unlimited aliases. No catch-all 🟒 Yes (IMAP, SMTP) 🟠 Apparently, but not published 🟠 Yes (BTC only, but only personal accounts) 🟠 Full name, recovery email
Disroot 🟠 Netherlands (9 Eyes) 🟠 PGP (Off-by-default) πŸ”΄ No πŸ”΄ No 🟒 Free, with payment required for each additional feature 🟠 Yes (Donation required) πŸ”΄ No 🟒 Yes (IMAP, POP, SMTP) πŸ”΄ No 🟒 Yes (BTC only) 🟒 None
Hushmail πŸ”΄ Canada (5 Eyes) 🟒 PGP πŸ”΄ No πŸ”΄ No 🟠 No free plan. Starts at $5.99 / month, rising to $7.99 for email archiving support 🟠 Yes (Small Business Plan, $5.99/m) 🟠 Yes (Small Business Plan, $5.99/m) 🟒 Yes (POP, SMTP) πŸ”΄ No πŸ”΄ No 🟠 Full name, recovery email
LavaBit πŸ”΄ United States (5 Eyes) 🟠 DIME πŸ”΄ No πŸ”΄ No 🟠 Free plan, but invite only. Pricing ranges from $30 - $60 / year, depending on storage requirements πŸ”΄ No πŸ”΄ No πŸ”΄ No πŸ”΄ No πŸ”΄ No 🟠 Full name, recovery email

To make changes to the above list, submit a pull request to

See Also

One-Line Web Server πŸ–₯️

The following commands will each start a simple web server, serving up the files in the current directory.
Just open up the browser, and navigate to the system's IP + port (e.g. http://localhost:8080).


python -m http.server 8000


npx http-server ./ --port 8080


php -S


ruby -run -e httpd ./ -p 8080


Rscript -e 'servr::httd()' -p8080


Caddy is a feature-rich production-ready Go-based web server, with easy configuration. Just download and use something like the following command.

caddy file-server

Rust (with miniserve)

cargo install miniserve
miniserve -p 8080 .


busybox httpd -f -p 8080

You can also share the server with someone remotely,
see: Using Ngrok to expose server to the internet

Dashy - A Self-Hosted Home Lab Dashboard πŸš€

Here's a quick project that I built in order to keep track of locally running services on my home lab. It serves as a landing page, to make it easier to navigate to various apps, without having to remember and type IP addresses or URLs.



  • Instant search by name, domain and tags - just start typing
  • Full keyboard shortcuts for navigation, searching and launching
  • Multiple color themes, with easy method for adding more
  • Customizable layout options, and item sizes
  • Quickly preview a website, by holding down the Alt key while clicking, to open it in a resizable pop-up modal
  • Many options for icons, including full Font-Awesome support and the ability to auto-fetch icon from URLs favicon
  • Additional info for each item visible on hover (including opening method icon and description as a tooltip)
  • Option for full-screen background image, custom nav-bar links, and custom footer text
  • User settings stored in local storage and applied on load
  • Encrypted cloud backup and restore feature available
  • Easy single-file YAML-based configuration
  • Small bundle size, fully responsive UI and PWA makes the app easy to use on any device
  • Plus lots more...

Source Code

Source, on GitHub:

Live Demo

Demo 1 ┆ Demo 2 ┆ Demo 3


Usage Guide

For full setup instructions, see this post, or follow the GitHub readme.


Get the code: git clone and cd dashy
Then install the dependencies: yarn


All settings are specified in ./public/conf.yml. You can see a full list of options in the docs, or modify one of these example configs


First build the project, with yarn build, you can then run yarn start to run it. Alternatively use Docker, with docker run -it -p 8080:80 --rm --name my-dashboard lissy93/dashy


Running yarn dev will build, test, lint then start the development server and watch for changes

Similar Apps / Alternatives

There are a few self-hosted web apps, that serve a similar purpose to Dashy. Including, but not limited to: Dashboard, Dash Machine, Heimdall, HomeDash2, Homepage, Homer, Organizr and Simple-Dash


The app makes use of the following components, kudos to their respective authors

And the app itself is built with Vue.js


Licensed under MIT X11, Β© Alicia Sykes 2021:

Using Espanso to boost Efficiency 🚀


Espanso is an open source, privacy-first, cross-platform text expander developed by @federico-terzi and written in Rust. In short, it detects when you type a certain keyword, and replaces it on the fly with a pre-defined string or dynamic output.

Espanso not only supports simple text replacement/ expansion, but also images, custom scripts and shell commands, app-specific configurations and more. There is also a basic form feature, enabling arguments to be passed to a block. It's under active development, so hopefully there will be even more functionality implemented in the future.

It uses a file-based configuration, written entirely in YAML (but I think there is a GUI in development), and for the most part is quick and easy to it it configured exactly to your liking. But you can also use pre-built packages, installed via Espanso Hub (or any external source).

There are many possibilities where Espanso can be really useful, but the main areas that I am using it for are:

  • Quickly typing characters that do not appear on my keyboard (such as math symbols, foreign language characters and emojis)
  • Easily inserting longer strings that would otherwise have required many keystrokes
  • Inserting dynamic content, such as the output of a script, response from an API call, or time/ date info
  • Making typing easier, with a custom spelling and grammar auto-correct system

Espanso Links: Docs, Reddit, Package Hub, Source Code, Quick Start, Author's Site

I'm still working on my config, but for reference here it is:

Use Cases

Easy Emoji Inputs

The first thing I used Espanso for was being able to type emojis, without having to use wait for a popup to load or use the internet.

There is a plugin that does exactly this perfectly, called espanso-all-emojis, by @FoxxMD, using gemoji. It can be installed with:
espanso install all-emojis

Then just type the name of the emoji, surrounded by colons. For example:
:smile: --> πŸ˜„, :rocket: --> πŸš€, :milky way: --> 🌌

For reference, here is the full list emojis, along with their shorthand code

The next thing I wanted to do, was be able to easily insert old-school ASCII emoticons or Lenny faces. This could be done with a similar method, but I didn't want to have to remember all the key combinations. A perfect opportunity to give Espanso's form feature a go!

With the above code, typing :lenny will open up a form with a dropdown, using the arrow keys I can now select an option, hit enter and it will be inserted

Espanso Lenny Demo

# Easily inputs ASCII emoticons from a dropdown
- trigger: :lenny
  form: "{{smileys}}"
      type: choice
      - 'Β―\\_(ツ)_/Β―'
      - '(β•―Β°β–‘Β°οΌ‰β•―οΈ΅ ┻━┻'
      - '( Ν‘ΰ²  Κ–Μ― Ν‘ΰ² )'
      - 'β˜‰ β€Ώ βš†'
      - 'Κ•β€’α΄₯β€’Κ”'
      - 'β‹†ο½‘Λš ☁︎ Λšο½‘β‹†ο½‘Λšβ˜½Λšο½‘β‹†'
      - '(γ₯α΅”β—‘α΅”)γ₯'
      - '|α΅”β€Ώα΅”|'
      - '“(*﹏*)‏'
      - 'ツ'

Inserting Dynamic Content

Espanso has a series of built in extensions, that are able to insert dynamic data, either from a command, script, web address or API

An example of how this can be useful, is for fetching your current public IP address, using

# Outputs public IP address
- trigger: ":ip"
  replace: "{{output}}"
    - name: output
      type: shell
        cmd: "curl ''"

Or the current weather in your location, using

# Outputs the current weather for your location
- trigger: ":weather"
  replace: "{{output}}"
    - name: output
      type: shell
        cmd: "curl ''"

Easily insert the MIT license:

# Outputs full MIT license text, from GitHub
- trigger: :mit-long
  replace: "{{output}}"
  - name: output
    type: shell
      cmd: "curl ''"

Generating Deterministic Passwords on the Fly

LessPass is a stateless password manager, given a set of arguments (usually site, username and master pass) the output will always be the same, omitting the need to store passwords anywhere. I use it for less important accounts, and this sounded like another great use case for Espanso.

- trigger: :pass
  replace: "{{lesspass}}"
  - name: "params"
    type: form
      layout: |
        Less Pass Generator
        Website: {{site}}
        Login: {{login}}
        Master Password: {{pass}}
  - name: lesspass
    type: shell

With the above block, you can type :pass, and a form will popup, prompting you for the three arguments, and on submit a password will be returned and auto-filled. This does of course require the LessPass CLI tool to be installed.

Quickly Closing Brackets

This is a small one, saving only a single key press, but over time it all adds up. In Espanso, you can specify where the cursor should be placed using $|$

So typing a colon : followed by any type of bracket, tag or formatting symbol, will result in the corresponding closing bracket will be filled, and the cursor will be moved conveniently middle of the parenthesis.
This works for (), [], {}, <>, ` `, '', "", __, -- and **

# Auto close brackets, quotes and modifiers, putting cursor in the center
- trigger: ':('
  replace: '($|$)'
- trigger: ':['
  replace: '[$|$]'
- trigger: ':{'
  replace: '{$|$}'
- trigger: ':<'
  replace: '<$|$>'
- trigger: ':`'
  replace: '`$|$`'
- trigger: ":\'"
  replace: "\'$|$\'"
- trigger: ':"'
  replace: '"$|$"'
- trigger: ':_'
  replace: '_$|$_'
- trigger: ':*'
  replace: '*$|$*'
- trigger: ':-'
  replace: '-$|$-'

Date / Time Info

Another handy feature, is the built-in date extension. For the format of the date, see the chrono::format::strftime Docs.

# Outputs todays date (dd/mm/yy)
- trigger: :date
  replace: "{{date}}"
  - name: date
    type: date
      format: "%d/%m/%y"

# Outputs the current time (24hr)
- trigger: :time
  replace: "{{time}}"
  - name: time
    type: date
      format: "%H:%M"

# Outputs the month and year (e.g. January 2020)
- trigger: :month
  replace: "{{date}}"
  - name: date
    type: date
      format: "%B %Y"

Inserting Links

This is handy if you find yourself often sharing links in forums, or pasting them in documents. It makes use of Espanso's handy built-in Clipboard Extension, to get the URL that has been copied.

This works for Markdown with :md-link, HTML with :html-link and BB Code with :bb-link.

# Outputs markdown link, with clipboard contents as the URL
- trigger: ":md-link"
  replace: "[$|$]({{clipboard}})"
  - name: "clipboard"
    type: "clipboard"

# Creates a HTML anchor element, with clipboard contents as href
- trigger: ":html-link"
  replace: "<a href=\"{{clipboard}}\" />$|$</a>"
  - name: "clipboard"
    type: "clipboard"

# Outputs BB Code link, with clipboard contents as the URL
- trigger: ":bb-link"
  replace: "[url={{clipboard}}]$|$[/url]"
  - name: "clipboard"
    type: "clipboard"

For example, say you's copied have and ran :html-link is would return <a href="" /></a>, with the cursor in the middle, ready for the title.

Auto-Correct Typos

This is certainly the task that I use Espanso for most! And I have previously written a post outlining this.

If you're interested in doing this, I prepared a list of 4,200 of the most commonly misspelled words from Wikipedia, presented in AHK format, and wrote a quick script to convert it to Espanso YAML.

I personally just use the 250 words that I most often mistype / spell. The format looks like this (below), and my full script is here

- trigger: acsent
  replace: accent
  propagate_case: true
  word: true
- trigger: advesary
  replace: adversary
  propagate_case: true
  word: true

The word will not update until a terminator character (such as space or enter) is pressed (defined by word: true). The case will be propogated, (because propagate_case: true is set), so the output will match the case of the original word (either lower-case, upper-case or capitalized)

Inserting Common HTML and Markdown Elements

A simple one, if you find yourself often typing the symbols required for DOM elements, then this can save a bit of time.

Common tags, like :hr, :br, :div, :span, :para, :h1, :h2 etc are autofilled, with the cursor placed inside the tag ready for the value. For custom web components and XML tags, use :tag, and a form will open, where you can type the name of the element

Right now, for markdown, all I have is :md-code to insert a code block, and :md-collapse will in the very annoying <details><summary>, and again place the cursor inside.

# Inserts common HTML elements
- trigger: :hr
  replace: '<hr />'
- trigger: :br
  replace: '<br />'
- trigger: :div
  replace: '<div>$|$</div>'
- trigger: :span
  replace: '<span>$|$</span>'
- trigger: :h1
  replace: '<h1>$|$</h1>'
- trigger: :h2
  replace: '<h2>$|$</h2>'
- trigger: :h3
  replace: '<h3>$|$</h3>'
- trigger: :para
  replace: '<p>$|$</p>'

# Inserts any custom HTML, XML or web component tag 
- trigger: ":tag"
  replace: "<{{html.element}}>$|$</{{html.element}}>"
  - name: "html"
    type: form
      layout: "XML / HTML Element Inserter\nTag Name: {{element}}"
      fields: { element: { type: text }}

# Inserts a markdown code block
- trigger: :md-code
  replace: "```\n$|$\n```"

# Inserts markdown collapsable section
- trigger: :md-collapse
  replace: "\n<details>\n\t<summary>$|$</summary>\n\t<p></p>\n</details>"

Inserting Personal Info

There are several things that I find I need to type quite often, for various reasons. For example, email addresses, phone numbers, social media links, address and other important details. For some of this, I just use shortcuts (e.g. :addr outputs my address), whereas for other tasks I use dropdowns.

For example, to insert a social media profile link, without having to remember different shortcuts for different services, I just type :social. I do the same thing with email addresses and project websites

# Inserts the URL to a selected website or social media platform
- trigger: ":social"
  replace: "{{social.links}}"
  - name: "social"
    type: form
      layout: "Social Media Profiles \n{{links}}"
          type: choice
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''
          - ''

Formulating Search Queries

There are browser extensions and web services that do this already (like DuckDuckGo bangs), but it's often useful to search a specific website, without having to first navigate to it. This function will formulate the URL, with the correct parameters ready for searching. You can also use Ctrl + L to focus the address bar.

For example, :srch-wiki will output You can also search with the contents of your clipboard (swc), where the query will be automatically filled.

# Quick search, formulates the URL params for searching a given website
- triggers: [:srch-ddg, :search-duckduckgo]
  replace: ''
- triggers: [:srch-wiki, :search-wikipedia]
  replace: ''
- triggers: [:srch-gh, :search-github]
  replace: ''
- triggers: [:srch-so, :search-stackoverflow]
  replace: ''
- triggers: [:srch-dh, :search-dockerhub]
  replace: ''
- triggers: [:srch-wa, :search-wolframalpha]
  replace: ''
- triggers: [:srch-red, :search-reddit]
  replace: ''
- triggers: [:srch-bbc, :search-bbc]
  replace: ''
- triggers: [:srch-vt, :search-virustotal]
  replace: ''
- triggers: [:srch-amz, :search-amazon]
  replace: ''
- triggers: [:srch-yt, :search-youtube]
  replace: ''
- triggers: [:srch-maps, :search-maps]
  replace: ''
- triggers: [:srch-goo, :search-google]
  replace: ''

# Similar to above, but it uses the clipboard for the search query
- trigger: ":swc-ddg"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wiki"
  replace: "'{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-gh"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-so"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-dh"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-wa"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-red"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-bbc"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-vt"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-amz"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-yt"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-maps"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]
- trigger: ":swc-goo"
  replace: "{{clipboard}}"
  vars: [{ name: "clipboard", type: "clipboard"}]

Additional Notes

This is just a tiny tiny selection of things you could use Espanso for, the possibilities are almost endless, and I keep finding new ways to use it to speed up my typing. I'm excited about the future of the project, as new features and improvements are being added all the time.

Huge kudos to the author, Federico Terzi, who has done the bulk of the work himself.


This is worth mentioning, as I am sure others will be wondering about it. Initially I was fearful of a system that could apparently intercept all of my keystrokes, but the author has highlighted that it has been built with security in mind, there is absolutely no logging, and Espanso has a memory of just 3 characters (in order for the backspace functionality to work). There's also no network requests, and I verified this myself, both in the source, any by running Wireshark.
The code is also extremely efficient, written in Rust, it uses virtually negligible system resources, even on a low-spec PC.

My Life in Months πŸ—“οΈ

Do you ever wonder how you're spending you're life? I do, and so I went through the main activities that I do on a daily, weekly or monthly basis and calculated the approximate total time I've spent on each of them. The following chart is a breakdown of time as a proportion of my total life (so far), where each square represents 1 month.

My Life in Months

I am now having an mini existential crisis after seeing how much of my life I have spent on relatively meaningless activities!

Quick How-To Guides πŸ’«

A mix of simple things that (despite doing regularly) I still forget, as well as more niche stuff that took me a little while to figure out. The purpose of documenting this, is both to help others and for future reference for myself




Code Management

See Also

SSH Tarpit with EndleSsh πŸͺ€πŸ•³οΈ

Endlessh is an SSH tarpit to keep the automated bots hitting port 22 locked up, and waste script kiddies time.

You can either get it from your package manager with sudo apt install endless, or build from source. To build, ensure you have libc6-dev installed, then git clone, cd endlessh, make, and move it to your path- sudo mv endlessh /usr/local/bin/.

Move the service to systemd, sudo cp util/endlessh.service /etc/systemd/system, and enable it sudo systemctl enable endlessh. Next, specify the configuration mkdir /etc/endlessh and sudo vim /etc/endlessh/config
Add your desired config, e.g.

Port 22
Delay 10000
MaxLineLength 32
MaxClients 4096
LogLevel 0
BindFamily 0

If you're using a low port number, ensure you update the endlessh.service with AmbientCapabilities=CAP_NET_BIND_SERVICE, and run sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/endlessh.

Finally, run sudo systemctl start endlessh to start the service, you should now see the service running on your specified port when you run netstat -tulpn | grep endlessh. If you need to check the logs, run sudo journalctl -u endlessh

Pimping up Your DuckDuckGo Search Results πŸ’„

Yet another awesome feature of DuckDuckGo, is that they make it really easy to modify your theme, just go to: From here you can customize your colors, fonts and layout of your search results and home page.

I am no designer by any stretch of the imagination (as you can probably see!), but here are a couple of themes I made, along with their code if you want to use them. You can preview themes live without making any changes by clicking the link below each screenshot, or to apply a theme, see the JS snippet at the end of this post.

Settings in DDG can either be applied temporarily with DuckDuckGo's URL parameters, locally as cookies, or globally using DDG's Cloud Save feature.


Screenshot - Navy Turquoise

Try it Out!

Color Palette: #0b1021, #080813, , #00af87, #0a7355, #d3d5e5, #a8d3ff


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"a8d3ff", "kaa":"0a7355", "kj":"080813", "k9":"00af87", "k18":"1", "ka":"Hack", "k8":"d3d5e5", "k21":"080813", "k7":"0b1021", "kt":"v"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; 18=1; aa=0a7355; x=a8d3ff; 8=d3d5e5; 9=00af87; j=080813; 7=0b1021; 21=080813; a=Hack; t=v


Screenshot - Titanium

Try it Out!

Color Palette: #dedede, #9b83db, #000000


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"000000", "kaa":"9b83db", "kj":"9b83db", "k9":"9b83db", "k18":"1", "k8":"000000", "k21":"9b83db", "k7":"dedede", "kt":"b", "ku":"1", "ka":"Arial Rounded MT Bold"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; u=1; 18=1; j=9b83db; x=000000; 7=dedede; 8=000000; aa=9b83db; 9=9b83db; 21=9b83db; t=b; a=Arial%20Rounded%20MT%20Bold


Screenshot - Cyberpunk

Try it Out!

Color Palette: #101116, #ff0055, #9254b5, #785eef, #fffc58


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"FFFC58", "kaa":"9254b5", "kj":"FF0055", "k9":"FF0055", "k18":"1", "ka":"Cyberpunk", "k8":"785eef ", "k21":"FFFC58", "k7":"101116", "kt":"e"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; 8=785eef%20; aa=9254b5; x=FFFC58; 18=1; j=FF0055; 21=FFFC58; 7=101116; 9=FF0055; a=Cyberpunk; t=e


Screenshot - Dracula

Try it Out!

Credit: This theme was inspired by Dracula


{"kae":"t", "ks":"m", "kw":"n", "ko":"s", "ku":"-1", "ky":"44475a", "k7":"282a36", "k8":"f8f8f2", "k9":"50fa7b", "kt":"p", "km":"l", "kj":"282a36", "ka":"p", "kaa":"bd93f9", "kx":"f1fa8c", "kaf":"1", "kai":"1", "kf":"1"}

Cookie Data

ae=t; s=m; w=n; o=s; u=-1; y=44475a; 7=282a36; 8=f8f8f2; 9=50fa7b; t=p; m=l; j=282a36; a=p; aa=bd93f9; x=f1fa8c; af=1; ai=1; f=1


Screenshot - Hack

Try it Out!

Color Palette: #101116, #070709, #00ff2b, #d1d1d1, #fffc58, #118b25, Font: Courier


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"FFFC58", "kaa":"0cbd2b", "kj":"070709", "k9":"00ff2b", "k18":"1", "ka":"Courier New", "k8":"d1d1d1", "k21":"118b25", "k7":"101116", "kt":"Courier"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; j=070709; x=FFFC58; 18=1; 7=101116; 9=00ff2b; aa=0cbd2b; 21=118b25; 8=d1d1d1; t=Courier; a=Courier%20New


Screenshot - Neon

Try it Out!

Color Palette: #261d49, #2a1f48, #df95ff, #9254b5, #1bccfd, #21f6bc, Font: Hack


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "ka":"Hack", "k7":"261d49", "k8":"1bccfd", "k21":"2a1f48", "k18":"1", "kx":"21f6bc", "kaa":"9254b5", "kj":"2a1f48", "k9":"df95ff"}

Cookie Data

5=1; ay=b; bc=1; ae=d; j=2a1f48; a=Hack; 18=1; aa=9254b5; 7=261d49; 9=df95ff; 8=1bccfd; 21=2a1f48; x=21f6bc


Pale grey and dusty pastel

Screenshot - Nord

Try it Out!

Color Palette: #2e3440, #404855, #81a1c1, #87c0d0, #b28ead


{"kae":"d", "k5":"1", "kay":"b", "kbc":"1", "kax":"v261-7", "kx":"b28ead", "kaa":"87c0d0", "kj":"404855", "k9":"#81a1c1", "k18":"1", "ka":"Courier New", "k8":"#81a1c1", "k21":"#81a1c1", "k7":"2e3440", "kt":"h"}

Cookie Data

5=1; ay=b; bc=1; ae=d; ax=v261-7; a=Courier%20New; 7=2e3440; 18=1; 9=81a1c1; 8=81a1c1; aa=87c0d0; x=b28ead; 21=81a1c1; j=404855; t=h


There are three different methods of applying themes: Using cookies, URL parameters or DDG's cloud store

For cookies, settings can be applied programmatically with JavaScript directly through the browser console (or using a dev tool or third-party extension). Settings are specified as individual cookies, with a single string identifier and a corresponding value. The following is a quick script to apply settings easily, just replace ddg_cookie_input with your desired data (or use one of the examples above). Note that you must be on the DuckDuckGo domain for this to work.

// Converts DDG cookie string into formatted JSON
const makeCookieData = (ddg_cookie_input) => {
    let ddg_json = {};
  const items = ddg_cookie_input.split(/[ ,]+/);
    let parts = item.split('=');
    ddg_json[parts[0]] = parts[1];
  return ddg_json;

// Iterates over JSON, and adds to browser cookie store
const setCookies = (ddg_json) => {
  Object.keys(ddg_json).forEach(function(key) {

// Paste your cookie data here
const ddg_cookie_input = `5=1; ay=b; bc=1; ae=d; ax=v261-7; 18=1; aa=0a7355; x=a8d3ff; 8=d3d5e5; 9=00af87; j=080813; 7=0b1021; 21=080813; a=Hack; t=v`;

// Call set cookies, passing in formated cookie data

// All done, reload page for changes to take effect :)

This is handy, because once you've got DDG setup just how you like, you can make note of these values, and then easily apply them to any other system or browser with a single command.

If you would rather not set cookies, then you can use URL GET parameters (but note that the identifiers are different, see the full list of options here). You can find pre-formatted URL under Settings --> Appearance --> Show Bookmarklet and Settings Data. Here you can also enable cloud save, where you pick a password which is encoded into a URL so that you can access your setup on a different browser/ device.

Alternatively, if you're already using TamperMonkey, then you can manage this with JavaScript. Similarly if you're comfortable with CSS, then you have a lot more flexibility, and extensions like Stylish can make it easy to manage CSS overrides (here are some examples). - But the great thing about DDG, is that no extensions of hacks are required. (Also note that browser extensions can be pretty bad for privacy- they make your fingerprint much more unique, and occasionally are plain malicious)

My Server Setup βš™οΈ

This article outlines the steps I take on any new server, to configure it for security, consistency and convenience. It is written specifically for Debian, but will also directly apply to derivatives (such as Ubuntu), and will likely be very similar for for other distros.

I am in the process of writing automation scripts to cover all of these steps, in the form of Ansible Playbooks.

This guide is split into 10 sections:

  1. System Update - Upgrade the OS and enable automated security updates
  2. System Setup - Specify hostname, add users, configure server time etc
  3. Configure SSH - Setup keys, configure sshd_config and set permissions
  4. Install Essential Software - Including git, vim, zsh, tmux, ranger etc
  5. Enable Firewall - Manage allowed inbound/ outbound connections with UFW
  6. Setup Intrusion Prevention - Protect from brute force attacks with Fail2Ban
  7. Configure Malicious Traffic Detection - Flag malicious packets with MalTrail
  8. Implement Security Auditing and Scanning - With ClamAV, Lynis and RKhunter
  9. Fetch Dotfiles for Vim, ZSH, Tmux etc to make SSH sessions more comfortable
  10. Automated Backups - Using Borg for incremental, off-site, encrypted backups
  11. Final Steps - Optional items (Go, Rust, Node, Python, Docker, NGINX etc..)

System Update

Update the System and Packages

  • apt update - Update system packages
  • apt -y upgrade - Upgrade OS
  • apt autoremove and apt clean - Remove locally downloaded deb packages and apt-get caches

Enable Unattended Upgrades

  • apt install unattended-upgrades - Install package (if not already installed)
  • dpkg-reconfigure --priority=high unattended-upgrades - Enable automatic upgrades
  • vi /etc/apt/apt.conf.d/50unattended-upgrades to update the configuration

System Setup

Specify Host Name

  • sudo hostnamectl set-hostname [new-host-name] - Set the machines host name
  • Add [hostname] into /etc/hosts - Add host name to the hosts file

Add New Users

  • useradd -m [username] -c "[user full name]" - Create a new user (-c Allows an associated name or comment)
  • passwd [username] - Specify a password for new user
  • sudo usermod -a -G sudo [username] - Gives the user root privileges (only apply if needed)

Set the Server Time

  • sudo timedatectl set-timezone Europe/London
  • sudo vi /etc/systemd/timesyncd.conf and add the address of the local NTP server
  • sudo systemctl restart systemd-timesyncd.service - Restart the time sync service

Configure SSH

Setup SSH Keys for Authentication

  • sudo apt install openssh-server - Install OpenSSH Server on remote host
  • ssh-keygen -t rsa -b 4096 - On the local system. Generates a new SSH key pair (enter a strong passphrase when prompted)
  • ssh-copy-id root@[] - Uploads to the remote server, and update the hosts file
  • chmod go-w ~ ~/.ssh ~/.ssh/authorized_keys - On the remote host, updated permissions
  • sudo ufw allow ssh - If UFW is enabled, then allow SSH access

Next we're going configure a couple of SSH security essentials

  • vim /etc/ssh/sshd_config - To open the SSH daemon's config file , and update:
    • Protocol 2 # Only use SSH 2 Protocol
    • PermitRootLogin no # Disable root SSH login
    • PasswordAuthentication no # Disable password-based SSH login
    • Compression delayed # Compression could be dangerous, only allow it once authenticated
    • MaxAuthTries 5 # Limit the maximum authentication attempts
    • PrintLastLog yes # Display last login date for an extra check (should be default)
    • PermitEmptyPasswords no # Disallow empty passwords (Not relevant for SSH Keys, but still good to have)
    • IgnoreRhosts yes # Disallow access via rhosts, which is rarely used anymore
    • IgnoreUserKnownHosts yes # Only trust the global known hosts list
    • HostBasedAuthentication no # Similar to rhosts, this is rarely used
    • Port 2200 # Set SSH access to a non-standard port
    • StrictModes yes # Prevent users from accidentally leaving their directories/ files as writable
    • UsePrivilegeSeparation sandbox # Prevent privilege escalation
    • PubkeyAuthentication yes # Public key authentication should be preferred (should be default)
    • GSSAPIAuthentication no # If you are not using GSSAPI authentication, this should be disabled
    • KerberosAuthentication no # If you are not using Kerberos authentication, this again should be disabled
    • Ciphers aes128-ctr,aes192-ctr,aes256-ctr # Use FIPS 140-2 compliant ciphers, to avoid weak encryption algorithms
    • MACs hmac-sha2-256,hmac-sha2-512 # Use FIPS 140-2 Compliant MACs, to avoid weak cryptographic hashes

The SSH daemon must be restarted, in order for these config changes to take effect: sudo systemctl restart ssh

Protect SSH Host Keys

  • sudo chmod 0600 /etc/ssh/*key - Set permissions for private keys
  • sudo chmod 0644 /etc/ssh/*pub - Set permissions for public keys

If your system stores keys in a different directory, you can find them with grep -i hostkey /etc/ssh/sshd_config. You can list the permissions of keys with ls -l /etc/ssh/*key (or *pub for public keys)

Optionally, configure an SSH tarpit, to lock up the bots hammering port 22, with Endlessh

Install Essential Software

Install Packages

  • sudo apt update - Ensure the package list is up-to-date
  • sudo apt install -y git vim tmux zsh ranger - Install essentials: vim, git, tmux, ZSH and ranger
  • sudo apt install -y make curl - Install utilities
  • sudo apt install -y fzf exa - Install command line improvements
  • sudo apt install -y ctags xsel glances fonts-powerline - Install visual improvements
  • sudo apt install -y clamav rkhunter lynis - Install security audit tools
  • sudo apt install -y neofetch figlet lolcat - Optionally, install fun stuff


  • If needed, install Docker
  • If needed, install Go Lang
  • If needed, install Rust and Cargo, with sudo curl -sSf | sh (check the script first!)
  • If needed, install Python and PIP, with sudo apt install python3 python3-pip
  • If needed, install Node.js and NPM, with sudo apt install nodejs npm
    • Or use NodeSource's PPA: curl -fsSL | bash -

Configure Firewall with UFW

  • sudo apt install ufw - Install UFW
  • sudo vi /etc/default/ufw and set IPV6=yes to use IPv6
  • sudo ufw default deny incoming and sudo ufw default allow outgoing to deny all incoming traffic, and allow outgoing
  • sudo ufw allow 2200/tcp to for example, allow incoming SSH traffic on port 2200
  • sudo ufw disable and sudo ufw enable (or systemctl restart ufw) to restart UFW
  • sudo ufw status - Check the current status

Whenever a new application is configured, UFW needs to be updated to allow incoming traffic to that port and protocol.

Intrusion Prevention with Fail2Ban

  • sudo apt install fail2ban - Install Fail2ban
  • sudo cp /etc/fail2ban/jail.{conf,local} - Copy jail.conf to jail.local
  • sudo vi /etc/fail2ban/jail.local - To edit the local config file, and add:
    • ignoreip = ::1 - with local IP addresses
    • bantime = 1d - Increase the ban time to 1 day
    • findtime = 10m - Time between each attempt
    • maxretry = 7 - Number of failures before IP is banned
  • sudo systemctl restart fail2ban - Restart Fail2ban, for changes to take effect
  • sudo systemctl status fail2ban - Show the current status

The fail2ban-client can also be used to interact with the Fail2ban service from the CLI

Malicious Traffic Detection with MalTrail

For systems that have services exposed to the internet, or for a firewall device that protects internal devices, then MalTrail can be really useful for flagging anything out of the ordinary.

Install dependencies and get the MalTrail source

  • sudo apt install schedtool python-pcapy git - SchedTool for better CPU scheduling, and Python for MalTrail
  • git clone - Get the MalTrail code
  • cd maltrail - Navigate into the directoru

Run MalTrail. There are two components, a sensor and a server.

  • sudo python & - Start the sensor (& will run it in the background)
  • python & - Start the server, in order to log results and allow access through a GUI

Access the GUI

  • Navigate to http://[ip]:8338 and enter username: admin and password: changeme!
  • To test things are working correctly, try ping -c 1 or, for DNS capturing nslookup
    • Results for both should display on the dashboard and in the logs: /var/log/maltrail/
    • To view today's logs, run cat /var/log/maltrail/$(date +"%Y-%m-%d").log

Configure MalTrail's Settings

  • echo -n '[your-desired-password]' | sha256sum | cut -d " " -f 1 - Choose a strong password and hash it
  • sudo vim /home/tech/maltrail/maltrail.conf - Open the configuration file
  • Under USERS section, replace the current Admin:05a181f00c15... with Admin:[your-hashed-password]
  • From within the maltrail.conf you can configure other settings for the server component
  • pkill -f && python & - Restart MalTrail
  • Under normal circumstances the logs are fairly sparse, so it is possible to use a system like entr to monitor them for changes and notify you using a channel of your choice.

Security Scanning with ClamAV, Lynis and RKhunter

For security monitoring, I am using Lynis to audit general system config, ClamAV for detecting malware and rkhunter for checking for root kits.

Install Packages

  • sudo apt install -y clamav rkhunter lynis - Install security audit tools
  • sudo rkhunter --propupd - Update rkhunter's file properties database

Run a System Audit

  • sudo lynis audit system - Run a full security audit
  • sudo clamscan / -r - Scan for malware
  • sudo rkhunter -c --sk --rwo - Check for rootkits (c for check, sk for skip keypress and rwo for report wanrings only)

These commands can also be put into an .sh file, and run periodically as a scheduled cron job, sending results to your email.

Setup Dotfiles

  • git clone --recursive - Download my dotfiles
  • cd ./dotfiles - Navigate to directory
  • ./ - Run the install script

Automated Backups

Borg is a deduplicating archiver with compression and encryption, it's space efficient, secure and easy. BorgBase provides affordable, performant, simple and safe Borg repositories (10 GB free or 100 GB for $24/year). I am also using for monitoring backup status.

Will Browning has written an excellent tutorial for setting up Borg Backups.

  • sudo apt install borgbackup python3-pip python3-setuptools - Install Borg backup, and Python (if you don't already have it)
  • pip3 install wheel and pip3 install --user --upgrade borgmatic - Install borgmatic for the user, and it's dependency, wheel
  • export PATH="$HOME/.local/bin:$PATH" - Add this to zsh/ bashrc to include the above commands in your path
  • sudo env "PATH=$PATH" generate-borgmatic-config - Generate a bormatic config

Final Steps

Setup Welcome Banner

  • sudo cp ~/dotfiles/utils/ /etc/profile.d/ - Copy welcome banner from utils to system
  • sudo chmod +x /etc/profile.d/ - Update permissions for all users

Install NetData, for web-based resource monitoring

  • bash <(curl -Ss --stable-channel --disable-telemetry - Install NetData
  • You will need to allow firewall access, sudo ufw allow from [your-static-ip] to any port 19999
  • If using a cloud platform (like AWS, Azure, GCP) then you may need specify an inbound port rule to allow access

Setup Glances

  • Install: sudo apt install glances
  • To enable Glances to start up at boot time, run it as a service with systemd. See docs for more info
  • If you need to access Glances remotely, either VPN into your server (recommended), or setup a reverse proxy to the Glances web page, as per docs

Install Bpytop

  • sudo pip3 install bpytop --upgrade

If needed, use Smartmontool to monitor the status of you're disks.

  • sudo apt install smartmontools - Install smartmontool, which includes smartctl
  • sudo fdisk -l - Find the disk(s) you wish to ceck
  • sudo smartctl -t short /dev/sdX - Run a quick check, where X is you're drive number
  • For more info regarding the output, see this post

Optionally, setup Bash Hub for indexed and searchable terminal history in the cloud

  • curl -OL && zsh setup - Check the installation script first, then install
  • When prompted, log into your account. Restart your session, and run bh to access the help menu
  • Add an environmental variable, indicating which commands should not be saved, e.g. export BH_FILTER="(scp|ssh|--password)"
  • Precede any command that contains sensitive information with #ignore to prevent it being saved
  • See usage docs:

Additional Tasks:

Spelling Auto-Correct System ✏️❌

TDLR; Auto-correct is a lot more efficient than manually correcting misspelled words. Espanso is awesome.
Beyond that, this isn't too interesting - I just documented this so I can refer back to it in the future.
If you're just looking for a generic word list, see this post, which contains 4,200 common misspellings.

  1. Intro
  2. Word List
  3. Converter
  4. Usage


I am terrible at spelling. About 15% of what I've typed will be underlined in red. It's usually the same couple hundred words that I forget how to write. The best solution would probably be to learn how to spell, but I've instead I use a system to auto-correct my mistakes.

I use Espanso to implement this.

There are of course standalone applications that do exactly this (like client9/misspell, streetsidesoftware/cspell and sedm0784/vim-you-autocorrect), but I have other Espanso scripts for various tasks, so it made sense to bundle it all into one simple, cross-platform solution. I've previously used Auto-Hot-Key which is also very good, but only available for Windows systems. Esprano's matching system makes it an extremely powerful tool, this is a very trivial task compared to all the other awesome stuff you can use it for.

My Auto-Correction List

These are just the 220-ish words that I most often miss type/ spell, along with their correct spellings. It is written as an AHK script (because it's easier to maintain), but I made a quick utility to convert AHK into YAML for use with Espanso.

For a more comprehensive list of 4,200 crowd source common misspellings, see here:

; This is my personal list of words I commonly misspelled plus auto-corrections
; Licensed under MIT - (C) Alicia Sykes, 2021 <>
; Format: '::[Incorrect Word]::[Correct Word]'

::ourself::our self

For more words, see:

Source code for Converter Script on


For Espanso, first convert your source into YAML, then run espanso path to find your config file location, drop the script into that directory, and restart Espanso, it should now be running.

For the Auto Hot Key script, once you have AHK installed, then just download the above script (save it with the .ahk extension), double click on it and it will be running.

Top 25 Raspberry Pi Projects πŸ₯§


Ever since the first version was released in 2012, the Raspberry Pi has been a staple piece of kit for professionals, hobbyists, educators and everyone in between. And for good reason, it's small, low power, affordable but extremely versatile. There are of course other single board computers on the market, but the Pi has a strong community behind it and provides a good balance between capabilities, form factor and price.

Raspberry Pi Projects

Here is a curated list of projects that I have used, enjoyed and would recommend for anyone looking to put their Pi to use. For links to each projects GitHub repo, see:

There's nothing too complicated here, so this should also provide a good starting point for beginners. Everything here is fully open source and backed by strong communities with large user bases.

  • Pi-Hole - Network-wide DNS-based ad blocker
  • RetroPi - Retro game emulator
  • Magic Mirror - Easy modular smart mirror project
  • Pi-KVM - Easy and fully-featured KVM-over-IP
  • OctoPrint - 3D printer remote controller interface
  • PiMusicBox - An all-in-one streaming device with Spotify support (see also PiCorePlayer or Balena Sound)
  • SonicPi - Code-based music creation tool
  • Kiwix Hotspot - An off-grid WiFi hotspot serving up all the world's knowledge (Wikipedia, StackExchange, TED, the Gutenburg Library and more)
  • RaspAP - Fully-features wireless access point, with GUI user interface. Supports ad-blocking, VPN, data usage, and more
  • PiVPN - Super simple OpenVPN or WireGuard server
  • P4wnP1_aloa - All-in-one highly customizable Pi Zero attack platform (see also PwnPi, a pen test drop box)
  • MotionEye - Easy multi-camera CCTV surveillance system (see also Rpisurv)
  • RaspiBlitz - Run a BTC lightning node (see also Umbrel or RaspNode and this tutorial)
  • NextCloudPi - Self-host a local file server, with tons of useful plugins available
  • Audio-Reactive-LED-Strip - Create lighting effects in time with music
  • BrewPi - Fully automated brewery controller
  • RaspberryShake - Earthquake monitoring with seismographs and infrasound
  • Sensor.Community - Host a Pi environment sensor, using the Enviro Air Quality pHAT
  • RPiPlay - Turn a Raspberry Pi into an Airplay server to enable screen mirroring on TVs, monitors and projectors
  • RpiITX - RF transmitter 5 KHz to 1500 MHz
  • PiAware - ADS-B aircraft tracking from FlightAware (see also Startus ADS-B)
  • Pirate Box - Anonymous offline mobile file-sharing and communications system
  • MoBro - An external resource monitor screen for your PC
  • Mycroft AI - Privacy-respecting AI voice assistant similar to Alexa. You can also build a full enclosure out of the developer kit. (Alternatively, build a Google Home, using Voice Kit or an Alexa)

There is almost no limit to what you can do with a Pi, this list is just intended to serve as an example and a provide a starting point.

Operating Systems

Raspberry Pi can also be used as a normal computer; either a desktop, mini handheld or headless as a server. You're not just limited to Raspberry Pi OS, it also works very well with Debian, FreeBSD, Arch, Kali, Slackware and Ubuntu to name a few.

For more specific use cases, there's also Diet Pi (super light-weight OS specifically for single-board computers), OpenElec (lightweight system for running a Kodi media center), Windows IoT Core, OCMC (media center), Emteria Android (for Android) and Chromium OS (similar to open source alternative to Chrome OS), Nems (for network monitoring) and many more

Self-Hosted Applications

Once running an OS of your choice, the Pi is also perfect for self-hosting Linux applications. For example;

... and tons more

If you're interested in self-hosting multiple apps, or using your Pi as a little home server, then check out Home Lab OS by Nick Busey, it makes correctly configuring a complex lab as easy as running a single command.

Tools for Flashing SD Card/ USB

For flashing an OS to you're Pi's SD card or USB: Official Pi Imager, Etcher or use the dd (CLI utility on Unix systems). Rufus and Win32 Disk Imager are also good utilities, but only available on Windows.

To backup you're Pi's SD card of USB, you can also use dd (the same as cloning, but in reverse). For example:

  • Backup: sudo dd bs=4M if=/dev/sdb of=PiOS.img
  • Restore: sudo dd bs=4M if=PiOS.img of=/dev/sdb

For more information, see this tutorial. Alternatively, on Windows systems, you can use Win32 Disk Imager to clone the SD card.

More Project Ideas & Tutorials

The following projects are a bit more hands-on

  • Truly WiFi extender - A very performant and inexpensive WiFi repeater solution: via Instructables by @mrtejas99
  • Print Server - Turn any old printer into an internet-connected WiFi printer: via by Christian Cawley
  • YouTube Streaming Bird Box: via Instructables by @buestad
  • Smart Glasses with a treansparent OLED display: via Instructables by @Bradley_Campbell
  • 3D-Printed Mini Macintosh PC: via Instructables
  • Mini Desktop PC with the Pi 4: via Instructables by @thediylife
  • Internet Radio Player - Stream content from Pandora's Radio: via Instructables by @Ayy
  • Raspberry Pi Zero Cherry MX Split Mechanical Keyboard: via Instructables by Gosse Adema
  • Step-by-step Pi NAS with OpenMediaVault: via Instructables by @araymbox
  • Distraction-Free Writing Machine: via Instructables by @CameronCoward

My Top 50 Windows Apps πŸ–₯

Open Source Apps on Microsoft Windows

This is my personal reference list of useful software for Microsoft Windows.
Typically I only install what I need, and uninstall software that hasn't been used for a while. Where possible I prefer to run containerized or portable apps. There's nothing installed on my system that isn't on this list.

Before setting anything up, the first task is to disable telemetry, remove pre-installed bloatware and make a few security tweaks. For this I use a series of PowerShell scripts provided by Privacy.Sexy.

Items marked with '❌' are not fully open source, and 'πŸ’²' indicates either not free, or has premium tier


  • Tor Browser - For more anonymous browsing + access to the Tor network Git
  • VirtualBox - x86, AMD64, and Intel64 virtual machines Git
  • WinSCP - SFTP client and remote access file manager GitHub
  • qBittorrent - BitTorrent client GitHub
  • WireShark - Packet analyzer GitHub
  • Angry IP Scanner - Quickly find IPs within a range, open ports and other info GitHub
  • NetLimiter - βŒπŸ’² Network traffic monitoring tool with simple firewall functionality
  • Universal Radio Hacker - SDR client for investigating wireless protocols GitHub

General Apps

  • HWiNFO64 - ❌ System info and diagnostics
  • Process Hacker - Monitor system resources and analyse currently running processes GitHub
  • Etcher - For flashing ISOs onto USB drives with a overly-fancy UI GitHub
  • ExifCleaner - Tool to easily remove metadata from images and media GitHub
  • X410 - βŒπŸ’² Fork of to run Linux GUI apps in Xfce via WSL
  • LinPhone - VOIP client for making/ receiving phone calls and SMS messages Git

Security Utilities

  • Cryptomator - Fast file encryption for cloud storage GitHub
  • VeraCrypt - Strong disk, container and file encryption GitHub
  • KeePassXC - Password manager for KeePass files GitHub
  • Kleopatra - Certificate manager and PGP file encryption suit GitHub
  • WireGuard - VPN connection client using WireGuard protocol GitHub
  • CalmAV - Anti-virus scanner (See also, ClamWin GUI app) Website
  • BleachBit - Frees up disk space by deleting unneeded data in the cache and temporary files GitHub
  • Windows Spy Blocker - Block Microsoft telemetry and data collection and manage application access GitHub
  • Harden Tools - Easily turn off undesired or privacy-invasive Windows features GitHub
  • WFN - Firewall notifier to monitor outgoing connections GitHub

UI Utilities

  • CopyQ - Advanced clipboard manager GitHub
  • Espanso - Text expander with powerful matching system (similar to AHK) GitHub
  • AutoHotKey - Keyboard remapping, macros and automation scripting GitHub
  • Quick Look - Small utility that lets you quickly preview a file by pressing Space GitHub
  • EarTrumpet - A utility that provides better volume control on a per app basis GitHub
  • ColorPicker - Minimal but complete color picker GitHub
  • Power Toys - Color picker, fancy zones, run dialog, rename utility, shortcuts and more GitHub
  • SidebarDiagnostics - Customizable desktop widget showing system resource and hardware info GitHub
  • Wox - Global search, run commands and execute actions with Alt + Space GitHub
  • Groupy ❌- Group multiple windows into browser-like tabs, while preserving Alt + Tab switching


  • Gimp - Image and photo editing application Git
  • DarkTable - Organize and bulk edit photos (similar to Lightroom) GitHub
  • InkScape - Digital drawing/ illustration Git
  • Audacity - Multi-track audio editor and recording GitHub
  • OBS Studio - High performance streaming/ broadcasting and recording GitHub
  • VLC Player - Multimedia player and play back framework GitHub
  • Shotcut - Video editing platform GitHub
  • HandBrake - For converting video from any format to a selection of modern codecs GitHub
  • Synfig Studio - 2D animation GitHub
  • Blender - 3D modelling, rendering and sculpting GitHub
  • Cura - 3D Printing software, for slicing models GitHub
  • Dia - Versatile diagramming tool, useful for UML (similar to MS Visio) Git
  • ShareX - Quick and easy screen recorder GitHub
  • SmugMug βŒπŸ’²- Premium photography backup, sync, sharing and publishing service GitHub




  • VS Code - Customizable code editor, with InteliSense, built-in compilers, git and plugins
  • Cmder - Better console emulator for Windows, with Tmux-like features, great for SSH sessions
  • PostMan - For testing and developing API endpoints
  • Android Studio - For native Android development with Java/ Kotlin
  • Arduino IDE - Compile and upload for IoT devices
  • Processing - IDE and compiler for creative coding with the Processing language
  • DB Browser for SQLite - Create, design, and edit database files for SQLite
  • RunJS - Real-time JavaScript playground, useful for writing quick scripts
  • Docker Desktop - Easy way to containerize applications
  • Notepad++ - Lightweight text editor with syntax highlighting
  • Zap - Web app security analyzer
  • Vega - Automated security testing to find XXS, SQL injection and other issues
  • Git - Version control system


  • YubiKey Manager - Configuring YubeKey devices
  • OnlyKey - For configuring the OnlyKey with PGP, SSH, Passwords, 2FA, Crypto and secure data
  • StreamDeck - Setting up macros on the StreamDeck
  • Razer Synapse ❌- Customize the RGB for Razer products
  • SoundBlaster Audigy FX ❌- Drivers and audio level customization for sound card
  • AMD Radeon Settings ❌- Drivers for customizing graphics card for different tasks
  • Chameleon - For programming the ChameleonMini NFC / RFID contactless smartcard emulator
  • Ledger Live ❌ - App for managing assets stored on Ledger Hardware Wallet GitHub


On new installs, Microsoft's Package Manager can be useful for quickly installing required software.

See also, my Winstall Collection of the above apps.

winget install --id=Lexikos.AutoHotkey -e
winget install --id=REALiX.HWiNFO -e
winget install --id=GNURadio.GNURadio -e
winget install --id=Balena.Etcher -e
winget install --id=WiresharkFoundation.Wireshark -e
winget install --id=Mozilla.Firefox -e
winget install --id=angryziber.AngryIPScanner -e
winget install --id=Microsoft.PowerToys -e
winget install --id=Docker.DockerDesktop -e
winget install --id=Oracle.VirtualBox -e
winget install --id=WinSCP.WinSCP -e
winget install --id=qBittorrent.qBittorrent -e
winget install --id=Cryptomator.Cryptomator -e
winget install --id=Keybase.Keybase -e
winget install --id=KeePassXCTeam.KeePassXC -e
winget install --id=StandardNotes.StandardNotes -e
winget install --id=Mozilla.Thunderbird -e
winget install --id=ProtonTechnologies.ProtonMailBridge -e
winget install --id=AgileBits.1Password -e
winget install --id=BraveSoftware.BraveBrowser -e
winget install --id=thehandbraketeam.handbrake -e
winget install --id=LibreOffice.LibreOffice -e
winget install --id=GIMP.GIMP -e
winget install --id=Inkscape.Inkscape -e
winget install --id=darktable.darktable -e
winget install --id=Audacity.Audacity -e
winget install --id=OBSProject.OBSStudio -e
winget install --id=VideoLAN.VLC -e
winget install --id=Meltytech.Shotcut -e
winget install --id=BlenderFoundation.Blender -e
winget install --id=Ultimaker.Cura -e
winget install --id=Spotify.Spotify -e
winget install --id=Valve.Steam -e
winget install --id=Postman.Postman -e
winget install --id=Arduino.Arduino -e
winget install --id=SQLiteBrowser.SQLiteBrowser -e
winget install --id=Notepad++.Notepad++ -e
winget install --id=elgato.streamdeck -e

Note: It's very important to always carefully check the URL for each download before proceeding. Only install applications from their official source.

[REFERENCE] Using Variable Fonts in CSS πŸ”€

This is just a short reference to using fonts with Variable Axes in CSS
If your looking for a complete guide or interactive playground, then check out the resources linked to at the end of this page instead

What are Variable Fonts?

Variable fonts are font files that encapsulate the entire family, and allow for custom attributes (regarding things like weight, slant, grade, character-width) to be set. This brings several benefits:

  • Much higher quality rendering of fonts, without browser distortions
  • Greater control over customization, as you can specify each value separably
  • The need only for a single font file (rather than a version for each style). Great for performance due to reduced file size and fewer requests

Variable fonts were announced in 2016, and now are officially supported by all modern browsers and most major operating systems, as an extension to the OpenType Specification. There are now many fonts that support variable axes.

Official Variation Axes

Weight (wght)

  • Corresponding CSS attribute: to font-weight
  • Example usage: font-variation-settings: 'wght' 625;
  • Typical range: 100 - 900

Italic (ital)

  • Corresponding CSS attribute: to font-style
  • Example usage: font-variation-settings: 'ital' 1;
  • Typical range: 0 - 1 (Indicating upright or italic)

Slant (slnt)

  • Similar to italics, but allows you to specify an exact value (in a degree continuum) and it does not include glyph substitution
  • Corresponding CSS attribute: to font-style
  • Example usage: font-variation-settings: 'slnt' 14;
  • Typical range: -90 – 90 degrees

Optical Size (opsz)

  • This allows adding or removing detail to improve legibility on small or large screen sizes. Set to auto by default, and usually this is adequate
  • Corresponding CSS attribute: to font-optical-sizing
  • Example usage: font-variation-settings: 'opsz' 36;
  • Typical range: value usually matches font-size


  • Corresponding CSS attribute: to font-stretch
  • Example usage: font-variation-settings: 'wdth' 115;
  • Typical range: 75% - 125%

Custom Axes

Many fonts also have a number of custom axes that can be modified. Typically these are represented with capitals. The below are several common custom axis, but Nick Sherman's project provides an interactive playground, where you can properly check out many more of these axes.

Grade (GRAD)

  • Lets you modify the weight, without effecting width. Useful for responding to low-resolution screens
  • Example usage: font-variation-settings: 'GRAD' 88;
  • Typical range: Decimal, between -1 - 1

Ascenders and Descenders (YTAS & YTDE)

  • Alters of height of the stems and tails of each character
  • Example usage: font-variation-settings: 'YTAS' 800, 'YTDE' -350;
  • Typical range: YTAS 650 - 850. YTDE -500 - -138

Combining Properties

To use multiple variable font properties, you must combine them into a single line, using a comma-separated list.
(Note: When overriding a single font-variation property, you must re-define all of the other properties.)

font-variation-settings: 'wght' 375, 'GRAD' 88;

Supporting Older Browsers

In order to support older browsers, use the @supports mixin to override text with variable font properties. For example:

h1 {
 font-family: some-non-variable-font-family;

@supports (font-variation-settings: 'wdth' 115) {
 h1 {
    font-family: some-variable-font-family;

Quick Tips

Slant & Italics

It is possible to use both slant (slnt) and italics (ital) at the same time. This enabled you to separate the angle change from the glyph substitution.
i.e the italics font property replaces some characters with a different glyph, usually for ascetics. Turning italics off, and then using slant to italicize the text, means that no characters are replaced. The reserve is also true, enabling italics and setting the slant to 0 will replace the glyths. This makes a much bigger than expected difference.

For example:

font-variation-settings: 'slnt' 10, 'ital' 0;

Additional Resources


Try. πŸ’―

  1. Try
  2. Try again
  3. Try harder
  4. Try differently
  5. Try again tomorrow
  6. Try again the next day
  7. Try to find another way
  8. Try to fix what's not working
  9. Try to find someone who has done it
  10. Just keep trying, until you succeed