Repackaging .deb file for NixOS and some questions about the packaging process

I want to package the Croatian electronic ID middleware for NixOS. Unfortunately, they don’t offer the source but provide only the .deb package that can be downloaded from this link.

It’s my first time packaging something for NixOS so I’d appreciate any general pointers in this process as well.

Here’s what I’ve got so far:

{ pkgs ? import <nixpkgs> {} } : pkgs.stdenv.mkDerivation rec {
  name = "eidhr";
  src = pkgs.fetchurl
    {
      url = "https://eid.hr/datastore/filestore/10/eidmiddleware_v3.7.4_amd64.deb";
      hash = "sha256-aWAvwtGrFH2HUNkzLW1WqukIy4NemN9Vrb/DMGYGf5Y=";
    };

  nativeBuildInputs = with pkgs; [ dpkg openssl pcre libp11 autoPatchelfHook libsForQt5.full cups pcsclite ];

  unpackPhase = "dpkg-deb -x $src unpack";

  installPhase =
    ''
      mkdir -p $out/etc
      cp -r unpack/etc/* $out/etc/
      
      mkdir -p $out/usr
      cp -r unpack/usr/* $out/usr/
    '';
}

And here’s the extracted .deb file tree, and also what I get in the result folder once I ru nix-build default.nix:

.
├── etc
│   ├── ssl
│   │   └── certs
│   │       ├── AKDCARoot.pem
│   │       └── HRIDCA.pem
│   └── xdg
│       └── autostart
│           └── signer.desktop
└── usr
    ├── lib
    │   └── akd
    │       └── eidmiddleware
    │           ├── Client
    │           ├── License.bin
    │           ├── Signer
    │           ├── certificates
    │           │   ├── AKDCARoot.pem
    │           │   └── HRIDCA.pem
    │           ├── lib
    │           │   ├── libQt5Core.so.5
    │           │   ├── libQt5DBus.so.5
    │           │   ├── libQt5Gui.so.5
    │           │   ├── libQt5Network.so.5
    │           │   ├── libQt5PrintSupport.so.5
    │           │   ├── libQt5Widgets.so.5
    │           │   ├── libQt5XcbQpa.so.5
    │           │   ├── libcrypto.so.1.0.0
    │           │   ├── libp11.so.2
    │           │   ├── libpcre16.so.3
    │           │   ├── libpkcs11.so
    │           │   └── libssl.so.1.0.0
    │           ├── pkcs11
    │           │   ├── libEidPkcs11.so
    │           │   └── libEidPkcs11.so.lic
    │           ├── plugins
    │           │   ├── imageformats
    │           │   │   ├── libqjp2.so
    │           │   │   └── libqjpeg.so
    │           │   ├── platforms
    │           │   │   └── libqxcb.so
    │           │   └── printsupport
    │           │       └── libcupsprintersupport.so
    │           └── qt.conf
    └── share
        ├── applications
        │   ├── eidclient.desktop
        │   └── signer.desktop
        ├── ca-certificates
        │   └── akd
        │       ├── AKDCARoot.crt
        │       └── HRIDCA.crt
        ├── doc
        │   └── akd
        │       └── eidmiddleware
        │           ├── changelog.Debian.gz
        │           └── copyright
        └── pixmaps
            └── eidclient32.png

When I try to build this with nix-build default.nix it builds successfully. At first autoPatchelf complained about missing deps, but I’ve managed to locate the correct packages to shut it up (although I’m not sure if they are really correct).

When I try to run the Client program in the usr/lib/akd/eidmiddleware I get this:

Documents/eid/result
▶ cd usr/lib/akd/eidmiddleware

lib/akd/eidmiddleware
▶ ./Client
xkbcommon: ERROR: failed to add default include path /usr/share/X11/xkb
Qt: Failed to create XKB context!
Use QT_XKB_CONFIG_ROOT environmental variable to provide an additional search path, add ':' as separator to provide several search paths and/or make sure that XKB configuration data directory contains recent enough contents, to update please see http://cgit.freedesktop.org/xkeyboard-config/ .
[1]    12944 segmentation fault (core dumped)  ./Client

When I set the QT_XKB_CONFIG_ROOT to some dummy value the QT_XKB… error disappears e.g.:

export QT_XKB_CONFIG_ROOT=/usr/share/X11/xkb

However I still have the segmentation fault error. I’m assuming the autoPatchelf didn’t do the job correctly, or that I’ve included the wrong dependency version.

SOME GENERAL QUESTIONS / COMMENTS

  • When I try to run the program I’m not in the nix shell. I just run it from the main OS environment. Does this make any difference when I want to “test” if my package is OK? I’m not sure how to test stuff other than just running stuff I have in the result folder.
  • I’ve tried running nix-shell default.nix first, and then running the Client from the result folder but I get the same result.
  • I’ve seen the use of wrapProgram in some packages. I suspect I need to do something with it, but not sure exactly what.
  • I’ve “installed” my package on my OS by including the nix expression and adding it to environment.systemPackages property, however, I don’t know how to run the program now that it’s in my system. I’m not even sure where to find it. I’d expect that my menu would pick up the .desktop files present in the .deb file but I guess not.
  • I’m on sway wayland. What do I do with the QT_XKB_CONFIG_ROOT thingie. From what I understand that’s X11 stuff. Any way around that?
  • Since NixOS has a bit different file structure, I’m assuming I still have to do some rearranging of .deb files? Not exactly sure what.
  • I can’t really figure out how to use the nix-shell to develop my package. When I try to run the installPhase i get some nonsense about no Makefile and nothing happens:
~/Documents/eid
▶ nix-shell default.nix

[nix-shell:~/Documents/eid]$ unpackPhase
unpacking source archive /nix/store/r5f8j0bgpwlmv4cmd1g2xp57rw0s5qcj-eidmiddleware_v3.7.4_amd64.deb
source root is root
setting SOURCE_DATE_EPOCH to timestamp 1694157001 of file root/usr/share/pixmaps/eidclient32.png

[nix-shell:~/Documents/eid]$ installPhase
no Makefile or custom installPhase, doing nothing

I tried to package this, and this is as far as I got:

(import <nixpkgs> { }).callPackage (

  {
    stdenv,
    fetchurl,
    dpkg,
    autoPatchelfHook,
    libsForQt5,
    pcsclite,
  }:

  stdenv.mkDerivation rec {
    pname = "eidhr";
    version = "3.7.4";

    src = fetchurl {
      url = "https://eid.hr/datastore/filestore/10/eidmiddleware_v${version}_amd64.deb";
      hash = "sha256-aWAvwtGrFH2HUNkzLW1WqukIy4NemN9Vrb/DMGYGf5Y=";
    };

    nativeBuildInputs = [
      dpkg
      autoPatchelfHook
      libsForQt5.wrapQtAppsHook
    ];

    buildInputs = [
      libsForQt5.qtbase
      libsForQt5.qtimageformats
      pcsclite
    ];

    installPhase = ''
      mkdir -p $out/share/eidhr
      cp -r usr/lib/akd/eidmiddleware/* $out/share/eidhr
      rm -r $out/share/eidhr/{lib,plugins,qt.conf}

      mkdir -p $out/bin
      ln -s $out/share/eidhr/Client $out/bin/eidhr-client
      ln -s $out/share/eidhr/Signer $out/bin/eidhr-signer

      cp -r etc $out/etc
      cp -r usr/share/* $out/share
      sed -i "s|Exec=.*|Exec=eidhr-client|g" $out/share/applications/eidclient.desktop
      sed -i "s|Exec=.*|Exec=eidhr-signer|g" $out/share/applications/signer.desktop
    '';
  }

) { }

Sadly the signer executable is showing an error. I might look into it later. Maybe I just removed too much stuff.

Note that I wrapped everything inside a callPackage, since that’s how it’s supposed to be anyway (usually callPackage is found in another file: my-package = pkgs.callPackage ./a.nix {}, but I put everything into one for clarity). (callPackage populates the function’s inputs with the values from inside pkgs)

Another thing: dpkg has a default unpack hook that just spills its contents into the working directory.

As for how to restructure the .deb file, I usually put the app’s main directory inside $out/share/my-package and then symlink/makeWrapper the executables there into $out/bin (I chose some arbitrary names for the binaries, you could change those). The /usr/share files go into $out/share and /etc goes into $out/etc. (The /etc one I’m not exactly sure of, but I’ve seen it in other packages)

When packaging prebuilt apps I usually try to remove as many binaries as possible. In this case I removed the lib and plugins directory, since everything in there is packaged inside nixpkgs. This fixed the xkb errors for me. If you can’t find where a binary comes from you could use nix-locate (from nix-index or nix-index-database).

QT plugins are intersting in the sense that nixpkgs handles them differently. We need to use wrapQtAppsHook to wrap the contents of $out/bin with some glue code that allows it to actually find the qt plugins listed inside buildInputs (this uses wrapProgram, but is automatic)

The reason why you weren’t able to run the program when putting it into your NixOS config is because only executables inside $out/bin are added to the PATH.

Running nix-shell a.nix or nix develop -f a.nix only puts you in a very similar environment to the builder environment (it uses nativeBuildInputs, buildInputs, env.*, setup hooks, etc… to populate your environment), however this is not really useful in this case.

nix shell -f a.nix is what you’re probably looking for, which is very similar to adding it into your NixOS config.
nix-shell -p some-package is very similar to this last one, but only works if some-package is already inside nixpkgs.

It is confusing. You might also need to enable some experimental features for some nix commands.

I usually just do nix-build or nix build and run the executables from result/bin, so I don’t really use nix shell. It’s usually fine just to run them like that.

Thanks a lot! This will be a good start for me. I’ve read that the signer app is not working for other people as well, so I guess that’s the apps fault. But I’m having some trouble with the app saying I don’t have permission to read from the card.

I’m not sure if that’s “app-wise permission” or “system-wise” permission. I suspect I need to have those certificates / keys present in order to decode info on the card.