stash: refactor; 0.25.1 -> 0.27.2; nixos/stash: init (#323231)

This commit is contained in:
Arne Keller 2025-01-26 15:16:35 +01:00 committed by GitHub
commit 2ab9225a40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 887 additions and 35 deletions

View File

@ -6173,6 +6173,13 @@
githubId = 20759788;
name = "JP Lippold";
};
DrakeTDL = {
name = "Drake";
email = "draketdl@mailbox.org";
matrix = "@draketdl:matrix.org";
github = "DrakeTDL";
githubId = 22124013;
};
dramaturg = {
email = "seb@ds.ag";
github = "dramaturg";

View File

@ -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).

View File

@ -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

View 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"
];
};
};
};
};
}

View File

@ -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
View 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")
'';
}
)

View File

@ -1,50 +1,137 @@
{
buildGoModule,
fetchFromGitHub,
fetchYarnDeps,
lib,
nixosTests,
nodejs,
stash,
stdenv,
fetchurl,
testers,
yarnBuildHook,
yarnConfigHook,
}:
let
version = "0.25.1";
sources = {
x86_64-linux = {
url = "https://github.com/stashapp/stash/releases/download/v${version}/stash-linux";
hash = "sha256-Rb4x6iKx6T9NPuWWDbNaz+35XPzLqZzSm0psv+k2Gw4=";
};
aarch64-linux = {
url = "https://github.com/stashapp/stash/releases/download/v${version}/stash-linux-arm64v8";
hash = "sha256-6qPyIYKFkhmBNO47w9E91FSKlByepBOnl0MNJighGSc=";
};
x86_64-darwin = {
url = "https://github.com/stashapp/stash/releases/download/v${version}/stash-macos";
hash = "sha256-W8+rgqWUDTOB8ykGO2GL9tKEjaDXdx9LpFg0TAtJsxM=";
};
};
in
stdenv.mkDerivation (finalAttrs: {
inherit version;
inherit (lib.importJSON ./version.json)
gitHash
srcHash
vendorHash
version
yarnHash
;
pname = "stash";
src = fetchurl { inherit (sources.${stdenv.system}) url hash; };
src = fetchFromGitHub {
owner = "stashapp";
repo = "stash";
tag = "v${version}";
hash = srcHash;
};
dontUnpack = true;
frontend = stdenv.mkDerivation (final: {
inherit version;
pname = "${pname}-ui";
src = "${src}/ui/v2.5";
installPhase = ''
runHook preInstall
yarnOfflineCache = fetchYarnDeps {
yarnLock = "${final.src}/yarn.lock";
hash = yarnHash;
};
install -Dm755 $src $out/bin/stash
nativeBuildInputs = [
yarnConfigHook
yarnBuildHook
# Needed for executing package.json scripts
nodejs
];
runHook postInstall
postPatch = ''
substituteInPlace codegen.ts \
--replace-fail "../../graphql/" "${src}/graphql/"
'';
buildPhase = ''
runHook preBuild
export HOME=$(mktemp -d)
export VITE_APP_DATE='1970-01-01 00:00:00'
export VITE_APP_GITHASH=${gitHash}
export VITE_APP_STASH_VERSION=v${version}
export VITE_APP_NOLEGACY=true
yarn --offline run gqlgen
yarn --offline build
mv build $out
runHook postBuild
'';
dontInstall = true;
dontFixup = true;
});
in
buildGoModule {
inherit
pname
src
version
vendorHash
;
ldflags = [
"-s"
"-w"
"-X 'github.com/stashapp/stash/internal/build.buildstamp=1970-01-01 00:00:00'"
"-X 'github.com/stashapp/stash/internal/build.githash=${gitHash}'"
"-X 'github.com/stashapp/stash/internal/build.version=v${version}'"
"-X 'github.com/stashapp/stash/internal/build.officialBuild=false'"
];
tags = [
"sqlite_stat4"
"sqlite_math_functions"
];
subPackages = [ "cmd/stash" ];
preBuild = ''
cp -a ${frontend} ui/v2.5/build
# `go mod tidy` requires internet access and does nothing
echo "skip_mod_tidy: true" >> gqlgen.yml
# remove `-trimpath` fron `GOFLAGS` because `gqlgen` does not work with it
GOFLAGS="''${GOFLAGS/-trimpath/}" go generate ./cmd/stash
'';
meta = with lib; {
description = "Stash is a self-hosted porn app";
homepage = "https://github.com/stashapp/stash";
license = licenses.agpl3Only;
maintainers = with maintainers; [ Golo300 ];
platforms = builtins.attrNames sources;
mainProgram = "stash";
strictDeps = true;
passthru = {
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";
};
};
};
})
meta = {
mainProgram = "stash";
description = "Organizer for your adult videos/images";
license = lib.licenses.agpl3Only;
homepage = "https://stashapp.cc/";
changelog = "https://github.com/stashapp/stash/blob/v${version}/ui/v2.5/src/docs/en/Changelog/v${lib.versions.major version}${lib.versions.minor version}0.md";
maintainers = with lib.maintainers; [
Golo300
DrakeTDL
];
platforms = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
};
}

82
pkgs/by-name/st/stash/update.py Executable file
View File

@ -0,0 +1,82 @@
#! /usr/bin/env nix-shell
#! nix-shell -i python3 -p python3 prefetch-yarn-deps nix-prefetch-git nix-prefetch
from pathlib import Path
from shutil import copyfile
from urllib.request import Request, urlopen
import json
import os
import subprocess
def run_external(args: list[str]):
proc = subprocess.run(
args,
check=True,
stdout=subprocess.PIPE,
)
return proc.stdout.strip().decode("utf8")
def get_latest_release_tag():
req = Request("https://api.github.com/repos/stashapp/stash/tags?per_page=1")
if "GITHUB_TOKEN" in os.environ:
req.add_header("authorization", f"Bearer {os.environ['GITHUB_TOKEN']}")
with urlopen(req) as resp:
return json.loads(resp.read())[0]
def prefetch_github(rev: str):
print(f"Prefetching stashapp/stash({rev})")
proc = run_external(["nix-prefetch-git", "--no-deepClone", "--rev", rev, f"https://github.com/stashapp/stash"])
return json.loads(proc)
def prefetch_yarn(lock_file: str):
print(f"Prefetching yarn deps")
hash = run_external(["prefetch-yarn-deps", lock_file])
return run_external(["nix", "hash", "convert", "--hash-algo", "sha256", hash])
def prefetch_go_modules(src: str, version: str):
print(f"Prefetching go modules")
expr = fr"""
{{ sha256 }}: (buildGoModule {{
pname = "stash";
src = {src};
version = "{version}";
vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}}).goModules.overrideAttrs (_: {{ modSha256 = sha256; }})
"""
return run_external([
"nix-prefetch",
"--option",
"extra-experimental-features",
"flakes",
expr
])
def save_version_json(version: dict[str, str]):
print("Writing version.json")
with open(Path(__file__).parent / "version.json", 'w') as f:
json.dump(version, f, indent=2)
f.write("\n")
if __name__ == "__main__":
release = get_latest_release_tag()
src = prefetch_github(release['name'])
yarn_hash = prefetch_yarn(f"{src['path']}/ui/v2.5/yarn.lock")
save_version_json({
"version": release["name"][1:],
"gitHash": release["commit"]["sha"][:8],
"srcHash": src["hash"],
"yarnHash": yarn_hash,
"vendorHash": prefetch_go_modules(src["path"], release["name"][1:])
})

View File

@ -0,0 +1,7 @@
{
"version": "0.27.2",
"gitHash": "76648fee",
"srcHash": "sha256-SMZBDKqgVdXf2abaSf/FuG2Vodav7SBu6onjHFZIZIM=",
"yarnHash": "sha256-ufGYQfEbcXO3XhpDQ3UTofS5B31L427KWy5NPbWhBJo=",
"vendorHash": "sha256-ZtKKs0JCEe4OpPulO74qYTYrZu2Ds3prWp5N8UP6z0g="
}