diff --git a/.editorconfig b/.editorconfig index 37edb2968..ae265aaa9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -47,7 +47,7 @@ end_of_line = lf [pkg/PKGBUILD] end_of_line = lf -[*.rb] +[*.{rb,nix}] end_of_line = lf indent_size = 2 indent_style = space diff --git a/.gitignore b/.gitignore index 76a4ab9c8..b99c6fe41 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.zst demo/ +result/ *.snap k8s/charts/meerkat-dsa/*.tgz diff --git a/apps/meerkat-docs/docs/development.md b/apps/meerkat-docs/docs/development.md index 1747e3fe4..52e04cabc 100644 --- a/apps/meerkat-docs/docs/development.md +++ b/apps/meerkat-docs/docs/development.md @@ -124,6 +124,57 @@ Run `brew uninstall meerkat_dsa` to uninstall it from your system. Run `brew test -d meerkat_dsa` to run tests after it is installed. +### Nix Package + +First, ensure that `pkg/default.nix` is moved to the root of this repository: +in other words, it should be in the same folder as `nx.json`. Then run: + +```bash +nix-build -E '(import {}).callPackage ./default.nix {}' +``` + +I don't really know why that is needed. Why doesn't it "just work"(tm)? + +Then, your build outputs will be symlinked to `./result`. Just poke around in +there: you'll get it. + +To test the Systemd service (which is a separate package), it seems like you +have to have NixOS. However, you can do a little pre-validation on it like so: + +```bash +nix-instantiate --parse pkg/meerkat-dsa.nix +``` + +Then you can do a little more validation like so: + +```bash +nix-instantiate '' -A config.systemd.services.meerkat-dsa -I nixos-config=./pkg/meerkat-dsa.nix +``` + +If you see + +``` +error: attribute 'meerkat-dsa' in selection path 'config.systemd.services.meerkat-dsa' not found +``` + +try removing `lib.mkIf cfg.enable` from `meerkat-dsa.nix`. + +### UPDATE ON NIX + +Creating the Nix package wasn't too hard. Actually, it was one of the easiest +packagings I have done so far! But using a Flake on NixOS, wiring up the +Systemd service, etc. is from Hell. I had to write so much Nix "code" to still +have problem after problem. One remaining problem I have is `pkgs.meerkat-dsa` +not being defined. I can try to get this working with an overlay, but that is +not how this would really be used once a proper Nix package is published; doing +so would make this whole thing a lot simpler. + +So I have decided on this: I am going to release Meerkat DSA 4.0.0 without a +Nix package. Then, once I have the commit hash, I am going to submit a PR to +get a Nix package published for Meerkat DSA. Once that's out, then I will work +on the OS module, flake, whatever. If that doesn't work out, or if I never get +to it, too bad: stop being so damn difficult. + ## CI [Here](https://github.com/actions/runner-images) are the different runners diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..c5c9881da --- /dev/null +++ b/flake.nix @@ -0,0 +1,40 @@ +{ + description = "Meerkat X.500 Directory System Agent (DSA) and LDAP Server"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + systems = [ "x86_64-linux" ]; + forAllSystems = f: + nixpkgs.lib.genAttrs systems (system: + f { + inherit system; + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + }; + } + ); + in { + packages = forAllSystems ({ pkgs, ... }: { + default = pkgs.meerkat-dsa; + meerkat-dsa = pkgs.meerkat-dsa; + }); + + apps = forAllSystems ({ pkgs, ... }: { + default = { + type = "app"; + program = "${pkgs.meerkat-dsa}/bin/meerkat"; + }; + }); + + overlays.default = import ./pkg/overlay.nix; + nixosModules.meerkat-dsa = import ./pkg/meerkat-dsa.nix; + checks = forAllSystems ({ pkgs, ... }: { + build = pkgs.meerkat-dsa; + }); + }; +} diff --git a/pkg/default.nix b/pkg/default.nix new file mode 100644 index 000000000..c227210e3 --- /dev/null +++ b/pkg/default.nix @@ -0,0 +1,82 @@ +{ lib +, stdenv +, nodejs_24 +, buildNpmPackage +, fetchFromGitHub +}: + +let + buildStage = buildNpmPackage { + pname = "meerkat-dsa-build"; + version = "4.0.0"; + src = fetchFromGitHub { + owner = "Wildboar-Software"; + repo = "directory"; + rev = "v4.0.0-rc1"; + hash = "sha256-n26A/OtmRuMcnrihSl1b+YFKd23EIg2gsXKRC+AZsq8="; + }; + npmDepsHash = "sha256-eih0APtkKpYzJenDP+xNHkfmQ6ynbO57fXRemaFzEHo="; + makeCacheWritable = true; + npmFlags = [ "--no-audit" "--no-fund" "--no-save" "--ignore-scripts" "--verbose" "--legacy-peer-deps" ]; + buildPhase = '' + npx nx \ + --tuiAutoExit=true \ + --outputStyle=static \ + run meerkat:build \ + --skipNxCache \ + --skipRemoteCache \ + --skip-nx-cache \ + --verbose + ''; + installPhase = '' + cp -r dist/apps/meerkat $out + cp doc/man/man1/meerkat-dsa.1 $out + cp doc/man/man5/meerkat.env.5 $out + ''; + }; + + runtimeStage = buildNpmPackage { + pname = "meerkat-dsa"; + version = "4.0.0"; + src = buildStage; + npmDepsHash = "sha256-uP0+7G8ujB9aG62za4+3t+X6EWaxoGFf/xZZhf/47jw="; + npmFlags = [ "--no-audit" "--no-fund" "--no-save" "--ignore-scripts" "--verbose" ]; + dontNpmBuild = true; + installPhase = '' + mkdir -p $out/lib + cp -r * $out/lib + cp prisma/prisma.config.ts $out/lib + mkdir -p $out/bin + # This will overwrite the bin that buildNpmPackage creates. + cat > $out/bin/meerkat <<'EOF' + #!${stdenv.shell} + set -euo pipefail + + # If the migration fails, still carry on: we do not want a failed migration + # (which is infrequently needed to begin with) to make us unable to start + # Meerkat DSA + # + # NOTE: Prisma seems to search for migrations relative to the config file, not + # the current directory. + PRISMA_HIDE_UPDATE_MESSAGE=1 ${nodejs_24}/bin/npx prisma migrate deploy \ + --schema @APP_DIR@/lib/prisma/schema.prisma \ + --config @APP_DIR@/lib/prisma.config.ts || true + + exec ${nodejs_24}/bin/node @APP_DIR@/lib/main.js start "$@" + EOF + substituteInPlace $out/bin/meerkat --replace "@APP_DIR@" "$out" + chmod +x $out/bin/meerkat + mkdir -p $out/share/man/man1 + mkdir -p $out/share/man/man5 + install -m 644 meerkat-dsa.1 $out/share/man/man1/meerkat-dsa.1 + install -m 644 meerkat.env.5 $out/share/man/man5/meerkat.env.5 + ''; + meta = with lib; { + description = "Meerkat X.500 Directory System Agent (DSA) and LDAP Server"; + license = licenses.mit; + platforms = platforms.linux; + mainProgram = "meerkat"; + }; + }; +in +runtimeStage diff --git a/pkg/meerkat-dsa.nix b/pkg/meerkat-dsa.nix new file mode 100644 index 000000000..665074418 --- /dev/null +++ b/pkg/meerkat-dsa.nix @@ -0,0 +1,75 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.meerkat-dsa; +in { + options.services.meerkat-dsa = { + enable = lib.mkEnableOption "Meerkat X.500 Directory System Agent (DSA) and LDAP Server"; + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/meerkat"; + }; + + environmentFile = lib.mkOption { + type = lib.types.path; + default = "/etc/meerkat/meerkat.env"; + description = "Environment file (using .env syntax)"; + }; + }; + + config = lib.mkIf cfg.enable { + + users.users.meerkat = { + isSystemUser = true; + group = "meerkat"; + }; + + users.groups.meerkat = {}; + + # Documented here: https://github.com/NixOS/nixos/blob/5f444a4d8d49a497bcfabe2544bda264c845653e/modules/system/boot/systemd-unit-options.nix + # Example application: https://github.com/DMarby/picsum-photos/blob/a4359f9d85e99aaf11a110590a681510e21130c4/flake.nix#L178 + systemd.services.meerkat-dsa = { + description = "Meerkat DSA"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + # This seems to be required even though undocumented. + startLimitBurst = 10; + # This seems to be required even though undocumented. + startLimitIntervalSec = 5; + serviceConfig = { + Type = "simple"; + User = "meerkat"; + Group = "meerkat"; + ExecStart = "${pkgs.meerkat-dsa}/bin/meerkat"; + Restart = "on-failure"; + RestartSec = "30s"; + WorkingDirectory = cfg.dataDir; + EnvironmentFile = cfg.environmentFile; + PassEnvironment = "LANG NO_COLOR"; + NoNewPrivileges = "yes"; + ProtectSystem = "full"; + ProtectHome = "yes"; + ProtectProc = "invisible"; + PrivateTmp = "yes"; + PrivateDevices = "yes"; + PrivateUsers = "self"; + SecureBits = ""; + ProtectClock = "yes"; + ProtectHostname = "yes"; + ProtectKernelModules = "yes"; + ProtectKernelLogs = "yes"; + ProtectControlGroups = "yes"; + RestrictRealtime = "yes"; + PrivateMounts = "yes"; + SystemCallFilter="~@cpu-emuation @chown @debug @keyring @module @mount @obsolete @privileged @reboot @resources @setuid @swap"; + LogNamespace = "meerkat"; + }; + environment = { + NODE_ENV = "production"; + DATABASE_URL = "file:${cfg.dataDir}/meerkat.db"; + PRISMA_HIDE_UPDATE_MESSAGE = "1"; + MEERKAT_LOG_SYSTEMD_LEVEL_PREFIX = "1"; + }; + }; + }; +} diff --git a/pkg/overlay.nix b/pkg/overlay.nix new file mode 100644 index 000000000..d687b788c --- /dev/null +++ b/pkg/overlay.nix @@ -0,0 +1,3 @@ +final: prev: { + meerkat-dsa = prev.callPackage ./default.nix {}; +}