Setting up Zabbix with PostgreSQL and TimescaleDB - Missing Timescale schema files

Hello NixOS community,

I’m setting up Zabbix 7.0 with PostgreSQL and TimescaleDB using the following configuration:

{ config, pkgs, lib, ... }:

let
  totalMem = 16384;
  sharedBuffers = toString (totalMem / 4) + "MB";
  workMem = "16MB";
  maintenanceWorkMem = "128MB";
  effectiveCacheSize = toString (totalMem / 2) + "MB";

  # Path to official Zabbix schema files
  zabbixSchemaDir = "${pkgs.zabbix70.server}/share/zabbix/database/postgresql/";

  # Database connection info
  db_user = "zabbix";
  db_host = "localhost";
  db_name = "zabbix";
  db_passwordFile = config.sops.secrets."zabbix_db_password".path;
in {
  # SOPS secret for Zabbix DB password
  sops.secrets."zabbix_db_password" = {
    sopsFile = ../secrets/secrets_sops.yaml;
    owner = "zabbix";
    group = "zabbix";
    mode = "0440";
  };

  # Rendered SQL file with secret injected
  sops.templates."zabbix-init.sql" = {
    content = ''
      DO $$ 
      BEGIN
        IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${db_user}') THEN
          CREATE ROLE ${db_user} WITH LOGIN PASSWORD '${config.sops.placeholder.zabbix_db_password}';
        ELSE
          ALTER ROLE ${db_user} WITH PASSWORD '${config.sops.placeholder.zabbix_db_password}';
        END IF;
      END
      $$;

      CREATE DATABASE ${db_name} OWNER ${db_user};
      GRANT ALL PRIVILEGES ON DATABASE ${db_name} TO ${db_user};
      \c ${db_name}
      CREATE EXTENSION IF NOT EXISTS timescaledb;
      GRANT USAGE ON SCHEMA public TO ${db_user};
      GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ${db_user};
    '';
    owner = "postgres";
  };

  # PostgreSQL service configuration
  services.postgresql = {
    enable = true;
    extensions = [ pkgs.postgresql16Packages.timescaledb ];
    ensureDatabases = [ db_name ];
    ensureUsers = [
      {
        name = db_user;
        ensureDBOwnership = true;
      }
    ];
    authentication = ''
      local all ${db_user} md5
      host all ${db_user} 127.0.0.1/32 md5
      host all ${db_user} ::1/128 md5
    '';
    initialScript = config.sops.templates."zabbix-init.sql".path;
    settings = {
      max_connections = 100;
      shared_buffers = sharedBuffers;
      work_mem = workMem;
      maintenance_work_mem = maintenanceWorkMem;
      effective_cache_size = effectiveCacheSize;
      wal_level = "replica";
      synchronous_commit = "off";
      checkpoint_completion_target = 0.9;
      checkpoint_timeout = "15min";
      max_worker_processes = 4;
      max_parallel_workers_per_gather = 2;
      autovacuum = "on";
      autovacuum_vacuum_scale_factor = 0.05;
      autovacuum_analyze_scale_factor = 0.02;
      shared_preload_libraries = "timescaledb";
    };
  };

  # Zabbix Server service configuration
  services.zabbixServer = {
    enable = true;
    package = pkgs.zabbix70.server;
    extraPackages = with pkgs; [ nettools nmap traceroute ];
    openFirewall = true;

    database = {
      type = "pgsql";
      createLocally = false;
      host = db_host;
      port = 5432;
      name = db_name;
      user = db_user;
      passwordFile = db_passwordFile;
    };
  };

  # Zabbix Web frontend
  services.zabbixWeb = {
    enable = true;
    package = pkgs.zabbix70.web;
    frontend = "nginx";

    # Zabbix DB settings
    database = {
      type = "pgsql";
      host = db_host;
      name = db_name;
      user = db_user;
      passwordFile = db_passwordFile;
    };

    httpd.virtualHost = {
      hostName = "zabbix.test.it";
      adminAddr = "antonio.nardella@provinzia.bz.it";
    };
  };

  # Firewall settings
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 80 443 ];
  };

  # Database initialization service
  systemd.services.zabbix-init-db = {
    description = "Initialize Zabbix DB schema";
    after = [ "postgresql.service" "sops-nix.service" ];
    wants = [ "postgresql.service" "sops-nix.service" ];
    serviceConfig = {
      Type = "oneshot";
      User = db_user;
      ExecStart = pkgs.writeShellScript "zabbix-schema-load.sh" ''
        set -e
        echo "Checking if Zabbix DB schema already exists..."
        if ! ${pkgs.postgresql}/bin/psql -U ${db_user} -h ${db_host} -d ${db_name} -c "SELECT 1 FROM users LIMIT 1;" >/dev/null 2>&1; then
          echo "Importing Zabbix schema..."
          export PGPASSWORD=$(cat ${db_passwordFile})
          ${pkgs.postgresql}/bin/psql -U ${db_user} -h ${db_host} -d ${db_name} -f ${zabbixSchemaDir}/schema.sql
          ${pkgs.postgresql}/bin/psql -U ${db_user} -h ${db_host} -d ${db_name} -f ${zabbixSchemaDir}/images.sql
          ${pkgs.postgresql}/bin/psql -U ${db_user} -h ${db_host} -d ${db_name} -f ${zabbixSchemaDir}/data.sql
        else
          echo "Zabbix schema already exists. Skipping import."
        fi
      '';
    };
    wantedBy = [ "multi-user.target" ];
  };

  # Ensure zabbix-server starts after DB initialization
  systemd.services.zabbix-server = {
    requires = [ "postgresql.service" "zabbix-init-db.service" ];
    after = [ "postgresql.service" "sops-nix.service" "zabbix-init-db.service" ];
    serviceConfig = {
      LoadCredential = [
        "db_password:${db_passwordFile}"
      ];
    };
  };
}

The issue I’m encountering is that while the official Zabbix schema files are available in the Nix store at:
zabbixSchemaDir = "${pkgs.zabbix70.server}/share/zabbix/database/postgresql/";

The TimescaleDB-specific schema file (timescaledb.sql) mentioned in the Zabbix documentation is missing.

When I download the 7.0.5.tar.gz from Zabbix’s official sources, I can see the expected schema files in the archive, but they’re not included in the Nix package.

Question: How can I make these TimescaleDB schema files available in the Nix store for my configuration?

I noticed the systemd.services.zabbix-init-db service in my configuration loads the standard schema - how would I best modify this to also load the TimescaleDB schema?

Any guidance on the most NixOS-idiomatic way to handle this would be greatly appreciated!

Thanks in advance for your help.

Antonio

Should anyone ever come across this issue, what I did at this point in time is to fetch the tar.gz and import it using a script

    pkgs = import nixpkgs {
      inherit system;
      overlays = [
        (final: prev: {
          zabbix70 = prev.zabbix70 // {
            server = prev.zabbix70.server.overrideAttrs (oldAttrs: let
              tarball = oldAttrs.src;
            in {
              postInstall = (oldAttrs.postInstall or "") + ''
                echo "Unpacking Zabbix source to get TimescaleDB schema..."
                tmpdir=$(mktemp -d)
                tar -xzf ${tarball} -C "$tmpdir"
                unpacked_dir=$(find "$tmpdir" -maxdepth 1 -type d -name "zabbix*" | head -n1)

                if [ -d "$unpacked_dir/database/postgresql/timescaledb" ]; then
                  echo "Copying TimescaleDB schema from $unpacked_dir"
                  mkdir -p $out/share/zabbix/database/postgresql/timescaledb
                  cp -r "$unpacked_dir/database/postgresql/timescaledb/"* $out/share/zabbix/database/postgresql/timescaledb/
                else
                  echo "Warning: timescaledb schema directory not found in source"
                fi
              '';
            });
          };
        })

so far it works