Kai Hendry's other blog archives

Good riddance netctl

For the last year (2013) Archlinux has been recommending users to use netctl to configure their network interfaces.

Netctl's ethernet-dhcp worked well, but not the wireless interface management which never made sense to me.

Upstream's Jouke Witteveen is sadly unhelpful despite my cries for help.

So on the back of systemd's (>210) systemd-networkd, which I previously blogged about when configuring my Droplet, I now have:

$ cat /etc/systemd/network/eth0.network

Via journalctl -u systemd-networkd.service -f this seems to be able to detect the carrier is on or off without ifplugd. Nice!

For your DNS nameservers, you need to symlink /etc/resolv.conf to /run/systemd/network/resolv.conf

etc$ sudo ln -s /run/systemd/network/resolv.conf


To do all the WPA authentication stuff:

sudo systemctl enable wpa_supplicant@wlan0.service

You need to make sure /etc/wpa_supplicant/wpa_supplicant-wlan0.conf is in place, as you can see in /usr/lib/systemd/system/wpa_supplicant@.service. I prefer to link in /etc/wpa_supplicant.conf where I've also stored my wireless passwords and things.

/etc/wpa_supplicant$ sudo ln -s /etc/wpa_supplicant.conf wpa_supplicant-wlan0.conf

Now to get the IP address, we use systemd-networkd, with the configuration:

$ cat /etc/systemd/network/wlan0.network

Wrap up

systemd-analyze for wlan0 is 3.203s and eth0 is 3.085s. I'm happy !

Currently I manually turn toggle wifi using my Thinkpad's wireless switch, for switching between wired and wireless interfaces.

Acknowledgements: WonderWoofy on the the Archlinux forums

Systemd network on a Droplet

From roughly systemd version 210, networking in my Droplet changed.

Out went netctl somehow (good riddance), and my network connectivity (uh oh).

Using Digital Ocean's buggy Console Access, I managed to setup my network access manually.

/etc/network.d/ethernet-static gave me the previous network settings:

[root@sgd ~]# cat /etc/network.d/ethernet-static
DNS=('' '' '')

How to setup a networking interface with ip

I'm more familiar with ifconfig & route, but with some frantic Googling I did figure out ip's odd unfamilar API:

ip addr add dev enp0s3
ip route add default via

ip a then looks like:

[root@sgd ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 04:01:11:8d:89:01 brd ff:ff:ff:ff:ff:ff
    inet brd scope global enp0s3
    inet6 fe80::601:11ff:fe8d:8901/64 scope link
       valid_lft forever preferred_lft forever

Sidenote: I'm not sure why my Droplet's network interface is called enp0s3 and not something simple like eth0.

Ping to test... YES... I can ssh in.

Now to migrate to systemd-networkd.

I created /etc/systemd/network/enp0s3.network:

[root@sgd ~]# cat /etc/systemd/network/enp0s3.network
# Migrated from /etc/network.d/ethernet-static
# https://coreos.com/blog/intro-to-systemd-networkd/
  1. To test systemctl start systemd-networkd.service.
  2. Check systemctl status systemd-networkd.service looks good

      [root@sgd ~]# systemctl status systemd-networkd.service
      systemd-networkd.service - Network Service
         Loaded: loaded (/usr/lib/systemd/system/systemd-networkd.service; enabled)
         Active: active (running) since Thu 2014-03-13 04:02:24 UTC; 11min ago
           Docs: man:systemd-networkd.service(8)
       Main PID: 158 (systemd-network)
         Status: "Processing requests..."
         CGroup: /system.slice/systemd-networkd.service
                 `-158 /usr/lib/systemd/systemd-networkd
      Mar 13 04:02:24 sgd systemd-networkd[158]: enp0s3: link is up
      Mar 13 04:02:24 sgd systemd-networkd[158]: enp0s3: carrier on
      Mar 13 04:02:24 sgd systemd-networkd[158]: enp0s3: link configured
      Mar 13 04:02:24 sgd systemd[1]: Started Network Service.

To watch/tail what the systemd-networkd.service is doing, journalctl -u systemd-networkd.service -f

  • Enable it systemctl enable systemd-networkd.service
  • And reboot!

Loose ends, ifplugd

I noticed in htop ifplugd is running. I can't see from systemctl or pstree what invoked it. Mysterious.

[root@sgd ~]# ps aux | grep ifplugd
root       136  0.0  0.0   2200   284 ?        Ss   04:02   0:00 /usr/bin/ifplugd -i enp0p3 -r /etc/ifplugd/netcfg.action -fIns
root     27388  0.0  0.1   2828   584 pts/1    S+   04:16   0:00 grep ifplugd

Latest tips

From Apache to Nginx rewrites


RewriteRule ^client/(.*)$ /client.php?id=$1               [L,QSA]


rewrite ^/client/(.*)$ /client.php?id=$1&$args last;
  • Notice uris in nginx must start with /
  • Notice &$args is needed to replicate Apache's QSA feature
Setting up structs

Examples of how to populate a struct.

package main

import "fmt"

type event struct {
    Name string
    Date string

type events []event

func main() {
    e1 := event{}
    e1.Date = "Today"
    e1.Name = "Somethiing"
    e2 := event{Date: "Tomorrow", Name: "Dentist"}

    s := events{e1, e2}

    fmt.Printf("%+v\n", s)

Parsing arbitary JSON

For example.json:

{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}

Desired output:

this.menu.popup.menuitem[0].value = "New"
this.menu.popup.menuitem[0].onclick = "CreateNewDoc()"
this.menu.popup.menuitem[1].value = "Open"
this.menu.popup.menuitem[1].onclick = "OpenDoc()"
this.menu.popup.menuitem[2].value = "Close"
this.menu.popup.menuitem[2].onclick = "CloseDoc()"
this.menu.value = "File"
this.menu.id = "file"

Python implementation

With thanks from Hyperair on Freenode's #hackerspace.sg


import json
import sys

fmt = '{key} = {value}'

def dump_obj(prefix, obj):
    if isinstance(obj, dict):
        for k, v in obj.items():
            if prefix:
                k = prefix + '.' + k

            dump_obj(k, v)

    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            if prefix:
                k = "{prefix}[{idx}]".format(prefix=prefix, idx=i)
            dump_obj(k, v)

        print(fmt.format(key=prefix, value=json.dumps(obj)))

obj = json.loads(sys.stdin.read())
dump_obj('this', obj)

Golang implementation

Since Golang is statically typed, the important element in the code is t := x.(type) where they type of x gets asserted, which is a required step, so you can then iterate over the resulting structure.

package main

import (

func dumpobj(prefix string, x interface{}) {

    switch t := x.(type) {

    case map[string]interface{}:
        for k, v := range t {
            dumpobj(prefix+"."+k, v)
    case []interface{}:
        for i, v := range t {
            dumpobj(prefix+"["+strconv.Itoa(i)+"]", v)
    case string:
        fmt.Printf("%s = %q\n", prefix, t)
        fmt.Printf("Unhandled: %T\n", t)

func main() {
    j, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
    var pj interface{}
    err = json.Unmarshal(j, &pj)
    if err != nil {

    dumpobj("this", pj)


Alternative example with depth: http://play.golang.org/p/QOwxJQ4z4Z

Source is with thanks again from dsal from #go-nuts http://play.golang.org/p/pt4MLJ6HcV

However ideally you know the JSON structure beforehand, and you create a structure for it to map to, e.g. http://play.golang.org/p/bdjC6DLTqo. Only then you can access values easily like:

Dovecot IMAP Read Only Archive

http://wiki2.dovecot.org/HowTo/ReadOnlyArchive is outdated.


$ sudo doveconf -n
# 2.2.9: /etc/dovecot/dovecot.conf
# OS: Linux 3.9.4-1-ARCH x86_64
auth_mechanisms = plain anonymous
log_path = /home/example/readonly/dovecot.log
mail_location = Maildir:~/mail/inbox
mail_log_prefix = %Us(%r):
passdb {
  args = /etc/anon.passwd
  driver = passwd-file
service auth {
  user = nobody
ssl = no
userdb {
  args = /etc/anon.passwd
  driver = passwd-file
valid_chroot_dirs = /home/example/readonly

For debugging purposes, tail /home/example/readonly/dovecot.log




Permissions are important. You will be doing sudo chown -R 501:501 .

$ tree -ugp readonly
|-- [-rw-r--r-- root     root    ]  dovecot.conf
|-- [-rw------- 501      501     ]  dovecot.log
`-- [dr-xr-xr-x 501      501     ]  mail
    `-- [drwxr-xr-x 501      501     ]  inbox
        |-- [dr-xr-xr-x 501      501     ]  cur
        |   |-- [-r--r--r-- 501      501     ]  1386050966.M540929P13587.sg.webconverger.com,S=315,W=328:2,S
        |   |-- [-r--r--r-- 501      501     ]  1386051008.M248603P13703.sg.webconverger.com,S=2081,W=2125:2,RS
        |   |-- [-r--r--r-- 501      501     ]  1386051093.M840486P13614.sg.webconverger.com,S=694,W=711:2,S
        |   |-- [-r--r--r-- 501      501     ]  1386051118.M789320P13725.sg.webconverger.com,S=2084,W=2127:2,S
        |   `-- [-r--r--r-- 501      501     ]  1386051172.M378951P13760.sg.webconverger.com,S=1881,W=1920:2,S
        |-- [-rw-r--r-- 501      501     ]  dovecot-uidlist
        |-- [-rw-r--r-- 501      501     ]  dovecot-uidvalidity
        |-- [-rw-r--r-- 501      501     ]  dovecot-uidvalidity.529d7573
        |-- [-rw-r--r-- 501      501     ]  dovecot.index.cache
        |-- [-rw-r--r-- 501      501     ]  dovecot.index.log
        |-- [-rw-r--r-- 501      501     ]  dovecot.index.thread
        |-- [-r--r--r-- 501      501     ]  inbox
        |-- [dr-xr-xr-x 501      501     ]  new
        `-- [dr-xr-xr-x 501      501     ]  tmp

5 directories, 14 files

.muttrc test

$ cat mutt-econv-test
set spoolfile=imap://anonymous@imap.dabase.com
set folder=imap://anonymous@imap.dabase.com
set sort=threads
set sort_aux=reverse-last-date-received

To test:

$ mutt -F mutt-econv-test

See https://github.com/kaihendry/econversations for why am I doing this.

Sharing a conversation

Tag the thread and (s)ave or (f)orward it

<esc>t ;f (or ;s)

Powered by Vanilla PHP feedback form