diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 040de458b015..4dc80d4793c9 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -28,6 +28,8 @@ - [qui](https://github.com/autobrr/qui), a modern alternative webUI for qBittorrent, with multi-instance support. Written in Go/React. Available as [services.qui](#opt-services.qui.enable). +- [Remark42](https://remark42.com/), a self-hosted comment engine. Available as [services.remark42](#opt-services.remark42.enable). + - [LibreChat](https://www.librechat.ai/), open-source self-hostable ChatGPT clone with Agents and RAG APIs. Available as [services.librechat](#opt-services.librechat.enable). - [nohang](https://github.com/hakavlad/nohang), a daemon for Linux that prevents out of memory (OOM) situations from affecting system responsiveness. Available as [services.nohang](#opt-services.nohang.enable) diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index cb003fc2709e..04b93f04baab 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1733,6 +1733,7 @@ ./services/web-apps/privatebin.nix ./services/web-apps/prosody-filer.nix ./services/web-apps/readeck.nix + ./services/web-apps/remark42.nix ./services/web-apps/reposilite.nix ./services/web-apps/rimgo.nix ./services/web-apps/rss-bridge.nix diff --git a/nixos/modules/services/web-apps/remark42.nix b/nixos/modules/services/web-apps/remark42.nix new file mode 100644 index 000000000000..33909cfef80b --- /dev/null +++ b/nixos/modules/services/web-apps/remark42.nix @@ -0,0 +1,143 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.remark42; + siteList = lib.concatStringsSep "," cfg.sites; +in +{ + options.services.remark42 = { + enable = lib.mkEnableOption "Remark42 commenting server"; + + package = lib.mkPackageOption pkgs "remark42" { }; + + remarkUrl = lib.mkOption { + type = lib.types.str; + example = "https://comments.example.com"; + description = '' + Public URL of this Remark42 instance. This is passed to the backend as + `REMARK_URL` and should match the frontend embed config `host`. + ''; + }; + + sites = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "remark" ]; + example = [ + "blog" + "docs" + ]; + description = '' + Site IDs served by this instance (passed as `SITE`, comma-separated). + The frontend embed config `site_id` must match one of these values. + ''; + }; + + listenAddress = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + example = "0.0.0.0"; + description = "Bind address (`REMARK_ADDRESS`)."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Listen port (`REMARK_PORT`)."; + }; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/remark42"; + description = '' + Working directory for Remark42. Data files are stored here and + automatic backups will be created in this directory by default. + ''; + }; + + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/remark42.env"; + description = '' + Optional environment file in systemd `EnvironmentFile=` format. + Use this for secrets to avoid storing them in the Nix store. + ''; + }; + + settings = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { + AUTH_ANON = "true"; + }; + description = "Extra environment variables passed to Remark42."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to open the firewall for `port`."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.sites != [ ]; + message = "services.remark42.sites must contain at least one site ID."; + } + { + assertion = cfg.environmentFile != null || (cfg.settings ? SECRET); + message = '' + Remark42 requires SECRET. + Provide it via services.remark42.environmentFile (recommended), + or via services.remark42.settings.SECRET (not recommended). + ''; + } + ]; + + users.groups.remark42 = { }; + users.users.remark42 = { + isSystemUser = true; + group = "remark42"; + home = cfg.dataDir; + createHome = true; + description = "Remark42 service user"; + }; + + systemd.services.remark42 = { + description = "Remark42 commenting server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = cfg.settings // { + REMARK_URL = cfg.remarkUrl; + SITE = siteList; + REMARK_ADDRESS = cfg.listenAddress; + REMARK_PORT = toString cfg.port; + }; + + serviceConfig = { + Type = "simple"; + User = "remark42"; + Group = "remark42"; + + WorkingDirectory = cfg.dataDir; + ExecStart = "${cfg.package}/bin/remark42 server"; + + Restart = "on-failure"; + RestartSec = "2s"; + } + // lib.optionalAttrs (cfg.environmentFile != null) { + EnvironmentFile = cfg.environmentFile; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 38a30c38368f..e8b6cb16b189 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1387,6 +1387,7 @@ in redlib = runTest ./redlib.nix; redmine = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./redmine.nix { }; refind = runTest ./refind.nix; + remark42 = runTest ./remark42.nix; renovate = runTest ./renovate.nix; replace-dependencies = handleTest ./replace-dependencies { }; reposilite = runTest ./reposilite.nix; diff --git a/nixos/tests/remark42.nix b/nixos/tests/remark42.nix new file mode 100644 index 000000000000..d9f67ae8bcff --- /dev/null +++ b/nixos/tests/remark42.nix @@ -0,0 +1,30 @@ +{ pkgs, ... }: + +{ + name = "remark42"; + + nodes.machine = + { ... }: + { + environment.systemPackages = [ pkgs.curl ]; + + services.remark42 = { + enable = true; + remarkUrl = "http://127.0.0.1:8080"; + sites = [ "remark" ]; + + environmentFile = pkgs.writeText "remark42.env" '' + SECRET=unit-test-secret + ''; + + settings.AUTH_ANON = "true"; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("remark42.service") + machine.wait_for_open_port(8080) + machine.succeed("curl -fsS http://127.0.0.1:8080/web/ | head") + ''; +}