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