Nix Tricks

Published:
January 30, 2024
Updated:
August 22, 2024

In which I compile snippets of useful Nix code

Anki sync server on Nix-Darwin

The following was tested with Anki version 23.12.1.

Here’s how to configure the Anki sync server to run on macOS using Nix-Darwin:

1. Configure and run Nix-Darwin

Make sure that Nix-Darwin works correctly on your machine. I use a flake file for configuring Nix-Darwin and it looks somewhat like so:

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    nix-darwin.url = "github:LnL7/nix-darwin";
    nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs =
    { self
    , nix-darwin
    , nixpkgs
    }@inputs: {
      darwinConfigurations."lithium" =
        let
          system = "aarch64-darwin";
        in
        nix-darwin.lib.darwinSystem {
          inherit system;.Reverse
          modules = [
            { _module.args = inputs; }
            ./darwin-configuration.nix
          ];
        };
    };
}

Next, in the same folder as the above flake.nix file, I place a darwin-configuration.nix file containing the following:

# darwin-configuration.nix
{ ... }:
{
  # ./anki.nix will be created further below
  imports = [ ./anki.nix ];
}

2. Adding the Anki sync server configuration Nix module

Now, add the below contents to a file called anki.nix in the same directory. The file contains all the configuration needed to install and run anki-sync-server as a launchd daemon.

Here’s some more information about how Nix modules are written.

# anki.nix
{ pkgs, ... }:
let
  anki-sync-server = pkgs.anki-sync-server;
  logPath = "/var/log/anki-sync-server";
in
{
  # gid was chosen quite arbitrarily
  users.groups.anki-sync-server = { gid = 601; };
  users.users.anki-sync-server = {
    createHome = false;
    description = "Anki-sync-server user";
    gid = 601;
    # uid chosen arbitrarily
    uid = 601;
    isHidden = true;
  };
  users.knownGroups = [ "anki-sync-server" ];
  users.knownUsers = [ "anki-sync-server" ];
  launchd.daemons.anki-sync-server = {
    script = ''
      # This file will be created below
      SYNC_USER1="$(cat /etc/anki-sync-server/sync_user1)"
      export SYNC_USER1
      # If you are interested in hashing the password, read this:
      # https://docs.ankiweb.net/sync-server.html#hashed-passwords
      exec ${anki-sync-server}/bin/anki-sync-server
    '';
    serviceConfig = {
      KeepAlive = true;
      StandardOutPath = "${logPath}/anki-sync-server.stdout.log";
      StandardErrorPath = "${logPath}/anki-sync-server.stderr.log";
      UserName = "anki-sync-server";
      EnvironmentVariables = {
        SYNC_HOST = "127.0.0.1";
        SYNC_PORT = "18090";
        SYNC_BASE = "/var/anki-sync-server";
      };
    };
  };

3. Switch to the new Nix-Darwin config

Run the following in a bash session with Nix and Nix-Darwin available:

# change directory to the path where the above created flake.nix,
# darwin-configuration.nix, and anki.nix can be found
cd $TO_THAT_LOCATION
# then switch to the new configuration
darwin-rebuild switch --flake $PWD

This will most likely prompt you for your admin password.

4. Create a user

Create a user that you can sign in with for Anki by placing a credentials file containing username and password, separated by a colon, into /etc/anki-sync-server/sync_user1.

Example:

local_anki_user:verysafepassw0rd

Make sure that only the above created anki-sync-server user can read the credentials file.

chmod 400 /etc/anki-sync-server/sync_user1
chown anki-sync-server:anki-sync-server /etc/anki-sync-server/

5. Provision run time folder

The Anki sync server is almost good to go. It needs a directory where the runtime data (your Anki decks etc.) can be stored. The following commands will create the directories and make sure that only anki-sync-server can read the runtime data.

mkdir -p /var/anki-sync-server
chown anki-sync-server:anki-sync-server /var/anki-sync-server
chmod 0400 /etc/anki-sync-server/sync_user1

6. Kick start the server

Assuming that you haven’t changed launchd.labelPrefix in nix-Darwin, you can make sure that anki-sync-server starts correctly by running

sudo launchctl kickstart -k -p system/org.nixos.anki-sync-server

7. Connect with Anki

Anki allows you to set a custom sync server

Anki allows you to set a custom sync server Open in new tab (full image size 69 KiB)

On macOS, follow these steps to connect Anki to the Anki sync server:

  1. Open the Anki preferences by going to Anki > Preferences … or pressing CMD + ,.
  2. Open the Syncing tab and enter http://localhost:18090.
  3. Close the preferences.
  4. Press Sync in the top toolbar.
  5. Anki will prompt you for username and password. Use the credentials from above.
  6. Anki will now sync your data to your own Anki sync server!

Cross-Compiling Arm Assembly in a Nix Flake

We want to compile this trivial assembly targeting ARM assembly on a x86_64 Linux machine (running Debian 12 with Nix in this case):

  .global main
  .type main, %function
main:
  mov w0, #123
  ret

The assembly file is in a file called src/main.s.

The program return 123 as a status code, so we can check whether the program ran correctly later by checking echo $? in bash or similar. We can run the program using qemu-aarch64 (not qemu-system-aarch64) to emulate an ARM CPU and corresponding Linux user land, instead of having to spin up a virtual machine – quite handy when reverse engineering binaries on an x86_64 host.

We want to work with a Nix flake here. That means better support for different targets, and the ability to have better reproducibility between builds, as all Nix dependencies are fixed in a flake.lock file.

I also want to use flake-utils for an easier time specifying the flake file.

{
  description = "Arm64 (aarch64) cross-compile demo";

  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          crossSystem = {
            config = "aarch64-unknown-linux-gnu";
          };
        };
      in
      {
        packages = rec {
          cross-arm-64-asm = pkgs.callPackage
            ({ stdenv, gcc }:
              stdenv.mkDerivation {
                name = "cross-arm-64-asm";
                nativeBuildInputs = [
                  gcc
                ];
                phases = [ "buildPhase" "installPhase" ];
                buildPhase = ''
                  $CC -march=armv8-a ${./src/main.s} -o main
                '';
                installPhase = ''
                  mkdir -p $out/bin
                  cp main $out/bin
                '';

              }
            )
            { };
          default = cross-arm-64-asm;
        };
      }
    );
}

Build the file by running nix build -L .#. On my system I got the following output:

warning: Git tree '/home/justusperlwitz/projects/nix-tricks' is dirty
warning: Ignoring setting 'auto-allocate-uids' because experimental feature 'auto-allocate-uids' is not enabled
warning: Ignoring setting 'impure-env' because experimental feature 'configurable-impure-env' is not enabled
cross-arm> Running phase: buildPhase
cross-arm> Running phase: installPhase

We see that the output is placed in a symlinked folder called result.

# tree result
result
└── bin
    └── main

2 directories, 1 file

We check the contents of the resulting result/bin/main file:

# file result/bin/main
result/bin/main: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/j8h1n0kgl15gpk270i7mr441yamnzvdg-glibc-aarch64-unknown-linux-gnu-2.38-27/lib/ld-linux-aarch64.so.1, for GNU/Linux 3.10.0, not stripped

If you have QEMU on your system, you can then run it like so:

$qemu-aarch64 ./result/bin/main
$echo $?
# Outputs `123`

Further reading

I would be thrilled to hear from you! Please share your thoughts and ideas with me via email.

Back to Index