about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--default.nix1
-rw-r--r--modules/nixos/default.nix1
-rw-r--r--modules/nixos/goatcounter.nix147
-rw-r--r--pkgs/goatcounter/default.nix39
4 files changed, 188 insertions, 0 deletions
diff --git a/default.nix b/default.nix
index f326620..9c0aa78 100644
--- a/default.nix
+++ b/default.nix
@@ -20,4 +20,5 @@
     inherit (pkgs.darwin.apple_sdk.frameworks) Cocoa;
   };
   emacs-unlimited-select = pkgs.callPackage ./pkgs/emacs-unlimited-select { };
+  goatcounter = pkgs.callPackage ./pkgs/goatcounter { };
 }
diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix
index 1901177..fb2eb61 100644
--- a/modules/nixos/default.nix
+++ b/modules/nixos/default.nix
@@ -3,4 +3,5 @@
   #
   # my-module = ./my-module;
   laminar = ./laminar.nix;
+  goatcounter = ./goatcounter.nix;
 }
diff --git a/modules/nixos/goatcounter.nix b/modules/nixos/goatcounter.nix
new file mode 100644
index 0000000..2ba0e31
--- /dev/null
+++ b/modules/nixos/goatcounter.nix
@@ -0,0 +1,147 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.services.goatcounter;
+
+  inherit (lib)
+    optionalAttrs
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    mkIf
+    types;
+
+  encodeParams = params: lib.concatStringsSep "&" (lib.mapAttrsToList (n: v: "${n}=${v}") params);
+  encoder = {
+    bool = n: v: lib.optionalString v "-${n}";
+    int = n: v: "-${n} ${toString v}";
+    list = n: v: "-${n} ${lib.concatStringsSep "," v}";
+    string = n: v: "-${n} ${v}";
+  };
+  mkArgs = args: lib.concatStringsSep " " (lib.mapAttrsToList (n: v: (encoder.${builtins.typeOf v} n v)) args);
+in
+{
+  options.services.goatcounter = {
+    enable = mkEnableOption "Easy web analytics. No tracking of personal data.";
+
+    user = mkOption {
+      type = types.str;
+      default = "goatcounter";
+      description = "User account under which goatcounter runs.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "goatcounter";
+      description = "Group under which goatcounter runs.";
+    };
+
+    package = mkPackageOption pkgs "goatcounter" { };
+
+    homeDir = mkOption {
+      type = types.path;
+      default = "/var/lib/goatcounter";
+      description = "Home directory for goatcounter user.";
+    };
+
+    database = mkOption {
+      default = {
+        type = "sqlite";
+        file = "db/goatcounter.sqlite3";
+      };
+
+      type = types.submodule {
+        options = {
+          type = mkOption {
+            type = types.enum [ "sqlite" "postgresql" ];
+            default = "sqlite";
+            description = "Database engine to use.";
+          };
+
+          file = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            description = "(sqlite) database file to use.";
+          };
+
+          params = mkOption {
+            type = with types; attrsOf str;
+            default = { };
+            description = "Database connection parameters. See `goatcounter help db`";
+          };
+        };
+      };
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "*";
+      description = "Start the server on the specified address.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8581;
+      description = "Port for goatcounter to listen on.";
+    };
+
+
+    settings = mkOption {
+      type = types.submodule rec {
+        freeformType = types.anything;
+
+        options = {
+          tls =
+            let
+              values = (types.enum [ "http" "proxy" "tls" "rdr" "acme" ]);
+            in
+            mkOption {
+              type = with types; either str (nonEmptyListOf values);
+              default = [ "acme" "rdr" ];
+              description = "Whether and how to handle TLS connections. See `goatcounter help listen`";
+            };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.goatcounter.settings = {
+      listen = lib.mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+      db = lib.mkDefault "${cfg.database.type}+${cfg.database.file}?${encodeParams cfg.database.params}";
+    };
+
+    systemd.services.goatcounter = {
+      description = "Goatcounter web analytics";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/goatcounter serve ${mkArgs cfg.settings}";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.homeDir;
+        ReadWritePaths = [ cfg.homeDir ];
+      } // optionalAttrs (cfg.port < 1024) {
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "goatcounter") {
+      goatcounter = {
+        inherit (cfg) group;
+        home = cfg.homeDir;
+        createHome = true;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "goatcounter") {
+      goatcounter = { };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/pkgs/goatcounter/default.nix b/pkgs/goatcounter/default.nix
new file mode 100644
index 0000000..2dcd1e3
--- /dev/null
+++ b/pkgs/goatcounter/default.nix
@@ -0,0 +1,39 @@
+{ lib
+, buildGoModule
+, fetchFromGitHub
+
+, withSQLite ? true
+}:
+
+buildGoModule rec {
+  pname = "goatcounter";
+  version = "2.5.0";
+
+  src = fetchFromGitHub {
+    owner = "arp242";
+    repo = "goatcounter";
+    rev = "v${version}";
+    sha256 = "sha256-lwiLk/YYxX4QwSDjpU/mAikumGXYMzleRzmPjZGruZU=";
+  };
+
+  CGO_ENABLED = if withSQLite then 1 else 0;
+  subPackages = [ "cmd/goatcounter" ];
+
+  ldflags = [ "-X=zgo.at/goatcounter/v2.Version=${version}" ];
+
+  doCheck = false;
+
+  vendorHash = "sha256-YAb3uBWQc6hWzF1Z5cAg8RzJQSJV+6dkppfczKS832s=";
+
+  meta = with lib; {
+    description = "Easy web analytics. No tracking of personal data.";
+    homepage = "https://www.goatcounter.com/";
+    license = {
+      spdxId = "LicenseRef-Goatcounter-EUPL-Modified";
+      fullName = "European Union Public License (EUPL) 1.2 (modified)";
+      url = "https://github.com/arp242/goatcounter/blob/master/LICENSE";
+      free = true;
+    };
+    platforms = platforms.unix;
+  };
+}