nixos/remark42: add module and test

Summary

Adds a NixOS module for running Remark42 as a systemd service and a
NixOS VM test to ensure it starts and serves the built-in /web/ page.

Changes

- Add services.remark42 module (nixos/modules/services/web-apps/remark42.nix)
- Core options: remarkUrl, sites, listenAddress, port, dataDir
- environmentFile for secrets and settings for additional environment variables
- Register module in nixos/modules/module-list.nix
- Add NixOS test nixos/tests/remark42.nix and register it in nixos/tests/all-tests.nix
- Add release note entry for the new module (NixOS 26.05)

Testing

`nix-build -A nixosTests.remark42`
This commit is contained in:
janhencic
2026-02-11 19:51:07 +00:00
parent 0a028e5a50
commit 8dd7b3c10d
5 changed files with 177 additions and 0 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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 ];
};
}

View File

@@ -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;

30
nixos/tests/remark42.nix Normal file
View File

@@ -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")
'';
}