main
  1{
  2  lib,
  3  pkgs,
  4  config,
  5  ...
  6}: let
  7  utils = pkgs.callPackage "${pkgs.path}/nixos/lib/utils.nix" {};
  8
  9  cfg = config.services.mihomo;
 10
 11  # mihomo use YAML as config format, but set to JSON for secret inject
 12  configFormat = pkgs.formats.json {};
 13
 14  AmbientCapabilities =
 15    lib.optional cfg.tunMode "CAP_NET_ADMIN"
 16    ++ lib.optionals cfg.processesInfo [
 17      "CAP_DAC_READ_SEARCH"
 18      "CAP_SYS_PTRACE"
 19    ];
 20  CapabilityBoundingSet = AmbientCapabilities;
 21in {
 22  disabledModules = ["services/networking/mihomo.nix"];
 23
 24  options.services.mihomo = {
 25    enable = lib.mkEnableOption "Mihomo, A rule-based proxy in Go";
 26
 27    package = lib.mkPackageOption pkgs "mihomo" {};
 28
 29    config = lib.mkOption {
 30      type = lib.types.submodule {
 31        freeformType = configFormat.type;
 32        options = {
 33          tun.enable = lib.mkOption {
 34            default = cfg.tunMode;
 35            type = lib.types.bool;
 36            example = true;
 37            description = "Enable mihomo's tun mode";
 38          };
 39        };
 40      };
 41      default = {};
 42      description = ''
 43        The mihomo configuration, see <https://wiki.metacubex.one/en/config/> for documentation.
 44
 45        Options containing secret data should be set to an attribute set
 46        containing the attribute `_secret` - a string pointing to a file
 47        containing the value the option should be set to.
 48
 49        Set `quote = true` (default behavior) to quote the content of the
 50        secret file as a string, or set `quote = false` to parse the content
 51        of the secret file to JSON.
 52      '';
 53    };
 54
 55    webui = lib.mkOption {
 56      default = null;
 57      type = lib.types.nullOr lib.types.path;
 58      example = lib.literalExpression "pkgs.metacubexd";
 59      description = ''
 60        Local web interface to use.
 61
 62        You can also use the following website:
 63        - metacubexd:
 64          - <https://d.metacubex.one>
 65          - <https://metacubex.github.io/metacubexd>
 66          - <https://metacubexd.pages.dev>
 67        - yacd:
 68          - <https://yacd.haishan.me>
 69        - clash-dashboard:
 70          - <https://clash.razord.top>
 71      '';
 72    };
 73
 74    extraOpts = lib.mkOption {
 75      default = null;
 76      type = lib.types.nullOr lib.types.str;
 77      description = "Extra command line options to use.";
 78    };
 79
 80    tunMode = lib.mkEnableOption ''
 81      necessary capabilities for Mihomo's systemd service for TUN mode to function properly.
 82    '';
 83
 84    processesInfo = lib.mkEnableOption ''
 85      necessary capabilities for rules about process information such as `process-name`
 86    '';
 87  };
 88
 89  config = lib.mkIf cfg.enable {
 90    systemd.services."mihomo" = {
 91      description = "Mihomo daemon, A rule-based proxy in Go.";
 92      documentation = ["https://wiki.metacubex.one/"];
 93      requires = ["network-online.target"];
 94      after = ["network-online.target"];
 95      wantedBy = ["multi-user.target"];
 96      serviceConfig =
 97        {
 98          User = "mihomo";
 99          Group = "mihomo";
100          StateDirectory = "mihomo";
101          StateDirectoryMode = "0700";
102          RuntimeDirectory = "mihomo";
103          RuntimeDirectoryMode = "0700";
104          WorkingDirectory = "/var/lib/mihomo";
105
106          ExecStart = lib.concatStringsSep " " [
107            (lib.getExe cfg.package)
108            "-d /var/lib/mihomo"
109            "-f /run/mihomo/config.yaml"
110            (lib.optionalString (cfg.webui != null) "-ext-ui ${cfg.webui}")
111            (lib.optionalString (cfg.extraOpts != null) cfg.extraOpts)
112          ];
113
114          ExecStartPre = "+${pkgs.writeShellScript "mihomo-pre-start" ''
115            ${utils.genJqSecretsReplacementSnippet cfg.config "/run/mihomo/config.json"}
116            ${lib.getExe pkgs.yq-go} --input-format 'json' --output-format 'yaml' \
117                /run/mihomo/config.json > /run/mihomo/config.yaml
118            rm /run/mihomo/config.json
119            chown --reference=/run/mihomo /run/mihomo/config.yaml
120          ''}";
121
122          inherit AmbientCapabilities CapabilityBoundingSet;
123          DeviceAllow = "";
124          LockPersonality = true;
125          MemoryDenyWriteExecute = true;
126          NoNewPrivileges = true;
127          PrivateDevices = true;
128          PrivateMounts = true;
129          PrivateTmp = true;
130          PrivateUsers = true;
131          ProcSubset = "pid";
132          ProtectClock = true;
133          ProtectControlGroups = true;
134          ProtectHome = true;
135          ProtectHostname = true;
136          ProtectKernelLogs = true;
137          ProtectKernelModules = true;
138          ProtectKernelTunables = true;
139          ProtectProc = "invisible";
140          ProtectSystem = "strict";
141          RestrictRealtime = true;
142          RestrictSUIDSGID = true;
143          RestrictNamespaces = true;
144          RestrictAddressFamilies = "AF_INET AF_INET6";
145          SystemCallArchitectures = "native";
146          SystemCallFilter = "@system-service bpf";
147          UMask = "0077";
148        }
149        // lib.optionalAttrs cfg.tunMode {
150          PrivateDevices = false;
151          PrivateUsers = false;
152          RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK";
153        };
154    };
155
156    users.users.mihomo = {
157      isSystemUser = true;
158      group = "mihomo";
159      home = "/var/lib/mihomo";
160    };
161    users.groups.mihomo = {};
162  };
163}