1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.paretosecurity;
9in
10{
11
12 options.services.paretosecurity = {
13 enable = lib.mkEnableOption "[ParetoSecurity](https://paretosecurity.com) [agent](https://github.com/ParetoSecurity/agent) and its root helper";
14 package = lib.mkPackageOption pkgs "paretosecurity" { };
15 trayIcon = lib.mkOption {
16 type = lib.types.bool;
17 default = true;
18 description = "Set to false to disable the tray icon and run as a CLI tool only.";
19 };
20 users = lib.mkOption {
21 type = lib.types.attrsOf (
22 lib.types.submodule {
23 options = {
24 inviteId = lib.mkOption {
25 type = lib.types.str;
26 description = ''
27 A unique ID that links the agent to Pareto Cloud.
28 Get it from the Join Team page on `https://cloud.paretosecurity.com/team/join/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
29 In Step 2, under Linux tab, enter your email then copy it from the generated command.
30 '';
31 example = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
32 };
33 };
34 }
35 );
36 default = { };
37 description = "Per-user Pareto Security configuration.";
38 };
39 };
40
41 config = lib.mkIf cfg.enable {
42 environment.systemPackages = [ cfg.package ];
43 systemd.packages = [ cfg.package ];
44
45 # In traditional Linux distributions, systemd would read the [Install] section from
46 # unit files and automatically create the appropriate symlinks to enable services.
47 # However, in NixOS, due to its immutable nature and the way the Nix store works,
48 # the [Install] sections are not processed during system activation. Instead, we
49 # must explicitly tell NixOS which units to enable by specifying their target
50 # dependencies here. This creates the necessary symlinks in the proper locations.
51 systemd.sockets.paretosecurity.wantedBy = [ "sockets.target" ];
52
53 # In NixOS, systemd services are configured with minimal PATH. However,
54 # paretosecurity helper looks for installed software to do its job, so
55 # it needs the full system PATH. For example, it runs `iptables` to see if
56 # firewall is configured. And it looks for various password managers to see
57 # if one is installed.
58 # The `paretosecurity-user` timer service that is configured lower has
59 # the same need.
60 systemd.services = {
61 paretosecurity.serviceConfig.Environment = [
62 "PATH=${config.system.path}/bin:${config.system.path}/sbin"
63 ];
64 }
65 // (
66
67 # Each user can set their inviteID, which creates a systemd service
68 # that runs `paretosecurity link ...` to link their device to Pareto Cloud.
69 lib.mapAttrs' (
70 username: userConfig:
71 lib.nameValuePair "paretosecurity-link-${username}" {
72 description = "Link Pareto Desktop to Pareto Cloud for user ${username}";
73 after = [ "network-online.target" ];
74 wants = [ "network-online.target" ];
75
76 serviceConfig = {
77 Type = "oneshot";
78 RemainAfterExit = true;
79 User = username;
80 StateDirectory = "paretosecurity/${username}";
81
82 ExecStart = pkgs.writeShellScript "paretosecurity-link-${username}" ''
83 set -euo pipefail
84
85 INVITE_ID="${userConfig.inviteId}"
86 STATE_FILE="/var/lib/paretosecurity/${username}/linked-$INVITE_ID"
87 CONFIG_FILE="$HOME/.config/pareto.toml"
88
89 # Check if already linked with this specific invite
90 if [ -f "$STATE_FILE" ]; then
91 echo "Device already linked with invite $INVITE_ID for user ${username}"
92 exit 0
93 fi
94
95 # Ensure config directory exists
96 mkdir -p "$(dirname "$CONFIG_FILE")"
97
98 # Perform linking
99 echo "Linking device to Pareto Cloud for user ${username}..."
100 ${cfg.package}/bin/paretosecurity link \
101 "paretosecurity://linkDevice/?invite_id=$INVITE_ID"
102
103 # Verify linking succeeded
104 if [ -f "$CONFIG_FILE" ] && grep -q "TeamID" "$CONFIG_FILE"; then
105 echo "Successfully linked to Pareto Cloud for user ${username}"
106 touch "$STATE_FILE"
107 else
108 echo "Failed to link to Pareto Cloud for user ${username}"
109 exit 1
110 fi
111 '';
112 };
113
114 wantedBy = [ "multi-user.target" ];
115 }
116 ) cfg.users
117 );
118
119 # Enable the tray icon and timer services if the trayIcon option is enabled
120 systemd.user = lib.mkIf cfg.trayIcon {
121 services = {
122 paretosecurity-trayicon.wantedBy = [ "graphical-session.target" ];
123 paretosecurity-user = {
124 wantedBy = [ "graphical-session.target" ];
125 serviceConfig.Environment = [
126 "PATH=${config.system.path}/bin:${config.system.path}/sbin"
127 ];
128 };
129 };
130 timers.paretosecurity-user.wantedBy = [ "timers.target" ];
131 };
132 };
133}