atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0

Commit 727d064fae38

tarball

atagen <boss@atagen.co>2026-10-12 13:18 UTC

2 files changed+144-0
nix: add a nixos module for jelqd

jelqd falls back to the XDG state directory when JELQD_DB is unset, which is meaningless for a system user, so the module points it at a systemd StateDirectory under /var/lib/jelqd.
Modified file: flake.nix+2 additions
@@ -88,6 +88,8 @@
8888 };
8989 });
9090
91+ nixosModules.default = import ./nix/module.nix self;
92+
9193 formatter = forAllSystems (pkgs: pkgs.nixfmt);
9294 };
9395 }
Added file: nix/module.nix+142 additions
@@ -0,0 +1,142 @@
1+self:
2+{
3+ config,
4+ lib,
5+ pkgs,
6+ ...
7+}:
8+let
9+ cfg = config.services.jelqd;
10+ host = if lib.hasInfix ":" cfg.address then "[${cfg.address}]" else cfg.address;
11+in
12+{
13+ options.services.jelqd = {
14+ enable = lib.mkEnableOption "jelqd, a roguelike played entirely over HTTP";
15+
16+ package = lib.mkOption {
17+ type = lib.types.package;
18+ default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
19+ defaultText = lib.literalExpression "jelq.packages.\${pkgs.stdenv.hostPlatform.system}.default";
20+ description = "The jelqd package to run.";
21+ };
22+
23+ address = lib.mkOption {
24+ type = lib.types.str;
25+ default = "127.0.0.1";
26+ description = "Address to listen on.";
27+ };
28+
29+ port = lib.mkOption {
30+ type = lib.types.port;
31+ default = 6969;
32+ description = "Port to listen on.";
33+ };
34+
35+ openFirewall = lib.mkOption {
36+ type = lib.types.bool;
37+ default = false;
38+ description = "Open the listening port in the firewall.";
39+ };
40+
41+ dataDir = lib.mkOption {
42+ type = lib.types.path;
43+ default = "/var/lib/jelqd";
44+ description = "Directory holding the sqlite database of finished runs.";
45+ };
46+
47+ oracle = lib.mkOption {
48+ type = lib.types.enum [
49+ "rng"
50+ "clm"
51+ ];
52+ default = "rng";
53+ description = "Where the oracle gets its answers.";
54+ };
55+
56+ clmUrl = lib.mkOption {
57+ type = lib.types.str;
58+ default = "http://127.0.0.1:8700/v1/systemone";
59+ description = "CLM endpoint used when `oracle` is `clm`.";
60+ };
61+
62+ environmentFile = lib.mkOption {
63+ type = lib.types.nullOr lib.types.path;
64+ default = null;
65+ example = "/run/secrets/jelqd.env";
66+ description = ''
67+ Environment file passed to the service, for setting `JELQD_SECRET`.
68+ Without it the run-signing secret is regenerated on every restart
69+ and live runs stop being accepted.
70+ '';
71+ };
72+ };
73+
74+ config = lib.mkIf cfg.enable {
75+ users.users.jelqd = {
76+ isSystemUser = true;
77+ group = "jelqd";
78+ home = cfg.dataDir;
79+ };
80+ users.groups.jelqd = { };
81+
82+ systemd.tmpfiles.settings.jelqd = lib.mkIf (cfg.dataDir != "/var/lib/jelqd") {
83+ ${cfg.dataDir}.d = {
84+ user = "jelqd";
85+ group = "jelqd";
86+ mode = "0750";
87+ };
88+ };
89+
90+ systemd.services.jelqd = {
91+ description = "jelqd roguelike server";
92+ wantedBy = [ "multi-user.target" ];
93+ after = [ "network.target" ];
94+ environment = {
95+ JELQD_ADDR = "${host}:${toString cfg.port}";
96+ JELQD_DB = "${cfg.dataDir}/jelqd.sqlite";
97+ JELQD_ORACLE = cfg.oracle;
98+ }
99+ // lib.optionalAttrs (cfg.oracle == "clm") { JELQD_CLM_URL = cfg.clmUrl; };
100+ serviceConfig = {
101+ ExecStart = lib.getExe cfg.package;
102+ User = "jelqd";
103+ Group = "jelqd";
104+ Restart = "on-failure";
105+ EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
106+ StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/jelqd") "jelqd";
107+ StateDirectoryMode = "0750";
108+ ReadWritePaths = [ cfg.dataDir ];
109+ CapabilityBoundingSet = "";
110+ LockPersonality = true;
111+ MemoryDenyWriteExecute = true;
112+ NoNewPrivileges = true;
113+ PrivateDevices = true;
114+ PrivateTmp = true;
115+ ProtectClock = true;
116+ ProtectControlGroups = true;
117+ ProtectHome = true;
118+ ProtectHostname = true;
119+ ProtectKernelLogs = true;
120+ ProtectKernelModules = true;
121+ ProtectKernelTunables = true;
122+ ProtectProc = "invisible";
123+ ProtectSystem = "strict";
124+ RestrictAddressFamilies = [
125+ "AF_INET"
126+ "AF_INET6"
127+ ];
128+ RestrictNamespaces = true;
129+ RestrictRealtime = true;
130+ RestrictSUIDSGID = true;
131+ SystemCallArchitectures = "native";
132+ SystemCallFilter = [
133+ "@system-service"
134+ "~@privileged"
135+ ];
136+ UMask = "0077";
137+ };
138+ };
139+
140+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
141+ };
142+}