nixos/stash: init
This commit is contained in:
		
							parent
							
								
									8b1f4a0f08
								
							
						
					
					
						commit
						8814ab8ab5
					
				@ -115,6 +115,8 @@
 | 
			
		||||
 | 
			
		||||
- [Zipline](https://zipline.diced.sh/), a ShareX/file upload server that is easy to use, packed with features, and with an easy setup. Available as [services.zipline](#opt-services.zipline.enable).
 | 
			
		||||
 | 
			
		||||
- [Stash](https://github.com/stashapp/stash), An organizer for your adult videos/images, written in Go. Available as [services.stash](#opt-services.stash.enable).
 | 
			
		||||
 | 
			
		||||
- [Fider](https://fider.io/), an open platform to collect and prioritize feedback. Available as [services.fider](#opt-services.fider.enable).
 | 
			
		||||
 | 
			
		||||
- [mqtt-exporter](https://github.com/kpetremann/mqtt-exporter/), a Prometheus exporter for exposing messages from MQTT. Available as [services.prometheus.exporters.mqtt](#opt-services.prometheus.exporters.mqtt.enable).
 | 
			
		||||
 | 
			
		||||
@ -1577,6 +1577,7 @@
 | 
			
		||||
  ./services/web-apps/snipe-it.nix
 | 
			
		||||
  ./services/web-apps/sogo.nix
 | 
			
		||||
  ./services/web-apps/stirling-pdf.nix
 | 
			
		||||
  ./services/web-apps/stash.nix
 | 
			
		||||
  ./services/web-apps/trilium.nix
 | 
			
		||||
  ./services/web-apps/tt-rss.nix
 | 
			
		||||
  ./services/web-apps/vikunja.nix
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										585
									
								
								nixos/modules/services/web-apps/stash.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								nixos/modules/services/web-apps/stash.nix
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,585 @@
 | 
			
		||||
{
 | 
			
		||||
  config,
 | 
			
		||||
  pkgs,
 | 
			
		||||
  lib,
 | 
			
		||||
  ...
 | 
			
		||||
}:
 | 
			
		||||
let
 | 
			
		||||
  inherit (lib)
 | 
			
		||||
    getExe
 | 
			
		||||
    literalExpression
 | 
			
		||||
    mkEnableOption
 | 
			
		||||
    mkIf
 | 
			
		||||
    mkOption
 | 
			
		||||
    mkPackageOption
 | 
			
		||||
    optionalString
 | 
			
		||||
    toUpper
 | 
			
		||||
    types
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
  cfg = config.services.stash;
 | 
			
		||||
 | 
			
		||||
  stashType = types.submodule {
 | 
			
		||||
    options = {
 | 
			
		||||
      path = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        description = "location of your media files";
 | 
			
		||||
      };
 | 
			
		||||
      excludevideo = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Whether to exclude video files from being scanned into Stash";
 | 
			
		||||
      };
 | 
			
		||||
      excludeimage = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Whether to exclude image files from being scanned into Stash";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
  stashBoxType = types.submodule {
 | 
			
		||||
    options = {
 | 
			
		||||
      name = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        description = "The name of the Stash Box";
 | 
			
		||||
      };
 | 
			
		||||
      endpoint = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        description = "URL to the Stash Box graphql api";
 | 
			
		||||
      };
 | 
			
		||||
      apikey = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        description = "Stash Box API key";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  recentlyReleased = mode: {
 | 
			
		||||
    __typename = "CustomFilter";
 | 
			
		||||
    message = {
 | 
			
		||||
      id = "recently_released_objects";
 | 
			
		||||
      values.objects = mode;
 | 
			
		||||
    };
 | 
			
		||||
    mode = toUpper mode;
 | 
			
		||||
    sortBy = "date";
 | 
			
		||||
    direction = "DESC";
 | 
			
		||||
  };
 | 
			
		||||
  recentlyAdded = mode: {
 | 
			
		||||
    __typename = "CustomFilter";
 | 
			
		||||
    message = {
 | 
			
		||||
      id = "recently_added_objects";
 | 
			
		||||
      values.objects = mode;
 | 
			
		||||
    };
 | 
			
		||||
    mode = toUpper mode;
 | 
			
		||||
    sortBy = "created_at";
 | 
			
		||||
    direction = "DESC";
 | 
			
		||||
  };
 | 
			
		||||
  uiPresets = {
 | 
			
		||||
    recentlyReleasedScenes = recentlyReleased "Scenes";
 | 
			
		||||
    recentlyAddedScenes = recentlyAdded "Scenes";
 | 
			
		||||
    recentlyReleasedGalleries = recentlyReleased "Galleries";
 | 
			
		||||
    recentlyAddedGalleries = recentlyAdded "Galleries";
 | 
			
		||||
    recentlyAddedImages = recentlyAdded "Images";
 | 
			
		||||
    recentlyReleasedMovies = recentlyReleased "Movies";
 | 
			
		||||
    recentlyAddedMovies = recentlyAdded "Movies";
 | 
			
		||||
    recentlyAddedStudios = recentlyAdded "Studios";
 | 
			
		||||
    recentlyAddedPerformers = recentlyAdded "Performers";
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  settingsFormat = pkgs.formats.yaml { };
 | 
			
		||||
  settingsFile = settingsFormat.generate "config.yml" cfg.settings;
 | 
			
		||||
  settingsType = types.submodule {
 | 
			
		||||
    freeformType = settingsFormat.type;
 | 
			
		||||
 | 
			
		||||
    options = {
 | 
			
		||||
      host = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "localhost";
 | 
			
		||||
        example = "::1";
 | 
			
		||||
        description = "The ip address that Stash should bind to.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      port = mkOption {
 | 
			
		||||
        type = types.port;
 | 
			
		||||
        default = 9999;
 | 
			
		||||
        example = 1234;
 | 
			
		||||
        description = "The port that Stash should listen on.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      stash = mkOption {
 | 
			
		||||
        type = types.listOf stashType;
 | 
			
		||||
        description = ''
 | 
			
		||||
          Add directories containing your adult videos and images.
 | 
			
		||||
          Stash will use these directories to find videos and/or images during scanning.
 | 
			
		||||
        '';
 | 
			
		||||
        example = literalExpression ''
 | 
			
		||||
          {
 | 
			
		||||
            stash = [
 | 
			
		||||
              {
 | 
			
		||||
                Path = "/media/drive/videos";
 | 
			
		||||
                ExcludeImage = true;
 | 
			
		||||
              }
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
        '';
 | 
			
		||||
      };
 | 
			
		||||
      stash_boxes = mkOption {
 | 
			
		||||
        type = types.listOf stashBoxType;
 | 
			
		||||
        default = [ ];
 | 
			
		||||
        description = ''Stash-box facilitates automated tagging of scenes and performers based on fingerprints and filenames'';
 | 
			
		||||
        example = literalExpression ''
 | 
			
		||||
          {
 | 
			
		||||
            stash_boxes = [
 | 
			
		||||
              {
 | 
			
		||||
                name = "StashDB";
 | 
			
		||||
                endpoint = "https://stashdb.org/graphql";
 | 
			
		||||
                apikey = "aaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccc";
 | 
			
		||||
              }
 | 
			
		||||
            ];
 | 
			
		||||
          }
 | 
			
		||||
        '';
 | 
			
		||||
      };
 | 
			
		||||
      ui.frontPageContent = mkOption {
 | 
			
		||||
        description = "Search filters to display on the front page.";
 | 
			
		||||
        type = types.either (types.listOf types.attrs) (types.functionTo (types.listOf types.attrs));
 | 
			
		||||
        default = presets: [
 | 
			
		||||
          presets.recentlyReleasedScenes
 | 
			
		||||
          presets.recentlyAddedStudios
 | 
			
		||||
          presets.recentlyReleasedMovies
 | 
			
		||||
          presets.recentlyAddedPerformers
 | 
			
		||||
          presets.recentlyReleasedGalleries
 | 
			
		||||
        ];
 | 
			
		||||
        example = literalExpression ''
 | 
			
		||||
          presets: [
 | 
			
		||||
            # To get the savedFilterId, you can query `{ findSavedFilters(mode: <FilterMode>) { id name } }` on localhost:9999/graphql
 | 
			
		||||
            {
 | 
			
		||||
              __typename = "SavedFilter";
 | 
			
		||||
              savedFilterId = 1;
 | 
			
		||||
            }
 | 
			
		||||
            # basic custom filter
 | 
			
		||||
            {
 | 
			
		||||
              __typename = "CustomFilter";
 | 
			
		||||
              title = "Random Scenes";
 | 
			
		||||
              mode = "SCENES";
 | 
			
		||||
              sortBy = "random";
 | 
			
		||||
              direction = "DESC";
 | 
			
		||||
            }
 | 
			
		||||
            presets.recentlyAddedImages
 | 
			
		||||
          ]
 | 
			
		||||
        '';
 | 
			
		||||
        apply = type: if builtins.isFunction type then (type uiPresets) else type;
 | 
			
		||||
      };
 | 
			
		||||
      blobs_path = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/blobs";
 | 
			
		||||
        description = "Path to blobs";
 | 
			
		||||
      };
 | 
			
		||||
      cache = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/cache";
 | 
			
		||||
        description = "Path to cache";
 | 
			
		||||
      };
 | 
			
		||||
      database = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/go.sqlite";
 | 
			
		||||
        description = "Path to the SQLite database";
 | 
			
		||||
      };
 | 
			
		||||
      generated = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/generated";
 | 
			
		||||
        description = "Path to generated files";
 | 
			
		||||
      };
 | 
			
		||||
      plugins_path = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/plugins";
 | 
			
		||||
        description = "Path to scrapers";
 | 
			
		||||
      };
 | 
			
		||||
      scrapers_path = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "${cfg.dataDir}/scrapers";
 | 
			
		||||
        description = "Path to scrapers";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      blobs_storage = mkOption {
 | 
			
		||||
        type = types.enum [
 | 
			
		||||
          "FILESYSTEM"
 | 
			
		||||
          "DATABASE"
 | 
			
		||||
        ];
 | 
			
		||||
        default = "FILESYSTEM";
 | 
			
		||||
        description = "Where to store blobs";
 | 
			
		||||
      };
 | 
			
		||||
      calculate_md5 = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Whether to calculate MD5 checksums for scene video files";
 | 
			
		||||
      };
 | 
			
		||||
      create_image_clip_from_videos = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Create Image Clips from Video extensions when Videos are disabled in Library";
 | 
			
		||||
      };
 | 
			
		||||
      dangerous_allow_public_without_auth = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Learn more at https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet/";
 | 
			
		||||
      };
 | 
			
		||||
      gallery_cover_regex = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "(poster|cover|folder|board)\.[^\.]+$";
 | 
			
		||||
        description = "Regex used to identify images as gallery covers";
 | 
			
		||||
      };
 | 
			
		||||
      no_proxy = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12";
 | 
			
		||||
        description = "A list of domains for which the proxy must not be used";
 | 
			
		||||
      };
 | 
			
		||||
      nobrowser = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
        description = "If we should not auto-open a browser window on startup";
 | 
			
		||||
      };
 | 
			
		||||
      notifications_enabled = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
        description = "If we should send notifications to the desktop";
 | 
			
		||||
      };
 | 
			
		||||
      parallel_tasks = mkOption {
 | 
			
		||||
        type = types.int;
 | 
			
		||||
        default = 1;
 | 
			
		||||
        description = "Number of parallel tasks to start during scan/generate";
 | 
			
		||||
      };
 | 
			
		||||
      preview_audio = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
        description = "Include audio stream in previews";
 | 
			
		||||
      };
 | 
			
		||||
      preview_exclude_end = mkOption {
 | 
			
		||||
        type = types.int;
 | 
			
		||||
        default = 0;
 | 
			
		||||
        description = "Duration of start of video to exclude when generating previews";
 | 
			
		||||
      };
 | 
			
		||||
      preview_exclude_start = mkOption {
 | 
			
		||||
        type = types.int;
 | 
			
		||||
        default = 0;
 | 
			
		||||
        description = "Duration of end of video to exclude when generating previews";
 | 
			
		||||
      };
 | 
			
		||||
      preview_segment_duration = mkOption {
 | 
			
		||||
        type = types.float;
 | 
			
		||||
        default = 0.75;
 | 
			
		||||
        description = "Preview segment duration, in seconds";
 | 
			
		||||
      };
 | 
			
		||||
      preview_segments = mkOption {
 | 
			
		||||
        type = types.int;
 | 
			
		||||
        default = 12;
 | 
			
		||||
        description = "Number of segments in a preview file";
 | 
			
		||||
      };
 | 
			
		||||
      security_tripwire_accessed_from_public_internet = mkOption {
 | 
			
		||||
        type = types.nullOr types.str;
 | 
			
		||||
        default = "";
 | 
			
		||||
        description = "Learn more at https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet/";
 | 
			
		||||
      };
 | 
			
		||||
      sequential_scanning = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Modifies behaviour of the scanning functionality to generate support files (previews/sprites/phash) at the same time as fingerprinting/screenshotting";
 | 
			
		||||
      };
 | 
			
		||||
      show_one_time_moved_notification = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
        description = "Whether a small notification to inform the user that Stash will no longer show a terminal window, and instead will be available in the tray";
 | 
			
		||||
      };
 | 
			
		||||
      sound_on_preview = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Enable sound on mouseover previews";
 | 
			
		||||
      };
 | 
			
		||||
      theme_color = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "#202b33";
 | 
			
		||||
        description = "Sets the `theme-color` property in the UI";
 | 
			
		||||
      };
 | 
			
		||||
      video_file_naming_algorithm = mkOption {
 | 
			
		||||
        type = types.enum [
 | 
			
		||||
          "OSHASH"
 | 
			
		||||
          "MD5"
 | 
			
		||||
        ];
 | 
			
		||||
        default = "OSHASH";
 | 
			
		||||
        description = "Hash algorithm to use for generated file naming";
 | 
			
		||||
      };
 | 
			
		||||
      write_image_thumbnails = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
        description = "Write image thumbnails to disk when generating on the fly";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  pluginType =
 | 
			
		||||
    kind:
 | 
			
		||||
    mkOption {
 | 
			
		||||
      type = types.listOf types.package;
 | 
			
		||||
      default = [ ];
 | 
			
		||||
      description = ''
 | 
			
		||||
        The ${kind} Stash should be started with.
 | 
			
		||||
      '';
 | 
			
		||||
      apply =
 | 
			
		||||
        srcs:
 | 
			
		||||
        optionalString (srcs != [ ]) (
 | 
			
		||||
          pkgs.runCommand "stash-${kind}"
 | 
			
		||||
            {
 | 
			
		||||
              inherit srcs;
 | 
			
		||||
              nativeBuildInputs = [ pkgs.yq-go ];
 | 
			
		||||
              preferLocalBuild = true;
 | 
			
		||||
            }
 | 
			
		||||
            ''
 | 
			
		||||
              find $srcs -mindepth 1 -name '*.yml' | while read plugin_file; do
 | 
			
		||||
                grep -q "^#pkgignore" "$plugin_file" && continue
 | 
			
		||||
 | 
			
		||||
                plugin_dir=$(dirname $plugin_file)
 | 
			
		||||
                out_path=$out/$(basename $plugin_dir)
 | 
			
		||||
                mkdir -p $out_path
 | 
			
		||||
                ls $plugin_dir | xargs -I{} ln -sf "$plugin_dir/{}" $out_path
 | 
			
		||||
 | 
			
		||||
                env \
 | 
			
		||||
                  plugin_id=$(basename $plugin_file .yml) \
 | 
			
		||||
                  plugin_name="$(yq '.name' $plugin_file)" \
 | 
			
		||||
                  plugin_description="$(yq '.description' $plugin_file)" \
 | 
			
		||||
                  plugin_version="$(yq '.version' $plugin_file)" \
 | 
			
		||||
                  plugin_files="$(find -L $out_path -mindepth 1 -type f -printf "%P\n")" \
 | 
			
		||||
                  yq -n '
 | 
			
		||||
                    .id = strenv(plugin_id) |
 | 
			
		||||
                    .name = strenv(plugin_name) |
 | 
			
		||||
                    (
 | 
			
		||||
                      strenv(plugin_description) as $desc |
 | 
			
		||||
                      with(select($desc == "null"); .metadata = {}) |
 | 
			
		||||
                      with(select($desc != "null"); .metadata.description = $desc)
 | 
			
		||||
                    ) |
 | 
			
		||||
                    (
 | 
			
		||||
                      strenv(plugin_version) as $ver |
 | 
			
		||||
                      with(select($ver == "null"); .version = "Unknown") |
 | 
			
		||||
                      with(select($ver != "null"); .version = $ver)
 | 
			
		||||
                    ) |
 | 
			
		||||
                    .date = (now | format_datetime("2006-01-02 15:04:05")) |
 | 
			
		||||
                    .files = (strenv(plugin_files) | split("\n"))
 | 
			
		||||
                  ' > $out_path/manifest
 | 
			
		||||
              done
 | 
			
		||||
            ''
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
in
 | 
			
		||||
{
 | 
			
		||||
  meta = {
 | 
			
		||||
    buildDocsInSandbox = false;
 | 
			
		||||
    maintainers = with lib.maintainers; [ DrakeTDL ];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  options = {
 | 
			
		||||
    services.stash = {
 | 
			
		||||
      enable = mkEnableOption "stash";
 | 
			
		||||
 | 
			
		||||
      package = mkPackageOption pkgs "stash" { };
 | 
			
		||||
 | 
			
		||||
      user = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "stash";
 | 
			
		||||
        description = "User under which Stash runs.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      group = mkOption {
 | 
			
		||||
        type = types.str;
 | 
			
		||||
        default = "stash";
 | 
			
		||||
        description = "Group under which Stash runs.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      dataDir = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        default = "/var/lib/stash";
 | 
			
		||||
        description = "The directory where Stash stores its files.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      openFirewall = mkOption {
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = false;
 | 
			
		||||
        description = "Open ports in the firewall for the Stash web interface.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      username = mkOption {
 | 
			
		||||
        type = types.nullOr types.nonEmptyStr;
 | 
			
		||||
        default = null;
 | 
			
		||||
        example = "admin";
 | 
			
		||||
        description = ''
 | 
			
		||||
          Username for login.
 | 
			
		||||
 | 
			
		||||
          ::: {.warning}
 | 
			
		||||
            This option takes precedence over {option}`services.stash.settings.username`
 | 
			
		||||
          ::
 | 
			
		||||
 | 
			
		||||
        '';
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      passwordFile = mkOption {
 | 
			
		||||
        type = types.nullOr types.path;
 | 
			
		||||
        default = null;
 | 
			
		||||
        example = "/path/to/password/file";
 | 
			
		||||
        description = ''
 | 
			
		||||
          Path to file containing password for login.
 | 
			
		||||
 | 
			
		||||
          ::: {.warning}
 | 
			
		||||
            This option takes precedence over {option}`services.stash.settings.password`
 | 
			
		||||
          ::
 | 
			
		||||
 | 
			
		||||
        '';
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      jwtSecretKeyFile = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        description = "Path to file containing a secret used to sign JWT tokens.";
 | 
			
		||||
      };
 | 
			
		||||
      sessionStoreKeyFile = mkOption {
 | 
			
		||||
        type = types.path;
 | 
			
		||||
        description = "Path to file containing a secret for session store.";
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      mutableSettings = mkOption {
 | 
			
		||||
        description = ''
 | 
			
		||||
          Whether the Stash config.yml is writeable by Stash.
 | 
			
		||||
 | 
			
		||||
          If `false`, Any config changes done from within Stash UI will be temporary and reset to those defined in {option}`services.stash.settings` upon `Stash.service` restart.
 | 
			
		||||
          If `true`, the {option}`services.stash.settings` will only be used to initialize the Stash configuration if it does not exist, and are subsequently ignored.
 | 
			
		||||
        '';
 | 
			
		||||
        type = types.bool;
 | 
			
		||||
        default = true;
 | 
			
		||||
      };
 | 
			
		||||
      mutablePlugins = mkEnableOption "Whether plugins/themes can be installed, updated, uninstalled manually.";
 | 
			
		||||
      mutableScrapers = mkEnableOption "Whether scrapers can be installed, updated, uninstalled manually.";
 | 
			
		||||
      plugins = pluginType "plugins";
 | 
			
		||||
      scrapers = pluginType "scrapers";
 | 
			
		||||
 | 
			
		||||
      settings = mkOption {
 | 
			
		||||
        type = settingsType;
 | 
			
		||||
        description = "Stash configuration";
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  config = mkIf cfg.enable {
 | 
			
		||||
    assertions = [
 | 
			
		||||
      {
 | 
			
		||||
        assertion =
 | 
			
		||||
          !lib.xor (cfg.username != null || cfg.settings.username or null != null) (
 | 
			
		||||
            cfg.passwordFile != null || cfg.settings.password or null != null
 | 
			
		||||
          );
 | 
			
		||||
        message = "You must set either both username and password, or neither.";
 | 
			
		||||
      }
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    services.stash.settings = {
 | 
			
		||||
      username = mkIf (cfg.username != null) cfg.username;
 | 
			
		||||
      plugins_path = mkIf (!cfg.mutablePlugins) cfg.plugins;
 | 
			
		||||
      scrapers_path = mkIf (!cfg.mutableScrapers) cfg.scrapers;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ];
 | 
			
		||||
 | 
			
		||||
    users.users.${cfg.user} = {
 | 
			
		||||
      inherit (cfg) group;
 | 
			
		||||
      isSystemUser = true;
 | 
			
		||||
      home = cfg.dataDir;
 | 
			
		||||
    };
 | 
			
		||||
    users.groups.${cfg.group} = { };
 | 
			
		||||
 | 
			
		||||
    systemd = {
 | 
			
		||||
      tmpfiles.settings."10-stash-datadir".${cfg.dataDir}."d" = {
 | 
			
		||||
        inherit (cfg) user group;
 | 
			
		||||
        mode = "0755";
 | 
			
		||||
      };
 | 
			
		||||
      services.stash = {
 | 
			
		||||
        wantedBy = [ "multi-user.target" ];
 | 
			
		||||
        after = [ "network.target" ];
 | 
			
		||||
        path = with pkgs; [
 | 
			
		||||
          ffmpeg-full
 | 
			
		||||
          python3
 | 
			
		||||
          ruby
 | 
			
		||||
        ];
 | 
			
		||||
        environment.STASH_CONFIG_FILE = "${cfg.dataDir}/config.yml";
 | 
			
		||||
        serviceConfig = {
 | 
			
		||||
          DynamicUser = false;
 | 
			
		||||
          User = cfg.user;
 | 
			
		||||
          Group = cfg.group;
 | 
			
		||||
          Restart = "on-failure";
 | 
			
		||||
          WorkingDirectory = cfg.dataDir;
 | 
			
		||||
          StateDirectory = mkIf (cfg.dataDir == "/var/lib/stash") (baseNameOf cfg.dataDir);
 | 
			
		||||
          ExecStartPre = pkgs.writers.writeBash "stash-setup.bash" (
 | 
			
		||||
            ''
 | 
			
		||||
              install -d ${cfg.settings.generated}
 | 
			
		||||
              if [[ ! -z "${toString cfg.mutableSettings}" || ! -f ${cfg.dataDir}/config.yml ]]; then
 | 
			
		||||
                env \
 | 
			
		||||
                  password=$(< ${cfg.passwordFile}) \
 | 
			
		||||
                  jwtSecretKeyFile=$(< ${cfg.jwtSecretKeyFile}) \
 | 
			
		||||
                  sessionStoreKeyFile=$(< ${cfg.sessionStoreKeyFile}) \
 | 
			
		||||
                  ${lib.getExe pkgs.yq-go} '
 | 
			
		||||
                    .jwt_secret_key = strenv(jwtSecretKeyFile) |
 | 
			
		||||
                    .session_store_key = strenv(sessionStoreKeyFile) |
 | 
			
		||||
                    (
 | 
			
		||||
                      strenv(password) as $password |
 | 
			
		||||
                      with(select($password != ""); .password = $password)
 | 
			
		||||
                    )
 | 
			
		||||
                  ' ${settingsFile} > ${cfg.dataDir}/config.yml
 | 
			
		||||
              fi
 | 
			
		||||
            ''
 | 
			
		||||
            + optionalString cfg.mutablePlugins ''
 | 
			
		||||
              install -d ${cfg.settings.plugins_path}
 | 
			
		||||
              ls ${cfg.plugins} | xargs -I{} ln -sf '${cfg.plugins}/{}' ${cfg.settings.plugins_path}
 | 
			
		||||
            ''
 | 
			
		||||
            + optionalString cfg.mutableScrapers ''
 | 
			
		||||
              install -d ${cfg.settings.scrapers_path}
 | 
			
		||||
              ls ${cfg.scrapers} | xargs -I{} ln -sf '${cfg.scrapers}/{}' ${cfg.settings.scrapers_path}
 | 
			
		||||
            ''
 | 
			
		||||
          );
 | 
			
		||||
          ExecStart = getExe cfg.package;
 | 
			
		||||
 | 
			
		||||
          ProtectHome = "tmpfs";
 | 
			
		||||
          BindReadOnlyPaths = mkIf (cfg.settings != { }) (map (stash: "${stash.path}") cfg.settings.stash);
 | 
			
		||||
 | 
			
		||||
          # hardening
 | 
			
		||||
 | 
			
		||||
          DevicePolicy = "auto"; # needed for hardware acceleration
 | 
			
		||||
          PrivateDevices = false; # needed for hardware acceleration
 | 
			
		||||
          AmbientCapabilities = [ "" ];
 | 
			
		||||
          CapabilityBoundingSet = [ "" ];
 | 
			
		||||
          ProtectSystem = "full";
 | 
			
		||||
          LockPersonality = true;
 | 
			
		||||
          NoNewPrivileges = true;
 | 
			
		||||
          PrivateTmp = true;
 | 
			
		||||
          PrivateUsers = true;
 | 
			
		||||
          ProtectClock = true;
 | 
			
		||||
          ProtectControlGroups = true;
 | 
			
		||||
          ProtectHostname = true;
 | 
			
		||||
          ProtectKernelLogs = true;
 | 
			
		||||
          ProtectKernelModules = true;
 | 
			
		||||
          ProtectKernelTunables = true;
 | 
			
		||||
          ProcSubset = "pid";
 | 
			
		||||
          ProtectProc = "invisible";
 | 
			
		||||
          RemoveIPC = true;
 | 
			
		||||
          RestrictAddressFamilies = [
 | 
			
		||||
            "AF_UNIX"
 | 
			
		||||
            "AF_INET"
 | 
			
		||||
            "AF_INET6"
 | 
			
		||||
          ];
 | 
			
		||||
          RestrictNamespaces = true;
 | 
			
		||||
          RestrictRealtime = true;
 | 
			
		||||
          RestrictSUIDSGID = true;
 | 
			
		||||
          MemoryDenyWriteExecute = true;
 | 
			
		||||
          SystemCallArchitectures = "native";
 | 
			
		||||
          SystemCallFilter = [
 | 
			
		||||
            "~@cpu-emulation"
 | 
			
		||||
            "~@debug"
 | 
			
		||||
            "~@mount"
 | 
			
		||||
            "~@obsolete"
 | 
			
		||||
            "~@privileged"
 | 
			
		||||
          ];
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -982,6 +982,7 @@ in {
 | 
			
		||||
  stalwart-mail = handleTest ./stalwart-mail.nix {};
 | 
			
		||||
  stargazer = runTest ./web-servers/stargazer.nix;
 | 
			
		||||
  starship = handleTest ./starship.nix {};
 | 
			
		||||
  stash = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./stash.nix {};
 | 
			
		||||
  static-web-server = handleTest ./web-servers/static-web-server.nix {};
 | 
			
		||||
  step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
 | 
			
		||||
  stratis = handleTest ./stratis {};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								nixos/tests/stash.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								nixos/tests/stash.nix
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
import ./make-test-python.nix (
 | 
			
		||||
  let
 | 
			
		||||
    host = "127.0.0.1";
 | 
			
		||||
    port = 1234;
 | 
			
		||||
    dataDir = "/stash";
 | 
			
		||||
  in
 | 
			
		||||
  { pkgs, ... }:
 | 
			
		||||
  {
 | 
			
		||||
    name = "stash";
 | 
			
		||||
    meta.maintainers = pkgs.stash.meta.maintainers;
 | 
			
		||||
 | 
			
		||||
    nodes.machine = {
 | 
			
		||||
      services.stash = {
 | 
			
		||||
        inherit dataDir;
 | 
			
		||||
        enable = true;
 | 
			
		||||
 | 
			
		||||
        username = "test";
 | 
			
		||||
        passwordFile = pkgs.writeText "stash-password" "MyPassword";
 | 
			
		||||
 | 
			
		||||
        jwtSecretKeyFile = pkgs.writeText "jwt_secret_key" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
 | 
			
		||||
        sessionStoreKeyFile = pkgs.writeText "session_store_key" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
 | 
			
		||||
 | 
			
		||||
        plugins =
 | 
			
		||||
          let
 | 
			
		||||
            src = pkgs.fetchFromGitHub {
 | 
			
		||||
              owner = "stashapp";
 | 
			
		||||
              repo = "CommunityScripts";
 | 
			
		||||
              rev = "9b6fac4934c2fac2ef0859ea68ebee5111fc5be5";
 | 
			
		||||
              hash = "sha256-PO3J15vaA7SD4r/LyHlXjnpaeYAN9Q++O94bIWdz7OA=";
 | 
			
		||||
            };
 | 
			
		||||
          in
 | 
			
		||||
          [
 | 
			
		||||
            (pkgs.runCommand "stashNotes" { inherit src; } ''
 | 
			
		||||
              mkdir -p $out/plugins
 | 
			
		||||
              cp -r $src/plugins/stashNotes $out/plugins/stashNotes
 | 
			
		||||
            '')
 | 
			
		||||
            (pkgs.runCommand "Theme-Plex" { inherit src; } ''
 | 
			
		||||
              mkdir -p $out/plugins
 | 
			
		||||
              cp -r $src/themes/Theme-Plex $out/plugins/Theme-Plex
 | 
			
		||||
            '')
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
        mutableScrapers = true;
 | 
			
		||||
        scrapers =
 | 
			
		||||
          let
 | 
			
		||||
            src = pkgs.fetchFromGitHub {
 | 
			
		||||
              owner = "stashapp";
 | 
			
		||||
              repo = "CommunityScrapers";
 | 
			
		||||
              rev = "2ece82d17ddb0952c16842b0775274bcda598d81";
 | 
			
		||||
              hash = "sha256-AEmnvM8Nikhue9LNF9dkbleYgabCvjKHtzFpMse4otM=";
 | 
			
		||||
            };
 | 
			
		||||
          in
 | 
			
		||||
          [
 | 
			
		||||
            (pkgs.runCommand "FTV" { inherit src; } ''
 | 
			
		||||
              mkdir -p $out/scrapers/FTV
 | 
			
		||||
              cp -r $src/scrapers/FTV.yml $out/scrapers/FTV
 | 
			
		||||
            '')
 | 
			
		||||
          ];
 | 
			
		||||
 | 
			
		||||
        settings = {
 | 
			
		||||
          inherit host port;
 | 
			
		||||
 | 
			
		||||
          stash = [ { path = "/srv"; } ];
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    testScript = ''
 | 
			
		||||
      machine.wait_for_unit("stash.service")
 | 
			
		||||
      machine.wait_for_open_port(${toString port}, "${host}")
 | 
			
		||||
      machine.succeed("curl --fail http://${host}:${toString port}/")
 | 
			
		||||
 | 
			
		||||
      with subtest("Test plugins/scrapers"):
 | 
			
		||||
        with subtest("mutable plugins directory should not exist"):
 | 
			
		||||
          machine.fail("test -d ${dataDir}/plugins")
 | 
			
		||||
        with subtest("mutable scrapers directory should exist and scraper FTV should be linked"):
 | 
			
		||||
          machine.succeed("test -L ${dataDir}/scrapers/FTV")
 | 
			
		||||
    '';
 | 
			
		||||
  }
 | 
			
		||||
)
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
  fetchFromGitHub,
 | 
			
		||||
  fetchYarnDeps,
 | 
			
		||||
  lib,
 | 
			
		||||
  nixosTests,
 | 
			
		||||
  nodejs,
 | 
			
		||||
  stash,
 | 
			
		||||
  stdenv,
 | 
			
		||||
@ -108,6 +109,7 @@ buildGoModule {
 | 
			
		||||
    inherit frontend;
 | 
			
		||||
    updateScript = ./update.py;
 | 
			
		||||
    tests = {
 | 
			
		||||
      inherit (nixosTests) stash;
 | 
			
		||||
      version = testers.testVersion {
 | 
			
		||||
        package = stash;
 | 
			
		||||
        version = "v${version} (${gitHash}) - Unofficial Build - 1970-01-01 00:00:00";
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user