From 1f22eb1d157275d40400c83b57fac4ad65ecb1a2 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sat, 29 Jun 2024 12:06:39 +0200 Subject: nixos/goatcounter: init module --- default.nix | 1 + modules/nixos/default.nix | 1 + modules/nixos/goatcounter.nix | 147 ++++++++++++++++++++++++++++++++++++++++++ pkgs/goatcounter/default.nix | 39 +++++++++++ 4 files changed, 188 insertions(+) create mode 100644 modules/nixos/goatcounter.nix create mode 100644 pkgs/goatcounter/default.nix 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; + }; +} -- cgit 1.4.1