Hubzilla on NixOS

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.