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.xray;
 10
 11  configFormat = pkgs.formats.json {};
 12
 13  runtimeConfig = "/run/xray/config.json";
 14in {
 15  disabledModules = ["services/networking/xray.nix"];
 16
 17  options.services.xray = {
 18    enable = lib.mkOption {
 19      type = lib.types.bool;
 20      default = false;
 21      description = ''
 22        Whether to run xray server.
 23
 24        Either `settingsFile` or `settings` must be specified.
 25      '';
 26    };
 27
 28    package = lib.mkPackageOption pkgs "xray" {};
 29
 30    settingsFile = lib.mkOption {
 31      type = lib.types.nullOr lib.types.path;
 32      default = null;
 33      example = "/etc/xray/config.json";
 34      description = ''
 35        The absolute path to the configuration file.
 36
 37        Either `settingsFile` or `settings` must be specified.
 38
 39        See <https://www.v2fly.org/en_US/config/overview.html>.
 40      '';
 41    };
 42
 43    settings = lib.mkOption {
 44      type = lib.types.nullOr (lib.types.submodule {
 45        freeformType = configFormat.type;
 46      });
 47      default = null;
 48      example = {
 49        inbounds = [
 50          {
 51            port = 1080;
 52            listen = "127.0.0.1";
 53            protocol = "http";
 54          }
 55        ];
 56        outbounds = [
 57          {
 58            protocol = "freedom";
 59          }
 60        ];
 61      };
 62      description = ''
 63        The configuration object.
 64
 65        Either `settingsFile` or `settings` must be specified.
 66
 67        See <https://www.v2fly.org/en_US/config/overview.html>.
 68
 69        Options containing secret data should be set to an attribute set
 70        containing the attribute `_secret` - a string pointing to a file
 71        containing the value the option should be set to.
 72
 73        Set `quote = true` (default behavior) to quote the content of the
 74        secret file as a string, or set `quote = false` to parse the content
 75        of the secret file to JSON.
 76      '';
 77    };
 78  };
 79
 80  config = lib.mkIf cfg.enable {
 81    assertions = [
 82      {
 83        assertion = (cfg.settingsFile == null) != (cfg.settings == null);
 84        message = "Either but not both `settingsFile` and `settings` should be specified for xray.";
 85      }
 86    ];
 87
 88    systemd.services.xray = {
 89      description = "xray Daemon";
 90      after = ["network.target"];
 91      wantedBy = ["multi-user.target"];
 92      serviceConfig = {
 93        ExecStart = "${lib.getExe cfg.package} run -config ${runtimeConfig}";
 94
 95        ExecStartPre = "+${pkgs.writeShellScript "xray-pre-start" ''
 96          ${lib.optionalString (cfg.settings != null) (utils.genJqSecretsReplacementSnippet cfg.settings runtimeConfig)}
 97          ${lib.optionalString (cfg.settings != null) "chown --reference=/run/xray ${runtimeConfig}"}
 98          ${lib.getExe cfg.package} -test -config ${
 99            if cfg.settings != null
100            then runtimeConfig
101            else cfg.settingsFile
102          }
103        ''}";
104
105        DynamicUser = true;
106        RuntimeDirectory = "xray";
107        RuntimeDirectoryMode = "0700";
108        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
109        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
110        NoNewPrivileges = true;
111      };
112    };
113  };
114}