0cc0bf14832be3da34d05108aef4c54084e33bbe — Xinglu Chen 1 year, 5 months ago 8e2072a
posts: Add gemini version for old posts
A src/posts/hello-world.gmi => src/posts/hello-world.gmi +2 -0
@@ 0,0 1,2 @@
# Hello World
This is my first blog post!

A src/posts/mailserver-with-nixos.gmi => src/posts/mailserver-with-nixos.gmi +182 -0
@@ 0,0 1,182 @@
# Mailserver with NixOS
In this blog post I will go through how I setup a mailserver using the
excellent [nixos-mailserver] module.  If you want to follow along, you
obviously need to setup a domain with a domain registrar.

This post will only cover the details for the server, I will make a
follow-up post on how I configure my laptop to deal with mail.

=> https://gitlab.com/simple-nixos-mailserver/nixos-mailserver nixos-mailserver

## What you get

Before we actually use the module, it is probably a good idea to go
over what it is that you get when using the module.  The module will
configure the following things for you

* Dovecot for delivering mail using IMAP or POP3
* Postfix for sending mail
* Rspamd for filtering spam and greylisting
* Let' Encrypt for SSL certificate
* Opendkim for DKIM signing
* Sieve for filtering mail

## Download the modules

The nixos-mailserver module is not part of the official Nixpkgs
repository, this means that we have to download it separately to use
it.  You can do this in many different ways, I am currently using the
experimental [nix-flakes] feature so I will put the following in my

nixos-mailserver = {
  url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-20.09";

If you do not use flakes, you can put the following in your

{ config, pkgs, ... }:
let release = "nixos-20.09";
in {
  imports = [
    (builtins.fetchTarball {
      url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/${release}/nixos-mailserver-${release}.tar.gz";
      # This hash needs to be updated
      sha256 = "0000000000000000000000000000000000000000000000000000";

  # The rest of the config

=> https://nixos.wiki/wiki/Flakes nix-flakes

## Configuration

All you have to configure is the domain for your server, the mailboxes
you want to use and the user accounts, and that is it!  Settings
`certificateScheme' to `3' means that my SSL certificate will be
created automatically with Let's Encrypt.  For this to work, you also
have to create an "A" record for the FQDN to point to your IP address.

The account password should be a hash of the real password, this can
be generated by running `mkpasswd -m sha-512' in the shell.  I will go
into more detail on the sieve script I am using in the next section.

  mailserver = {
    enable = true;
    fqdn = "mail.yoctocell.xyz";
    domains = [ "yoctocell.xyz" ];

    loginAccounts = {
      "public@yoctocell.xyz" = {
        # mkpasswd -m sha-512
        hashedPassword = "<hashed-password>";
        sieveFilter = builtins.readFile ./filters.sieve;

    mailboxes = {
      Trash = {
        auto = "no";
        specialUse = "Trash";
      Junk = {
        auto = "subscribe";
        specialUse = "Junk";
      Drafts = {
        auto = "subscribe";
        specialUse = "Drafts";
      Sent = {
        auto = "subscribe";
        specialUse = "Sent";
      Archive = {
        auto = "subscribe";
        specialUse = "Archive";

    certificateScheme = 3;

  networking.firewall.allowedTCPPorts = [ 465 993 ];

## Sieve

I am subscribed to a few mailing lists, to better keep track of them I
have a sieve filter on the server that refiles the emails to different
folders.  Every mailing list will get its own folder and the folder
structure looks something like this (it follows the Maildir++ spec):

├── .Archive
├── .Sent
├── .lists.emacs.git-email
├── .lists.emacs.piem
├── .lists.nix.nixpkgs-dev
├── .lists.mail.public-inbox
├── cur
├── new
└── tmp

I could write all of the sieve filters manually, but that would be
very tedious.  John Wiegley has a really nice [Haskell script] which
generates the sieve script from a Haskell lookup table.  I have
modified it to filter the relevant mailing lists, and the result looks
like this:

elsif anyof (header :contains ["List-Id"]
          header :contains ["Sender","From","To","Reply-To","Cc"]
            "piem@inbox.kyleam.com") {
  fileinto "lists.emacs.piem";

elsif anyof (header :contains ["List-Id"]
          header :contains ["Sender","From","To","Reply-To","Cc"]
            "~yoctocell/git-email-devel@lists.sr.ht") {
  fileinto "lists.emacs.git-email";

elsif anyof (header :contains ["List-Id"]
          header :contains ["Sender","From","To","Reply-To","Cc"]
            "meta@public-inbox.org") {
  fileinto "lists.mail.public-inbox";

Then I just import this file in my mailserver config with `sieveFilter
= builtins.readFile ./filters.sieve;'.

This is pretty much it for the server-side. There will be a follow up
blog post about the client-side configuration (it is quite
complicated). You can find the relevant configurations [here].

=> https://github.com/jwiegley/scripts/blob/master/sievegen Haskell script
=> https://git.sr.ht/~yoctocell/nixrc/tree/master/item/profiles/mail/mailserver here

A src/posts/nixos-on-btrfs-with-encrypted-root.gmi => src/posts/nixos-on-btrfs-with-encrypted-root.gmi +236 -0
@@ 0,0 1,236 @@
# NixOS on Btrfs with encrypted root
In this guide I will install NixOS on btrfs with an encrypted root

*Note*: This guide is mostly just some notes for myself, proceed at your
own risk!

## Prerequisites

You are expected to have a basic knowledge of both [NixOS] and the
[btrfs] filesystem, and you will need an installation media if you are
doing this on bare metal.

First, download the NixOS iso and flash it to your usb, where `sdX' is
the name of the usb drive.

sudo dd if=/path/to/iso of=/dev/sdX bs=4M status=progress

=> https://nixos.org NixOS
=> https://btrfs.wiki.kernel.org/index.php/Main_Page btrfs

## Get started

Boot from the usb and setup your wifi connection

wpa_supplicant -B -i interface -c <(wpa_passphrase '<SSID>' '<password>')

## Partitioning

The next step is to partition your drives, I will create three

 Name         Type                       Size   
 `/dev/sdX1'  EFI boot partition         512 MB 
 `/dev/sdX2'  Swap partition             8 GB   
 `/dev/sdX3'  Root partition with btrfs  <rest> 

Use your favourite partition program, I will use `cfdisk'. Run `lsblk'
to make sure everything you didn't mess things up.

## Encryption

We will encrypt the root partition (`/dev/sdX3') using
[dm-crypt]. First, format the partition and enter a passphrase which
will be used for decrypting the partition.

cryptsetup luksFormat /dev/sdX3

Decrypt the partition and give it a name, I will call it

cryptsetup open /dev/sdX3 crypted-nixos

=> https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMCrypt dm-crypt

## Formatting

Format the partition and label them.

mkfs.vfat -F32 -n boot /dev/sdX1 # Boot
mkswap -L swap /dev/sdX2 # Swap
swapon /dev/sdX2 # Activate swap
mkfs.btrfs -L nixos /dev/mapper/crypted-nixos # Root

## Mounting & Subvolumes

We now have one btrfs volume and we will have to create some

 Name   Mount point  Purpose                           
 @      `/'          Root filesystem                   
 @home  `/home'      Home directory, will be backed up 

The home directory will backed up, everything else is either managed
by nix or just temporary files. We will first mount our encrypted root
partition and then create the subvolumes.

mount -t btfs /dev/mapper/crypted-nixos /mnt # Remember the device name?

btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home

umount /mnt

Once the subvolumes have been created, we will mount them with our
desired options.

mount -o subvol=@,compress=lzo,noatime /dev/mapper/crypted-nixos /mnt

mkdir /mnt/home
mount -o subvol=@home,compress=lzo,noatime /dev/mapper/crypted-nixos /mnt/home

And don't forget to mount the boot partition!

mkdir /mnt/boot
mount /dev/sdX1 /mnt/boot

We can run `btrfs subvol list /mnt/' to list our subvolumes and make
sure everything is correct.

## Configuration

Now we can install NixOS on the filesystem. First, generate a base

nixos-generate-config --root /mnt

Since we have encryption, we need to make sure that we have the
following in our `hardware-configuration.nix' or `configuration.nix'.

{ config, lib, pkgs, ... }:

  boot.supportedFilesystems = [ "btrfs" ];

  boot.initrd.luks.devices = {
    "crypted-nixos" = {
      device = "/dev/disk/by-uuid/<uuid>";

Replace `crypted-nixos' with the name of your device, and replace
`<uuid>' with the uuid of `/dev/sdX3'. The rest of the config is left
for you to configure yourself.

Go back to the shell and install the system.


If it all goes well, we should be able enter our dm-crypt passphrase
and login as a user.

## Post-installation

When your system works you probably want to make snapshots on a
regular basis in case something goes wrong. I like to take snapshots
every time my system starts up and shuts down, so that's what we will
configure in this guide.

We first want to create a script which will take snapshot for us. The
following script is based on a script from the [Gentoo wiki].

{ config, lib, pkgs, ... }:
  btrfsSnapshot = pkgs.writers.writeBashBin "btrfs-snapshot"
    NOW=$(date +"%Y-%m-%d_%H:%M:%S")
    if [ ! -e /mnt/backup/home ]; then
      mkdir -p /mnt/backup/home
    cd /
    ${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot /home "/mnt/backup/home/home_''${NOW}"
  environment.systemPackages = [

Now we want to create a systemd service that runs this script on
startup and shutdown.

{ config, lib, pkgs, ... }:

  systemd.services."btrfs-snapshot" = {
    description = "Create btrfs snapshot on startup and shutdown.";
    serviceConfig = rec {
      ExecStart = "${btrfsSnapshot}/bin/btrfs-snapshot";
      ExecStop = ExecStart;
      Type = "oneshot";
      RemainAfterExit = true;
    requiredBy = [ "multi-user.target" ];

We can also scrub our file system once every month.

{ config, lib, pkgs, ... }:

  services.btrfs.autoScrub = {
    enable = true;
    fileSystems = [ "/" ];
    interval = "monthly";

=> https://wiki.gentoo.org/wiki/Btrfs#Snapshots Gentoo wiki

A src/posts/securing-nixos-with-yubikey.gmi => src/posts/securing-nixos-with-yubikey.gmi +149 -0
@@ 0,0 1,149 @@
# Securing NixOS with Yubikey
In this blog post I will go over some things I have configured with
NixOS and a yubikey to improve the security of my system. I will not go
into detail on how to setup a GPG keypair, there are already plenty of
great tutorials. [1]

## GnuPG

To use GPG with a yubikey, we first need to install some packages, put
the following in your `configuration.nix'

  services.pcscd.enable = true;
  environment.systemPackages = with pkgs; [

  services.udev.packages = with pkgs; [


We will export the subkeys to our yubikey so we can use it when
signing and decrypting mail, but first plug in the yubikey and run

$ gpg --card-status

Then run `gpg --card-edit' and you should see a prompt like this.


Type `admin' and then `passwd' to change the user and the admin
pin. The user pin will be used for day-to-day things like signing and
decrypting files, the admin pin will only be used for operations
concerning the configuration of the yubikey, eg. adding subkeys. The
default user pin is `123456' and the default admin pin is `12345678'.

Now it's time to export the keys, beware that this process will remove
the keys from your computer, so make sure your keys are backed up on
an external drive.

gpg --edit-key <keyid>

Secret subkeys are available.

pub  rsa4096/33947BA1AA8847FF
     created: 2020-12-13  expires: never       usage: C   
     trust: ultimate      validity: ultimate
ssb  rsa4096/D1B318ACDABCAEE6
     created: 2020-12-13  expires: 2021-12-13  usage: S   
     card-no: 0006 14257444
ssb  rsa4096/38E09A208656B970
     created: 2020-12-13  expires: 2021-12-13  usage: E   
     card-no: 0006 14257444
ssb  rsa4096/18ED52D1A730A8CA
     created: 2020-12-13  expires: 2021-12-13  usage: A   
     card-no: 0006 14257444
[ultimate] (1). yoctocell <public@yoctocell.xyz>


Mark the signing subkey with `key 1' and run `keytocard' to export it
to your yubikey. When it has been exported you have to unmark the
signing key by running `key 1' again, you will see that the `*' next
to the key disappears. Repeat the same process for `key 2' and `key
3', then type `quit' to exit.

Run `gpg -K' and you should see something like this

sec#  rsa4096/33947BA1AA8847FF 2020-12-13 [C]
      Key fingerprint = 4217 475C B91A 4C94 3FCE  C870 3394 7BA1 AA88 47FF
uid                 [ultimate] yoctocell <public@yoctocell.xyz>
ssb>  rsa4096/D1B318ACDABCAEE6 2020-12-13 [S] [expires: 2021-12-13]
ssb>  rsa4096/38E09A208656B970 2020-12-13 [E] [expires: 2021-12-13]
ssb>  rsa4096/18ED52D1A730A8CA 2020-12-13 [A] [expires: 2021-12-13]

The `>' next to `ssb' means that it is a pointer to the subkey on your

If you are currently running a live OS like Tails, you have to export
your subkeys to an external drive.

gpg --armor --output=/path/to/external/drive --export-secret-subkeys <keyid>

You can import the subkeys on your main computer by running

gpg --import /path/to/external/drive

To test if everything is working, encrypt a file and then decrypt it
with your private key

echo 'test' > test.txt
gpg -o test.gpg -e -r <keyid>
gpg --decrypt test.gpg

This should prompt you for the user pin you created for your yubikey.

## PAM

There is a PAM modules that allows us to use the yubikey to
authenticate when logging in.

security.pam.yubico = { 
  enable = true;
  debug = true;
  mode = "challenge-response"; 
  control = "required";

You then need to run the following commands.

nix-shell -p yubico-pam -p yubikey-personalization
ykpersonalize -2 -ochal-resp -ochal-hmac -ohmac-lt64 -oserial-api-visible
ykpamcfg -2 -v

You now need your yubikey and your password to login to your machine,
if don't want to enter the password just remove the `control =
"required";' line.


[1] See [here], [here] and [here].