main
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.daneel;
9in
10{
11 options.services.daneel = {
12 enable = lib.mkEnableOption "Daneel XMPP research bot";
13
14 package = lib.mkOption {
15 type = lib.types.package;
16 default = pkgs.callPackage ./package.nix { };
17 description = "The daneel package to use";
18 };
19
20 xmppJid = lib.mkOption {
21 type = lib.types.str;
22 example = "daneel@xmpp.example.com";
23 description = "XMPP JID for the bot";
24 };
25
26 xmppPasswordFile = lib.mkOption {
27 type = lib.types.path;
28 description = "Path to file containing XMPP password";
29 };
30
31 ownerJid = lib.mkOption {
32 type = lib.types.str;
33 example = "user@xmpp.example.com";
34 description = "XMPP JID of the bot owner (only this JID can interact)";
35 };
36
37 dataDir = lib.mkOption {
38 type = lib.types.path;
39 default = "/var/lib/daneel";
40 description = "Directory for session data";
41 };
42
43 inboxPath = lib.mkOption {
44 type = lib.types.path;
45 default = "/var/lib/daneel/inbox.org";
46 description = "Path to org-mode inbox file";
47 };
48
49 defaultModel = lib.mkOption {
50 type = lib.types.str;
51 default = "sonnet";
52 description = "Default model alias (e.g., sonnet, opus, gemini)";
53 };
54
55 anthropicApiKeyFile = lib.mkOption {
56 type = lib.types.nullOr lib.types.path;
57 default = null;
58 description = "Path to file containing Anthropic API key";
59 };
60
61 googleApiKeyFile = lib.mkOption {
62 type = lib.types.nullOr lib.types.path;
63 default = null;
64 description = "Path to file containing Google API key";
65 };
66
67 openaiApiKeyFile = lib.mkOption {
68 type = lib.types.nullOr lib.types.path;
69 default = null;
70 description = "Path to file containing OpenAI API key";
71 };
72
73 ollamaBaseUrl = lib.mkOption {
74 type = lib.types.nullOr lib.types.str;
75 default = null;
76 example = "http://localhost:11434";
77 description = "Base URL for Ollama API";
78 };
79
80 debug = lib.mkOption {
81 type = lib.types.bool;
82 default = false;
83 description = "Enable debug logging";
84 };
85
86 user = lib.mkOption {
87 type = lib.types.str;
88 default = "daneel";
89 description = "User to run daneel as";
90 };
91
92 group = lib.mkOption {
93 type = lib.types.str;
94 default = "daneel";
95 description = "Group to run daneel as";
96 };
97 };
98
99 config = lib.mkIf cfg.enable {
100 users.users.${cfg.user} = {
101 isSystemUser = true;
102 group = cfg.group;
103 home = cfg.dataDir;
104 createHome = true;
105 };
106
107 users.groups.${cfg.group} = { };
108
109 systemd.services.daneel = {
110 description = "Daneel XMPP Research Bot";
111 wantedBy = [ "multi-user.target" ];
112 after = [ "network.target" ];
113
114 serviceConfig = {
115 Type = "simple";
116 User = cfg.user;
117 Group = cfg.group;
118 WorkingDirectory = cfg.dataDir;
119 ExecStart = "${cfg.package}/bin/daneel";
120 Restart = "always";
121 RestartSec = "10s";
122
123 # Hardening
124 NoNewPrivileges = true;
125 PrivateTmp = true;
126 ProtectSystem = "strict";
127 ProtectHome = true;
128 ReadWritePaths = [ cfg.dataDir (builtins.dirOf cfg.inboxPath) ];
129 ProtectKernelTunables = true;
130 ProtectKernelModules = true;
131 ProtectControlGroups = true;
132 RestrictAddressFamilies = [
133 "AF_INET"
134 "AF_INET6"
135 "AF_UNIX"
136 ];
137 RestrictNamespaces = true;
138 LockPersonality = true;
139 MemoryDenyWriteExecute = true;
140 RestrictRealtime = true;
141 RestrictSUIDSGID = true;
142 PrivateDevices = true;
143 };
144
145 script = ''
146 export DANEEL_XMPP_JID="${cfg.xmppJid}"
147 export DANEEL_XMPP_PASSWORD="$(cat ${cfg.xmppPasswordFile})"
148 export DANEEL_OWNER_JID="${cfg.ownerJid}"
149 export DANEEL_DATA_DIR="${cfg.dataDir}"
150 export DANEEL_INBOX_PATH="${cfg.inboxPath}"
151 export DANEEL_DEFAULT_MODEL="${cfg.defaultModel}"
152 ${lib.optionalString cfg.debug ''export DANEEL_DEBUG="true"''}
153
154 ${lib.optionalString (cfg.anthropicApiKeyFile != null) ''
155 export ANTHROPIC_API_KEY="$(cat ${cfg.anthropicApiKeyFile})"
156 ''}
157 ${lib.optionalString (cfg.googleApiKeyFile != null) ''
158 export GOOGLE_API_KEY="$(cat ${cfg.googleApiKeyFile})"
159 ''}
160 ${lib.optionalString (cfg.openaiApiKeyFile != null) ''
161 export OPENAI_API_KEY="$(cat ${cfg.openaiApiKeyFile})"
162 ''}
163 ${lib.optionalString (cfg.ollamaBaseUrl != null) ''
164 export OLLAMA_BASE_URL="${cfg.ollamaBaseUrl}"
165 ''}
166
167 exec ${cfg.package}/bin/daneel
168 '';
169 };
170 };
171}