|
| 1 | +{ config, lib, pkgs, ... }: |
| 2 | +let |
| 3 | + inherit (lib) types; |
| 4 | + |
| 5 | + cfg = config.services.tabby; |
| 6 | + format = pkgs.formats.toml { }; |
| 7 | + tabbyPackage = cfg.package.override { |
| 8 | + inherit (cfg) acceleration; |
| 9 | + }; |
| 10 | +in |
| 11 | +{ |
| 12 | + options = { |
| 13 | + services.tabby = { |
| 14 | + enable = lib.mkEnableOption ( |
| 15 | + lib.mdDoc "Self-hosted AI coding assistant using large language models" |
| 16 | + ); |
| 17 | + |
| 18 | + package = lib.mkPackageOption pkgs "tabby" { }; |
| 19 | + |
| 20 | + port = lib.mkOption { |
| 21 | + type = types.port; |
| 22 | + default = 11029; |
| 23 | + description = lib.mdDoc '' |
| 24 | + Specifies the bind port on which the tabby server HTTP interface listens. |
| 25 | + ''; |
| 26 | + }; |
| 27 | + |
| 28 | + model = lib.mkOption { |
| 29 | + type = types.str; |
| 30 | + default = "TabbyML/StarCoder-1B"; |
| 31 | + description = lib.mdDoc '' |
| 32 | + Specify the model that tabby will use to generate completions. |
| 33 | +
|
| 34 | + This model will be downloaded automatically if it is not already present. |
| 35 | +
|
| 36 | + If you want to utilize an existing model that you've already |
| 37 | + downloaded you'll need to move it into tabby's state directory which |
| 38 | + lives in `/var/lib/tabby`. Because the tabby.service is configured to |
| 39 | + use a DyanmicUser the service will need to have been started at least |
| 40 | + once before you can move the locally existing model into |
| 41 | + `/var/lib/tabby`. You can set the model to 'none' and tabby will |
| 42 | + startup and fail to download a model, but will have created the |
| 43 | + `/var/lib/tabby` directory. You can then copy over the model manually |
| 44 | + into `/var/lib/tabby`, update the model option to the name you just |
| 45 | + downloaded and copied over then `nixos-rebuild switch` to start using |
| 46 | + it. |
| 47 | +
|
| 48 | + $ tabby download --model TabbyML/DeepseekCoder-6.7B |
| 49 | + $ find ~/.tabby/ | tail -n1 |
| 50 | + /home/ghthor/.tabby/models/TabbyML/DeepseekCoder-6.7B/ggml/q8_0.v2.gguf |
| 51 | + $ sudo rsync -r ~/.tabby/models/ /var/lib/tabby/models/ |
| 52 | + $ sudo chown -R tabby:tabby /var/lib/tabby/models/ |
| 53 | +
|
| 54 | + See for Model Options: |
| 55 | + > https://github.com/TabbyML/registry-tabby |
| 56 | + ''; |
| 57 | + }; |
| 58 | + |
| 59 | + acceleration = lib.mkOption { |
| 60 | + type = types.nullOr (types.enum [ "cpu" "rocm" "cuda" "metal" ]); |
| 61 | + default = null; |
| 62 | + example = "rocm"; |
| 63 | + description = lib.mdDoc '' |
| 64 | + Specifies the device to use for hardware acceleration. |
| 65 | +
|
| 66 | + - `cpu`: no acceleration just use the CPU |
| 67 | + - `rocm`: supported by modern AMD GPUs |
| 68 | + - `cuda`: supported by modern NVIDIA GPUs |
| 69 | + - `metal`: supported on darwin aarch64 machines |
| 70 | +
|
| 71 | + Tabby will try and determine what type of acceleration that is |
| 72 | + already enabled in your configuration when `acceleration = null`. |
| 73 | +
|
| 74 | + - nixpkgs.config.cudaSupport |
| 75 | + - nixpkgs.config.rocmSupport |
| 76 | + - if stdenv.isDarwin && stdenv.isAarch64 |
| 77 | +
|
| 78 | + IFF multiple acceleration methods are found to be enabled or if you |
| 79 | + haven't set either `cudaSupport or rocmSupport` you will have to |
| 80 | + specify the device type manually here otherwise it will default to |
| 81 | + the first from the list above or to cpu. |
| 82 | + ''; |
| 83 | + }; |
| 84 | + |
| 85 | + settings = lib.mkOption { |
| 86 | + inherit (format) type; |
| 87 | + default = { }; |
| 88 | + description = lib.mdDoc '' |
| 89 | + Tabby scheduler configuration |
| 90 | +
|
| 91 | + See for more details: |
| 92 | + > https://tabby.tabbyml.com/docs/configuration/#repository-context-for-code-completion |
| 93 | + ''; |
| 94 | + example = lib.literalExpression '' |
| 95 | + settings = { |
| 96 | + repositories = [ |
| 97 | + { name = "tabby"; git_url = "https://github.com/TabbyML/tabby.git"; } |
| 98 | + { name = "CTranslate2"; git_url = "git@github.com:OpenNMT/CTranslate2.git"; } |
| 99 | +
|
| 100 | + # local directory is also supported, but limited by systemd DynamicUser=1 |
| 101 | + # adding local repositories will need to be done manually |
| 102 | + { name = "repository_a"; git_url = "file:///var/lib/tabby/repository_a"; } |
| 103 | + ]; |
| 104 | + }; |
| 105 | + ''; |
| 106 | + }; |
| 107 | + |
| 108 | + usageCollection = lib.mkOption { |
| 109 | + type = types.bool; |
| 110 | + default = false; |
| 111 | + description = lib.mdDoc '' |
| 112 | + Enable sending anonymous usage data. |
| 113 | +
|
| 114 | + See for more details: |
| 115 | + > https://tabby.tabbyml.com/docs/configuration#usage-collection |
| 116 | + ''; |
| 117 | + }; |
| 118 | + |
| 119 | + indexInterval = lib.mkOption { |
| 120 | + type = types.str; |
| 121 | + default = "5hours"; |
| 122 | + example = "5hours"; |
| 123 | + description = lib.mdDoc '' |
| 124 | + Run tabby scheduler to generate the index database at this interval. |
| 125 | + Updates by default every 5 hours. This value applies to |
| 126 | + `OnUnitInactiveSec` |
| 127 | +
|
| 128 | + The format is described in |
| 129 | + {manpage}`systemd.time(7)`. |
| 130 | +
|
| 131 | + To disable running `tabby scheduler --now` updates, set to `"never"` |
| 132 | + ''; |
| 133 | + }; |
| 134 | + }; |
| 135 | + }; |
| 136 | + |
| 137 | + # TODO(ghthor): firewall config |
| 138 | + |
| 139 | + config = lib.mkIf cfg.enable { |
| 140 | + environment = { |
| 141 | + etc."tabby/config.toml".source = format.generate "config.toml" cfg.settings; |
| 142 | + systemPackages = [ tabbyPackage ]; |
| 143 | + }; |
| 144 | + |
| 145 | + |
| 146 | + systemd = let |
| 147 | + serviceUser = { |
| 148 | + WorkingDirectory = "/var/lib/tabby"; |
| 149 | + StateDirectory = [ "tabby" ]; |
| 150 | + ConfigurationDirectory = [ "tabby" ]; |
| 151 | + DynamicUser = true; |
| 152 | + User = "tabby"; |
| 153 | + Group = "tabby"; |
| 154 | + }; |
| 155 | + |
| 156 | + serviceEnv = lib.mkMerge [ |
| 157 | + { |
| 158 | + TABBY_ROOT = "%S/tabby"; |
| 159 | + } |
| 160 | + (lib.mkIf (!cfg.usageCollection) { |
| 161 | + TABBY_DISABLE_USAGE_COLLECTION = "1"; |
| 162 | + }) |
| 163 | + ]; |
| 164 | + in { |
| 165 | + services.tabby = { |
| 166 | + wantedBy = [ "multi-user.target" ]; |
| 167 | + description = "Self-hosted AI coding assistant using large language models"; |
| 168 | + after = [ "network.target" ]; |
| 169 | + environment = serviceEnv; |
| 170 | + serviceConfig = lib.mkMerge [ |
| 171 | + serviceUser |
| 172 | + { |
| 173 | + ExecStart = |
| 174 | + "${lib.getExe tabbyPackage} serve --model ${cfg.model} --port ${toString cfg.port} --device ${tabbyPackage.featureDevice}"; |
| 175 | + } |
| 176 | + ]; |
| 177 | + }; |
| 178 | + |
| 179 | + services.tabby-scheduler = lib.mkIf (cfg.indexInterval != "never") { |
| 180 | + wantedBy = [ "multi-user.target" ]; |
| 181 | + description = "Tabby repository indexing service"; |
| 182 | + after = [ "network.target" ]; |
| 183 | + environment = serviceEnv; |
| 184 | + preStart = "cp -f /etc/tabby/config.toml \${TABBY_ROOT}/config.toml"; |
| 185 | + serviceConfig = lib.mkMerge [ |
| 186 | + serviceUser |
| 187 | + { |
| 188 | + # Type = "oneshot"; |
| 189 | + ExecStart = "${lib.getExe tabbyPackage} scheduler --now"; |
| 190 | + } |
| 191 | + ]; |
| 192 | + }; |
| 193 | + timers.tabby-scheduler = lib.mkIf (cfg.indexInterval != "never") { |
| 194 | + description = "Update timer for tabby-scheduler"; |
| 195 | + partOf = [ "tabby-scheduler.service" ]; |
| 196 | + wantedBy = [ "timers.target" ]; |
| 197 | + timerConfig.OnUnitInactiveSec = cfg.indexInterval; |
| 198 | + }; |
| 199 | + }; |
| 200 | + }; |
| 201 | + |
| 202 | + meta.maintainers = with lib.maintainers; [ ghthor ]; |
| 203 | +} |
0 commit comments