diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4e2275cc661d..0dbcf4b3b2e2 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -865,6 +865,7 @@ ./services/networking/bitlbee.nix ./services/networking/blockbook-frontend.nix ./services/networking/blocky.nix + ./services/networking/centrifugo.nix ./services/networking/cgit.nix ./services/networking/charybdis.nix ./services/networking/chisel-server.nix diff --git a/nixos/modules/services/networking/centrifugo.nix b/nixos/modules/services/networking/centrifugo.nix new file mode 100644 index 000000000000..143fe6a24dc3 --- /dev/null +++ b/nixos/modules/services/networking/centrifugo.nix @@ -0,0 +1,123 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.centrifugo; + + settingsFormat = pkgs.formats.json { }; + + configFile = settingsFormat.generate "centrifugo.json" cfg.settings; +in +{ + options.services.centrifugo = { + enable = lib.mkEnableOption (lib.mdDoc "Centrifugo messaging server"); + + package = lib.mkPackageOptionMD pkgs "centrifugo" { }; + + settings = lib.mkOption { + type = settingsFormat.type; + default = { }; + description = lib.mdDoc '' + Declarative Centrifugo configuration. See the [Centrifugo + documentation] for a list of options. + + [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration + ''; + }; + + credentials = lib.mkOption { + type = lib.types.attrsOf lib.types.path; + default = { }; + example = { + CENTRIFUGO_UNI_GRPC_TLS_KEY = "/run/keys/centrifugo-uni-grpc-tls.key"; + }; + description = lib.mdDoc '' + Environment variables with absolute paths to credentials files to load + on service startup. + ''; + }; + + environmentFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = lib.mdDoc '' + Files to load environment variables from. Options set via environment + variables take precedence over {option}`settings`. + + See the [Centrifugo documentation] for the environment variable name + format. + + [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration#os-environment-variables + ''; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "redis-centrifugo" ]; + description = lib.mdDoc '' + Additional groups for the systemd service. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.centrifugo = { + description = "Centrifugo messaging server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Type = "exec"; + + ExecStartPre = "${lib.getExe cfg.package} checkconfig --config ${configFile}"; + ExecStart = "${lib.getExe cfg.package} --config ${configFile}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + + Restart = "always"; + RestartSec = "1s"; + + # Copy files to the credentials directory with file name being the + # environment variable name. Note that "%d" specifier expands to the + # path of the credentials directory. + LoadCredential = lib.mapAttrsToList (name: value: "${name}:${value}") cfg.credentials; + Environment = lib.mapAttrsToList (name: _: "${name}=%d/${name}") cfg.credentials; + + EnvironmentFile = cfg.environmentFiles; + + SupplementaryGroups = cfg.extraGroups; + + DynamicUser = true; + UMask = "0077"; + + ProtectHome = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + ProtectClock = true; + ProtectHostname = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + PrivateUsers = true; + PrivateDevices = true; + RestrictRealtime = true; + RestrictNamespaces = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + DeviceAllow = [ "" ]; + DevicePolicy = "closed"; + CapabilityBoundingSet = [ "" ]; + MemoryDenyWriteExecute = true; + LockPersonality = true; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index c221835f9a6a..e6ccdc25a0f5 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -163,6 +163,7 @@ in { cassandra_3_0 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_0; }; cassandra_3_11 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_3_11; }; cassandra_4 = handleTest ./cassandra.nix { testPackage = pkgs.cassandra_4; }; + centrifugo = runTest ./centrifugo.nix; ceph-multi-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-multi-node.nix {}; ceph-single-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node.nix {}; ceph-single-node-bluestore = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node-bluestore.nix {}; diff --git a/nixos/tests/centrifugo.nix b/nixos/tests/centrifugo.nix new file mode 100644 index 000000000000..45c2904f5585 --- /dev/null +++ b/nixos/tests/centrifugo.nix @@ -0,0 +1,80 @@ +let + redisPort = 6379; + centrifugoPort = 8080; + nodes = [ + "centrifugo1" + "centrifugo2" + "centrifugo3" + ]; +in +{ lib, ... }: { + name = "centrifugo"; + meta.maintainers = [ lib.maintainers.tie ]; + + nodes = lib.listToAttrs (lib.imap0 + (index: name: { + inherit name; + value = { config, ... }: { + services.centrifugo = { + enable = true; + settings = { + inherit name; + port = centrifugoPort; + # See https://centrifugal.dev/docs/server/engines#redis-sharding + engine = "redis"; + # Connect to local Redis shard via Unix socket. + redis_address = + let + otherNodes = lib.take index nodes ++ lib.drop (index + 1) nodes; + in + map (name: "${name}:${toString redisPort}") otherNodes ++ [ + "unix://${config.services.redis.servers.centrifugo.unixSocket}" + ]; + usage_stats_disable = true; + api_insecure = true; + }; + extraGroups = [ + config.services.redis.servers.centrifugo.user + ]; + }; + services.redis.servers.centrifugo = { + enable = true; + bind = null; # all interfaces + port = redisPort; + openFirewall = true; + settings.protected-mode = false; + }; + }; + }) + nodes); + + testScript = '' + import json + + redisPort = ${toString redisPort} + centrifugoPort = ${toString centrifugoPort} + + start_all() + + for machine in machines: + machine.wait_for_unit("redis-centrifugo.service") + machine.wait_for_open_port(redisPort) + + for machine in machines: + machine.wait_for_unit("centrifugo.service") + machine.wait_for_open_port(centrifugoPort) + + # See https://centrifugal.dev/docs/server/server_api#info + def list_nodes(machine): + curl = "curl --fail-with-body --silent" + body = "{}" + resp = json.loads(machine.succeed(f"{curl} -d '{body}' http://localhost:{centrifugoPort}/api/info")) + return resp["result"]["nodes"] + machineNames = {m.name for m in machines} + for machine in machines: + nodes = list_nodes(machine) + assert len(nodes) == len(machines) + nodeNames = {n['name'] for n in nodes} + assert machineNames == nodeNames + ''; +} diff --git a/pkgs/by-name/ce/centrifugo/package.nix b/pkgs/by-name/ce/centrifugo/package.nix new file mode 100644 index 000000000000..6cdfa9bb3510 --- /dev/null +++ b/pkgs/by-name/ce/centrifugo/package.nix @@ -0,0 +1,60 @@ +{ lib +, buildGoModule +, fetchFromGitHub +, nix-update-script +, nixosTests +, testers +, centrifugo +}: +let + # Inspect build flags with `go version -m centrifugo`. + statsEndpoint = "https://graphite-prod-01-eu-west-0.grafana.net/graphite/metrics,https://stats.centrifugal.dev/usage"; + statsToken = "425599:eyJrIjoi" + + "OWJhMTcyZGNjN2FkYjEzM2E1OTQwZjIyMTU3MTBjMjUyYzAyZWE2MSIsIm4iOiJVc2FnZSBTdGF0cyIsImlkIjo2NDUzOTN9"; +in +buildGoModule rec { + pname = "centrifugo"; + version = "5.1.1"; + + src = fetchFromGitHub { + owner = "centrifugal"; + repo = "centrifugo"; + rev = "v${version}"; + hash = "sha256-g496cXjgliDi2XLkdE+dERrUl5hBGLICJx5JundeOfo="; + }; + + vendorHash = "sha256-VuxnP9Dryo0L7sGvtvAIicYGkHoQ2iGVBtAdkmiqL7E="; + + ldflags = [ + "-s" + "-w" + "-X=github.com/centrifugal/centrifugo/v5/internal/build.Version=${version}" + "-X=github.com/centrifugal/centrifugo/v5/internal/build.UsageStatsEndpoint=${statsEndpoint}" + "-X=github.com/centrifugal/centrifugo/v5/internal/build.UsageStatsToken=${statsToken}" + ]; + + excludedPackages = [ + "./internal/gen/api" + ]; + + passthru = { + updateScript = nix-update-script { }; + tests = { + inherit (nixosTests) centrifugo; + version = testers.testVersion { + package = centrifugo; + command = "${pname} version"; + version = "v${version}"; + }; + }; + }; + + meta = { + description = "Scalable real-time messaging server"; + homepage = "https://centrifugal.dev"; + changelog = "https://github.com/centrifugal/centrifugo/releases/tag/v${version}"; + license = lib.licenses.asl20; + maintainers = [ lib.maintainers.tie ]; + mainProgram = "centrifugo"; + }; +}