Home

tsnet-serve-nix @008b9e3f31d582a0656bac02ba04d57ca0a3d1ba - refs - log -
-
https://git.jolheiser.com/tsnet-serve-nix.git
tsnet-serve flake, package, and module
tsnet-serve-nix / module / default.nix
- raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{
  config,
  lib,
  pkgs,
  ...
}: let
  cfg = config.services.tsnet-serve;
  pkg = pkgs.callPackage ../pkg {inherit pkgs;};

  instanceOptions = {
    name,
    config,
    ...
  }: {
    options = {
      enable = lib.mkEnableOption "tsnet-serve-${name}";

      user = lib.mkOption {
        type = lib.types.str;
        default = "tsnet-serve";
        description = "User to run tsnet-serve";
      };

      backend = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Target URL to proxy";
        example = "https://localhost:3000";
      };

      hostname = lib.mkOption {
        type = lib.types.str;
        default = name;
        description = "App name";
        example = "myapp";
      };

      funnel = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Enable funnel mode";
      };

      port = lib.mkOption {
        type = lib.types.port;
        default = 443;
        description = "Port to listen on";
      };

      mountPath = lib.mkOption {
        type = lib.types.str;
        default = "/";
        description = "Path to mount proxy on";
      };

      stateDir = lib.mkOption {
        type = lib.types.str;
        default = "/var/lib/tsnet-serve-${name}";
        description = "Directory to store state in";
      };

      authKey = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Tailscale auth key";
      };

      package = lib.mkOption {
        type = lib.types.package;
        default = pkg;
        description = "The tsnet-serve package to use";
      };
    };
  };
in {
  options = {
    services.tsnet-serve.instances = lib.mkOption {
      type = lib.types.attrsOf (lib.types.submodule instanceOptions);
      default = {};
      description = "Attribute set of tsnet-serve instances";
    };
  };

  config = lib.mkIf (cfg.instances != {}) {
    systemd.services = lib.mapAttrs' (
      name: instanceCfg:
        lib.nameValuePair "tsnet-serve-${name}" {
          description = "tsnet-serve-${name}";
          wantedBy = ["multi-user.target"];
          after = ["network.target"];
          serviceConfig = {
            ExecStart = let
              args =
                lib.optionals (instanceCfg.backend != null) [
                  "--backend=${instanceCfg.backend}"
                ]
                ++ [
                  (lib.optionalString instanceCfg.funnel "--funnel")
                  "--hostname=${instanceCfg.hostname}"
                  "--listen-port=${builtins.toString instanceCfg.port}"
                  "--mount-path=${instanceCfg.mountPath}"
                  "--state-dir=${instanceCfg.stateDir}"
                ];
            in "${cfg.package}/bin/tsnet-serve ${lib.concatStringsSep " " args}";
            User = instanceCfg.user;
            Restart = "on-failure";
            Environment = ["TS_AUTHKEY=${instanceCfg.authKey}"];
          };
        }
    ) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);

    users.users = lib.mapAttrs' (
      name: instanceCfg:
        lib.nameValuePair instanceCfg.user {
          isSystemUser = true;
          group = instanceCfg.user;
          home = instanceCfg.stateDir;
          createHome = true;
        }
    ) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);

    users.groups = lib.mapAttrs' (
      name: instanceCfg:
        lib.nameValuePair instanceCfg.user {}
    ) (lib.filterAttrs (name: instanceCfg: instanceCfg.enable) cfg.instances);
  };
}