Random texts

Anything that I feel I have to write down and that I'm not embarassed enough to hide. RSS and ActivityPub (@tokudan@blog.tokudan.de).

NOTE: This text describes how I set up #Hubzilla on #NixOS. As I'm no longer using Hubzilla, I've just copied this text over for archival purposes.

PHP webapplications usually ignore all sensible conventions that exist in the unix world. Your typical php webservice needs to be put directly into the document root or a subdirectory of the webserver. That's not a big deal if it is completely static. But usually it is not, it typically needs a somewhat static config.php containing a database password, it may need a temporary directory, a log directory and maybe another directory for long-term storage. This is bad, because you can't just change a link to point to a different version. This is bad in terms of security because the program must reside in a writeable directory. This is bad because config.php with your precious database password is in a directory that's readable by the webserver. Typically you have another problem: all php webservices share a common user that they run as.

NixOS and its package manager Nix completely clashes with that. All derivations are put into /nix/store world-readable and immutable. There's no place for a config.php with a database password in /nix/store. Even root cannot write to /nix/store. The distinction between application and data is enforced by NixOS. You could put the webapplication into /srv/www or a similar directory, but you would lose all of the features that make Nix so good. Instead there's no other sensible option than to split the webapplication into the program and data part. The trick is to set symlinks during the build. I'm going to use hubzilla as an obvious example here.

Building Hubzilla with Nix

Nix first needs some generic information about how and where to download Hubzilla:

{ stdenv, lib, fetchgit, php, dataDir ? null }:
stdenv.mkDerivation rec {
  name = "hubzilla-${version}";
  version = "3.6.1";
  rev = "${version}";

  src = fetchgit {
    inherit rev;
    url = "https://framagit.org/hubzilla/core.git";
    sha256 = "1zaczw4mxxbv7p6xmmf8wpy54jmnf980yd21c4kfncmh3ri0mrf6";

  nativeBuildInputs = [ php ];

  phases = [ "unpackPhase" "installPhase" ];
  installPhase = ''
    cp -Rp ./ $out/
    cd "$out"
    echo Building documentation...
    TEMP=$(mktemp -d)
    ln -s $TEMP/store $out/store
    mkdir -p "$TEMP/store/[data]/smarty3"
    php util/importdoc
    rm -rf "$out/store" "$TEMP/store"
    ${lib.optionalString (dataDir != null) ''
      ln -s ${dataDir}/htconfig.php $out/.htconfig.php
      ln -s ${dataDir}/addon $out/addon
      ln -s ${dataDir}/extend $out/extend
      ln -s ${dataDir}/store $out/store
      mv $out/view/theme $out/view/theme.dist
      ln -s ${dataDir}/view/theme $out/view/theme
      ln -s ${dataDir}/widget $out/widget

If you store the above code in default.nix and build it with nix-build -E 'with import <nixpkgs> { }; callPackage ./default.nix { }', you already get the hubzilla source in a /nix/store and even updated documentation in there. Not special so far. Try to build it with nix-build -E 'with import <nixpkgs> { }; callPackage ./default.nix { dataDir = "/var/lib/hubzilla" }'. Now you get a special version that expects its writeable directories and config file in /var/lib/hubzilla. Everytime you change that directory you obviously get a new derivation in /nix/store. The nice thing about this is that a version upgrade is just a change of the version number in the file and thus rollbacks should work – as long as the database is not upgraded. I also like that I can give the dataDir permissions that forbid the webserver any access. Only the php processes can access dataDir.

I haven't noticed any downsides yet, but I haven't delved into themes or addons yet, so there may be some issues later.

#NixOS is a linux distribution that has a very different approach compared to other distributions.

You do not change configuration files of applications. You just change the build instructions that the package manager Nix uses to build the system. On NixOS the full system is rebuilt everytime you want to change even a minor detail. A rebuild means that the Nix package manager reads in NixPKGs, then reads in your local build instructions (e.g. “services.openssh.enable = true”) to complement them and then builds all derivations that are required for this specific configuration. A derivation is roughly similar to a package in other distributions, but in NixOS even /etc/ssh/sshd_config has its own derivation, meaning that it gets built automatically. Every derivation that gets built, ends up in a directory in /nix/store, indexed with a checksum over all its version information, build instruction and all its dependencies. That means when you change the build instructions (even if you just insert an irrelevant space in a field somewhere), change a dependency or update to a newer version, Nix will put it into a different directory in /nix/store. That in turn means that Nix can easily determine if it has already built a specific configuration or program with its specific dependencies. If the path in /nix/store exist, it has already been built. Oh, and /nix/store is immutable. You are not supposed to change any files in there and there are many good reasons to just accept that and not even try it. If you want to make a change, edit the build instructions or the system configuration and let Nix rebuild the system. By following that, you gain the ability to just rollback to older versions (called generations) of your system configuration. Made a change and noticed that it doesn't work well? sudo nixos-rebuild --rollback And you're good. The system doesn't boot anymore because of a software problem? Hit space in the boot manager and boot an older generation. Your harddisk failed and you have to reinstall your system? With NixOS you still have to partition your drive manually from the installation CD/DVD, that's a bit annoying. But then you just copy /etc/nixos from your backup, tell Nix to install the system (completely non-interactive). Finally restore your /home from the latest backup and your system will be in exactly the same state as before the disk failure. Well ok: it will be in the same state as your last backup. But there is no question about the packages you had installed or their versions. Also the system configuration is completely included in /etc/nixos, so just backing up /etc/nixos and /home is sufficient for a desktop or laptop. For servers you obviously may need other directories as well, but that depends on the applications running on them.

On my systems /etc/nixos is a git repository. That's enough to allow me to share this system configuration on multiple systems. I'm using NixOS since 2015 now and I really don't want to go back. Ansible and Puppet are just workarounds for the package and /etc hell.