about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/lanraragi.nix100
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/lanraragi.nix40
-rw-r--r--pkgs/by-name/la/lanraragi/expose-password-hashing.patch36
-rw-r--r--pkgs/by-name/la/lanraragi/fix-minion-redis-password.patch34
-rw-r--r--pkgs/by-name/la/lanraragi/fix-paths.patch100
-rw-r--r--pkgs/by-name/la/lanraragi/install.patch63
-rw-r--r--pkgs/by-name/la/lanraragi/package.nix130
-rw-r--r--pkgs/development/perl-modules/Alien-FFI-dont-download.patch29
-rw-r--r--pkgs/development/perl-modules/ArchiveLibarchive-set-findlib-path.patch25
-rw-r--r--pkgs/top-level/perl-packages.nix334
12 files changed, 893 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index e54774a6a4ea..2964689f0ee1 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1270,6 +1270,7 @@
   ./services/web-apps/kavita.nix
   ./services/web-apps/keycloak.nix
   ./services/web-apps/komga.nix
+  ./services/web-apps/lanraragi.nix
   ./services/web-apps/lemmy.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mainsail.nix
diff --git a/nixos/modules/services/web-apps/lanraragi.nix b/nixos/modules/services/web-apps/lanraragi.nix
new file mode 100644
index 000000000000..f1ab8b8b4eb4
--- /dev/null
+++ b/nixos/modules/services/web-apps/lanraragi.nix
@@ -0,0 +1,100 @@
+{ pkgs, lib, config, ... }:
+
+let
+  cfg = config.services.lanraragi;
+in
+{
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  options.services = {
+    lanraragi = {
+      enable = lib.mkEnableOption (lib.mdDoc "LANraragi");
+      package = lib.mkPackageOptionMD pkgs "lanraragi" { };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 3000;
+        description = lib.mdDoc "Port for LANraragi's web interface.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/lanraragi-password";
+        description = lib.mdDoc ''
+          A file containing the password for LANraragi's admin interface.
+        '';
+      };
+
+      redis = {
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 6379;
+          description = lib.mdDoc "Port for LANraragi's Redis server.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/redis-lanraragi-password";
+          description = lib.mdDoc ''
+            A file containing the password for LANraragi's Redis server.
+          '';
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.redis.servers.lanraragi = {
+      enable = true;
+      port = cfg.redis.port;
+      requirePassFile = cfg.redis.passwordFile;
+    };
+
+    systemd.services.lanraragi = {
+      description = "LANraragi main service";
+      after = [ "network.target" "redis-lanraragi.service" ];
+      requires = [ "redis-lanraragi.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        DynamicUser = true;
+        StateDirectory = "lanraragi";
+        RuntimeDirectory = "lanraragi";
+        LogsDirectory = "lanraragi";
+        Restart = "on-failure";
+        WorkingDirectory = "/var/lib/lanraragi";
+      };
+      environment = {
+        "LRR_TEMP_DIRECTORY" = "/run/lanraragi";
+        "LRR_LOG_DIRECTORY" = "/var/log/lanraragi";
+        "LRR_NETWORK" = "http://*:${toString cfg.port}";
+        "HOME" = "/var/lib/lanraragi";
+      };
+      preStart = ''
+        REDIS_PASS=${lib.optionalString (cfg.redis.passwordFile != null) "$(head -n1 ${cfg.redis.passwordFile})"}
+        cat > lrr.conf <<EOF
+        {
+          redis_address => "127.0.0.1:${toString cfg.redis.port}",
+          redis_password => "$REDIS_PASS",
+          redis_database => "0",
+          redis_database_minion => "1",
+          redis_database_config => "2",
+          redis_database_search => "3",
+        }
+        EOF
+      '' + lib.optionalString (cfg.passwordFile != null) ''
+        PASS_HASH=$(
+          PASS=$(head -n1 ${cfg.passwordFile}) ${cfg.package.perlEnv}/bin/perl -I${cfg.package}/share/lanraragi/lib -e \
+            'use LANraragi::Controller::Config; print LANraragi::Controller::Config::make_password_hash($ENV{PASS})' \
+            2>/dev/null
+        )
+
+        ${lib.getExe pkgs.redis} -h 127.0.0.1 -p ${toString cfg.redis.port} -a "$REDIS_PASS" <<EOF
+          SELECT 2
+          HSET LRR_CONFIG password $PASS_HASH
+        EOF
+      '';
+    };
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 5ea7bc9394c6..3531930d863a 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -432,6 +432,7 @@ in {
   kubo = import ./kubo { inherit recurseIntoAttrs runTest; };
   ladybird = handleTest ./ladybird.nix {};
   languagetool = handleTest ./languagetool.nix {};
+  lanraragi = handleTest ./lanraragi.nix {};
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   leaps = handleTest ./leaps.nix {};
   lemmy = handleTest ./lemmy.nix {};
diff --git a/nixos/tests/lanraragi.nix b/nixos/tests/lanraragi.nix
new file mode 100644
index 000000000000..f513ac9d252b
--- /dev/null
+++ b/nixos/tests/lanraragi.nix
@@ -0,0 +1,40 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lanraragi";
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  nodes = {
+    machine1 = { pkgs, ... }: {
+      services.lanraragi.enable = true;
+    };
+    machine2 = { pkgs, ... }: {
+      services.lanraragi = {
+        enable = true;
+        passwordFile = pkgs.writeText "lrr-test-pass" ''
+          ultra-secure-password
+        '';
+        port = 4000;
+        redis = {
+          port = 4001;
+          passwordFile = pkgs.writeText "redis-lrr-test-pass" ''
+            still-a-very-secure-password
+          '';
+        };
+      };
+    };
+
+
+  };
+
+  testScript = ''
+    start_all()
+
+    machine1.wait_for_unit("lanraragi.service")
+    machine1.wait_until_succeeds("curl -f localhost:3000")
+    machine1.succeed("[ $(curl -o /dev/null -X post 'http://localhost:3000/login' --data-raw 'password=kamimamita' -w '%{http_code}') -eq 302 ]")
+
+    machine2.wait_for_unit("lanraragi.service")
+    machine2.wait_until_succeeds("curl -f localhost:4000")
+    machine2.succeed("[ $(curl -o /dev/null -X post 'http://localhost:4000/login' --data-raw 'password=ultra-secure-password' -w '%{http_code}') -eq 302 ]")
+  '';
+})
+
diff --git a/pkgs/by-name/la/lanraragi/expose-password-hashing.patch b/pkgs/by-name/la/lanraragi/expose-password-hashing.patch
new file mode 100644
index 000000000000..1f6941f55ff4
--- /dev/null
+++ b/pkgs/by-name/la/lanraragi/expose-password-hashing.patch
@@ -0,0 +1,36 @@
+diff --git a/lib/LANraragi/Controller/Config.pm b/lib/LANraragi/Controller/Config.pm
+index 2cd2c999..0bd8ab6e 100644
+--- a/lib/LANraragi/Controller/Config.pm
++++ b/lib/LANraragi/Controller/Config.pm
+@@ -50,6 +50,15 @@ sub index {
+     );
+ }
+ 
++sub make_password_hash {
++    my $ppr = Authen::Passphrase::BlowfishCrypt->new(
++        cost        => 8,
++        salt_random => 1,
++        passphrase  => shift,
++    );
++    return $ppr->as_rfc2307;
++}
++
+ # Save the given parameters to the Redis config
+ sub save_config {
+ 
+@@ -95,14 +104,7 @@ sub save_config {
+         my $password = $self->req->param('newpassword');
+ 
+         if ( $password ne "" ) {
+-            my $ppr = Authen::Passphrase::BlowfishCrypt->new(
+-                cost        => 8,
+-                salt_random => 1,
+-                passphrase  => $password,
+-            );
+-
+-            my $pass_hashed = $ppr->as_rfc2307;
+-            $confhash{password} = $pass_hashed;
++            $confhash{password} = make_password_hash($password);
+         }
+     }
+ 
diff --git a/pkgs/by-name/la/lanraragi/fix-minion-redis-password.patch b/pkgs/by-name/la/lanraragi/fix-minion-redis-password.patch
new file mode 100644
index 000000000000..eb6b88f68f7f
--- /dev/null
+++ b/pkgs/by-name/la/lanraragi/fix-minion-redis-password.patch
@@ -0,0 +1,34 @@
+diff --git a/lib/LANraragi.pm b/lib/LANraragi.pm
+index e6b833c4..d677030b 100644
+--- a/lib/LANraragi.pm
++++ b/lib/LANraragi.pm
+@@ -144,8 +144,13 @@ sub startup {
+     shutdown_from_pid( get_temp . "/minion.pid" );
+ 
+     my $miniondb = $self->LRR_CONF->get_redisad . "/" . $self->LRR_CONF->get_miniondb;
++    my $redispassword = $self->LRR_CONF->get_redispassword;
++
++    # If the password is non-empty, add the required delimiters
++    if ($redispassword) { $redispassword = "x:" . $redispassword . "@"; }
++
+     say "Minion will use the Redis database at $miniondb";
+-    $self->plugin( 'Minion' => { Redis => "redis://$miniondb" } );
++    $self->plugin( 'Minion' => { Redis => "redis://$redispassword$miniondb" } );
+     $self->LRR_LOGGER->info("Successfully connected to Minion database.");
+     $self->minion->missing_after(5);    # Clean up older workers after 5 seconds of unavailability
+ 
+diff --git a/lib/LANraragi/Model/Config.pm b/lib/LANraragi/Model/Config.pm
+index f52056d4..63e1f5d3 100644
+--- a/lib/LANraragi/Model/Config.pm
++++ b/lib/LANraragi/Model/Config.pm
+@@ -42,8 +42,8 @@ sub get_minion {
+     my $miniondb = get_redisad . "/" . get_miniondb;
+     my $password = get_redispassword;
+ 
+-    # If the password is non-empty, add the required @
+-    if ($password) { $password = $password . "@"; }
++    # If the password is non-empty, add the required delimiters
++    if ($password) { $password = "x:" . $password . "@"; }
+ 
+     return Minion->new( Redis => "redis://$password$miniondb" );
+ }
diff --git a/pkgs/by-name/la/lanraragi/fix-paths.patch b/pkgs/by-name/la/lanraragi/fix-paths.patch
new file mode 100644
index 000000000000..f545bd4759f7
--- /dev/null
+++ b/pkgs/by-name/la/lanraragi/fix-paths.patch
@@ -0,0 +1,100 @@
+diff --git a/lib/LANraragi.pm b/lib/LANraragi.pm
+index e6b833c4..4b90e4c5 100644
+--- a/lib/LANraragi.pm
++++ b/lib/LANraragi.pm
+@@ -21,6 +21,8 @@ use LANraragi::Utils::Minion;
+ use LANraragi::Model::Search;
+ use LANraragi::Model::Config;
+ 
++use FindBin;
++
+ # This method will run once at server start
+ sub startup {
+     my $self = shift;
+@@ -30,7 +32,7 @@ sub startup {
+     say "キタ━━━━━━(゚∀゚)━━━━━━!!!!!";
+ 
+     # Load package.json to get version/vername/description
+-    my $packagejson = decode_json( Mojo::File->new('package.json')->slurp );
++    my $packagejson = decode_json( Mojo::File->new("$FindBin::Bin/../package.json")->slurp );
+ 
+     my $version = $packagejson->{version};
+     my $vername = $packagejson->{version_name};
+diff --git a/lib/LANraragi/Model/Archive.pm b/lib/LANraragi/Model/Archive.pm
+index 73e824dd..8bcea29c 100644
+--- a/lib/LANraragi/Model/Archive.pm
++++ b/lib/LANraragi/Model/Archive.pm
+@@ -13,6 +13,7 @@ use Time::HiRes qw(usleep);
+ use File::Basename;
+ use File::Copy "cp";
+ use File::Path qw(make_path);
++use FindBin;
+ 
+ use LANraragi::Utils::Generic qw(remove_spaces remove_newlines render_api_response);
+ use LANraragi::Utils::TempFolder qw(get_temp);
+@@ -126,7 +127,7 @@ sub serve_thumbnail {
+         } else {
+ 
+             # If the thumbnail doesn't exist, serve the default thumbnail.
+-            $self->render_file( filepath => "./public/img/noThumb.png" );
++            $self->render_file( filepath => "$FindBin::Bin/../public/img/noThumb.png" );
+         }
+         return;
+ 
+diff --git a/lib/LANraragi/Utils/Generic.pm b/lib/LANraragi/Utils/Generic.pm
+index 14736893..4352f023 100644
+--- a/lib/LANraragi/Utils/Generic.pm
++++ b/lib/LANraragi/Utils/Generic.pm
+@@ -17,6 +17,8 @@ use Sys::CpuAffinity;
+ use LANraragi::Utils::TempFolder qw(get_temp);
+ use LANraragi::Utils::Logging qw(get_logger);
+ 
++use FindBin;
++
+ # Generic Utility Functions.
+ use Exporter 'import';
+ our @EXPORT_OK =
+@@ -161,7 +163,7 @@ sub start_shinobu {
+     my $mojo = shift;
+ 
+     my $proc = Proc::Simple->new();
+-    $proc->start( $^X, "./lib/Shinobu.pm" );
++    $proc->start( $^X, "$FindBin::Bin/../lib/Shinobu.pm" );
+     $proc->kill_on_destroy(0);
+ 
+     $mojo->LRR_LOGGER->debug( "Shinobu Worker new PID is " . $proc->pid );
+@@ -201,7 +203,7 @@ sub get_css_list {
+ 
+     #Get all the available CSS sheets.
+     my @css;
+-    opendir( my $dir, "./public/themes" ) or die $!;
++    opendir( my $dir, "$FindBin::Bin/../public/themes" ) or die $!;
+     while ( my $file = readdir($dir) ) {
+         if ( $file =~ /.+\.css/ ) { push( @css, $file ); }
+     }
+diff --git a/lib/LANraragi/Utils/Logging.pm b/lib/LANraragi/Utils/Logging.pm
+index ee29c507..6bdfc1bd 100644
+--- a/lib/LANraragi/Utils/Logging.pm
++++ b/lib/LANraragi/Utils/Logging.pm
+@@ -18,7 +18,7 @@ our @EXPORT_OK = qw(get_logger get_plugin_logger get_logdir get_lines_from_file)
+ # Get the Log folder.
+ sub get_logdir {
+ 
+-    my $log_folder = "$FindBin::Bin/../log";
++    my $log_folder = "./log";
+ 
+     # Folder location can be overriden by LRR_LOG_DIRECTORY
+     if ( $ENV{LRR_LOG_DIRECTORY} ) {
+diff --git a/lib/LANraragi/Utils/TempFolder.pm b/lib/LANraragi/Utils/TempFolder.pm
+index 792b1c1b..f0eb341b 100644
+--- a/lib/LANraragi/Utils/TempFolder.pm
++++ b/lib/LANraragi/Utils/TempFolder.pm
+@@ -20,7 +20,7 @@ our @EXPORT_OK = qw(get_temp get_tempsize clean_temp_full clean_temp_partial);
+ #Get the current tempfolder.
+ #This can be called from any process safely as it uses FindBin.
+ sub get_temp {
+-    my $temp_folder = "$FindBin::Bin/../public/temp";
++    my $temp_folder = "./public/temp";
+ 
+     # Folder location can be overriden by LRR_TEMP_DIRECTORY
+     if ( $ENV{LRR_TEMP_DIRECTORY} ) {
diff --git a/pkgs/by-name/la/lanraragi/install.patch b/pkgs/by-name/la/lanraragi/install.patch
new file mode 100644
index 000000000000..9d260f5a494f
--- /dev/null
+++ b/pkgs/by-name/la/lanraragi/install.patch
@@ -0,0 +1,63 @@
+diff --git a/tools/cpanfile b/tools/cpanfile
+index 359c61fe..ca3b7ec7 100755
+--- a/tools/cpanfile
++++ b/tools/cpanfile
+@@ -20,7 +20,7 @@ requires 'Sort::Naturally',     1.03;
+ requires 'Authen::Passphrase',  0.008;
+ requires 'File::ReadBackwards', 1.05;
+ requires 'URI::Escape',         1.74;
+-requires 'URI',                 5.09;
++requires 'URI',                 5.05;
+ 
+ # Used by Installer
+ requires 'IPC::Cmd', 1.02;
+diff --git a/tools/install.pl b/tools/install.pl
+index 0cbb847d..1bd61fa0 100755
+--- a/tools/install.pl
++++ b/tools/install.pl
+@@ -91,32 +91,6 @@ if ( $ENV{HOMEBREW_FORMULA_PREFIX} ) {
+     $cpanopt = " -l " . $ENV{HOMEBREW_FORMULA_PREFIX} . "/libexec";
+ }
+ 
+-#Load IPC::Cmd
+-install_package( "IPC::Cmd",         $cpanopt );
+-install_package( "Config::AutoConf", $cpanopt );
+-IPC::Cmd->import('can_run');
+-require Config::AutoConf;
+-
+-say("\r\nWill now check if all LRR software dependencies are met. \r\n");
+-
+-#Check for Redis
+-say("Checking for Redis...");
+-can_run('redis-server')
+-  or die 'NOT FOUND! Please install a Redis server before proceeding.';
+-say("OK!");
+-
+-#Check for GhostScript
+-say("Checking for GhostScript...");
+-can_run('gs')
+-  or warn 'NOT FOUND! PDF support will not work properly. Please install the "gs" tool.';
+-say("OK!");
+-
+-#Check for libarchive
+-say("Checking for libarchive...");
+-Config::AutoConf->new()->check_header("archive.h")
+-  or die 'NOT FOUND! Please install libarchive and ensure its headers are present.';
+-say("OK!");
+-
+ #Check for PerlMagick
+ say("Checking for ImageMagick/PerlMagick...");
+ my $imgk;
+@@ -154,12 +128,6 @@ if ( $back || $full ) {
+ #Clientside Dependencies with Provisioning
+ if ( $front || $full ) {
+ 
+-    say("\r\nObtaining remote Web dependencies...\r\n");
+-
+-    if ( system("npm install") != 0 ) {
+-        die "Something went wrong while obtaining node modules - Bailing out.";
+-    }
+-
+     say("\r\nProvisioning...\r\n");
+ 
+     #Load File::Copy
diff --git a/pkgs/by-name/la/lanraragi/package.nix b/pkgs/by-name/la/lanraragi/package.nix
new file mode 100644
index 000000000000..74ec38eeae23
--- /dev/null
+++ b/pkgs/by-name/la/lanraragi/package.nix
@@ -0,0 +1,130 @@
+{ lib
+, stdenv
+, buildNpmPackage
+, fetchFromGitHub
+, fetchpatch
+, makeBinaryWrapper
+, perl
+, ghostscript
+, nixosTests
+}:
+
+let
+  perlEnv = perl.withPackages (_: cpanDeps);
+
+  cpanDeps = with perl.pkgs; [
+    ImageMagick
+    locallib
+    Redis
+    Encode
+    ArchiveLibarchiveExtract
+    ArchiveLibarchivePeek
+    NetDNSNative
+    SortNaturally
+    AuthenPassphrase
+    FileReadBackwards
+    URI
+    LogfileRotate
+    Mojolicious
+    MojoliciousPluginTemplateToolkit
+    MojoliciousPluginRenderFile
+    MojoliciousPluginStatus
+    IOSocketSSL
+    CpanelJSONXS
+    Minion
+    MinionBackendRedis
+    ProcSimple
+    ParallelLoops
+    SysCpuAffinity
+    FileChangeNotify
+    ModulePluggable
+    TimeLocal
+  ] ++ lib.optional stdenv.isLinux LinuxInotify2;
+in
+buildNpmPackage rec {
+  pname = "lanraragi";
+  version = "0.8.90";
+
+  src = fetchFromGitHub {
+    owner = "Difegue";
+    repo = "LANraragi";
+    rev = "v.${version}";
+    hash = "sha256-ljnREUGCKvUJvcQ+aJ6XqiMTkVmfjt/0oC47w3PCj/k=";
+  };
+
+  patches = [
+    (fetchpatch {
+      name = "add-package-lock-json.patch"; # Can be removed when updating to 0.9.0
+      url = "https://github.com/Difegue/LANraragi/commit/c5cd8641795bf7e40deef4ae955ea848dde44050.patch";
+      hash = "sha256-XKxRzeugkIe6N4XRN6+O1wEZpxo6OzU0OaG0ywKFv38=";
+    })
+    ./install.patch
+    ./fix-paths.patch
+    ./expose-password-hashing.patch
+    ./fix-minion-redis-password.patch # Should be upstreamed
+  ];
+
+  npmFlags = [ "--legacy-peer-deps" ];
+
+  npmDepsHash = "sha256-UQsChPU5b4+r5Kv6P/3rJCGUzssiUNSKo3w4axNyJew=";
+
+  nativeBuildInputs = [
+    perl
+    makeBinaryWrapper
+    perl.pkgs.Appcpanminus
+  ] ++ cpanDeps;
+
+  nativeCheckInputs = with perl.pkgs; [
+    TestMockObject
+    TestTrap
+    TestDeep
+  ];
+
+  buildPhase = ''
+    runHook preBuild
+
+    perl ./tools/install.pl install-full
+    rm -r node_modules public/js/vendor/*.map public/css/vendor/*.map
+
+    runHook postBuild
+  '';
+
+  doCheck = true;
+
+  checkPhase = ''
+    runHook preCheck
+
+    rm tests/plugins.t # Uses network
+    prove -r -l -v tests
+
+    runHook postCheck
+  '';
+
+  installPhase = ''
+    runHook preInstall
+
+    mkdir -p $out/share/lanraragi
+    cp -r lib public script templates package.json $out/share/lanraragi
+
+    makeWrapper ${perlEnv}/bin/perl $out/bin/lanraragi \
+      --prefix PATH : ${lib.makeBinPath [ ghostscript ]} \
+      --add-flags "$out/share/lanraragi/script/launcher.pl -f $out/share/lanraragi/script/lanraragi"
+
+    runHook postInstall
+  '';
+
+  passthru = {
+    inherit perlEnv;
+    tests = { inherit (nixosTests) lanraragi; };
+  };
+
+  meta = {
+    changelog = "https://github.com/Difegue/LANraragi/releases/tag/${src.rev}";
+    description = "Web application for archival and reading of manga/doujinshi";
+    homepage = "https://github.com/Difegue/LANraragi";
+    license = lib.licenses.mit;
+    mainProgram = "lanraragi";
+    maintainers = with lib.maintainers; [ tomasajt ];
+    platforms = lib.platforms.unix;
+  };
+}
diff --git a/pkgs/development/perl-modules/Alien-FFI-dont-download.patch b/pkgs/development/perl-modules/Alien-FFI-dont-download.patch
new file mode 100644
index 000000000000..ba79c8c0eb28
--- /dev/null
+++ b/pkgs/development/perl-modules/Alien-FFI-dont-download.patch
@@ -0,0 +1,29 @@
+diff --git a/alienfile b/alienfile
+index 18d6b42..5ccf296 100644
+--- a/alienfile
++++ b/alienfile
+@@ -11,12 +11,6 @@ plugin 'PkgConfig'    => 'libffi';
+ 
+ share {
+ 
+-  plugin 'Download::GitHub' => (
+-    github_user => 'libffi',
+-    github_repo => 'libffi',
+-    asset => 1,
+-  );
+-
+   plugin 'Build::Autoconf' => ();
+ 
+   my $configure = '--disable-shared --enable-static --disable-builddir';
+diff --git a/t/00_diag.t b/t/00_diag.t
+index 51dd784..2bc314c 100644
+--- a/t/00_diag.t
++++ b/t/00_diag.t
+@@ -13,7 +13,6 @@ $modules{$_} = $_ for qw(
+   Alien::Base
+   Alien::Build
+   Alien::Build::MM
+-  Alien::Build::Plugin::Download::GitHub
+   Alien::Build::Plugin::Probe::Vcpkg
+   Capture::Tiny
+   ExtUtils::MakeMaker
diff --git a/pkgs/development/perl-modules/ArchiveLibarchive-set-findlib-path.patch b/pkgs/development/perl-modules/ArchiveLibarchive-set-findlib-path.patch
new file mode 100644
index 000000000000..47bc8ee9dc56
--- /dev/null
+++ b/pkgs/development/perl-modules/ArchiveLibarchive-set-findlib-path.patch
@@ -0,0 +1,25 @@
+diff --git a/lib/Archive/Libarchive/Lib.pm b/lib/Archive/Libarchive/Lib.pm
+index 3fcbcf4..214df7a 100644
+--- a/lib/Archive/Libarchive/Lib.pm
++++ b/lib/Archive/Libarchive/Lib.pm
+@@ -3,7 +3,7 @@ package Archive::Libarchive::Lib;
+ use strict;
+ use warnings;
+ use 5.020;
+-use FFI::CheckLib 0.30 qw( find_lib_or_die );
++use FFI::CheckLib qw( find_lib_or_die );
+ use Encode qw( decode );
+ use experimental qw( signatures );
+ 
+index 3fcbcf4..718caed 100644
+--- a/lib/Archive/Libarchive/Lib.pm
++++ b/lib/Archive/Libarchive/Lib.pm
+@@ -23,7 +23,7 @@ L<Archive::Libarchive>.
+ 
+ sub lib
+ {
+-  $ENV{ARCHIVE_LIBARCHIVE_LIB_DLL} // find_lib_or_die( lib => 'archive', symbol => ['archive_read_free','archive_write_free','archive_free'], alien => ['Alien::Libarchive3'] );
++  $ENV{ARCHIVE_LIBARCHIVE_LIB_DLL} // find_lib_or_die( lib => 'archive', symbol => ['archive_read_free','archive_write_free','archive_free'], libpath => '@@libarchive@@' );
+ }
+ 
+ sub ffi
diff --git a/pkgs/top-level/perl-packages.nix b/pkgs/top-level/perl-packages.nix
index 7c121240193b..14f168c6b25e 100644
--- a/pkgs/top-level/perl-packages.nix
+++ b/pkgs/top-level/perl-packages.nix
@@ -278,6 +278,25 @@ with self; {
     };
   };
 
+  AlienFFI = buildPerlPackage {
+    pname = "Alien-FFI";
+    version = "0.27";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Alien-FFI-0.27.tar.gz";
+      hash = "sha256-Kbsgg/P5gqOfSFIkP09qEZFpZvIObneGTpkmnRHotl4=";
+    };
+    patches = [ ../development/perl-modules/Alien-FFI-dont-download.patch ];
+    nativeBuildInputs = [ pkgs.pkg-config ];
+    buildInputs = [ pkgs.libffi CaptureTiny Test2Suite NetSSLeay MojoDOM58 IOSocketSSL ];
+    propagatedBuildInputs = [ AlienBuild ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Alien::FFI";
+      description = "Build and make available libffi";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   AlienGMP = buildPerlPackage {
     pname = "Alien-GMP";
     version = "1.16";
@@ -992,6 +1011,61 @@ with self; {
     };
   };
 
+  ArchiveLibarchive = buildPerlPackage {
+    pname = "Archive-Libarchive";
+    version = "0.08";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Archive-Libarchive-0.08.tar.gz";
+      hash = "sha256-6ONC1U/T1uXn4xYP4IjBOgpQM8/76JSBodJHHUNyAFk=";
+    };
+    patches = [ ../development/perl-modules/ArchiveLibarchive-set-findlib-path.patch ];
+    postPatch = ''
+      substituteInPlace lib/Archive/Libarchive/Lib.pm --replace "@@libarchive@@" "${pkgs.libarchive.lib}/lib"
+    '';
+    buildInputs = [ FFIC Filechdir PathTiny SubIdentify TermTable Test2Suite Test2ToolsMemoryCycle TestArchiveLibarchive TestScript ];
+    propagatedBuildInputs = [ FFICStat FFICheckLib FFIPlatypus FFIPlatypusTypeEnum FFIPlatypusTypePtrObject RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Archive::Libarchive";
+      description = "Modern Perl bindings to libarchive";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  ArchiveLibarchiveExtract = buildPerlPackage {
+    pname = "Archive-Libarchive-Extract";
+    version = "0.03";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Archive-Libarchive-Extract-0.03.tar.gz";
+      hash = "sha256-yXfAR0hnIX6zJvte5pA04e9spBQUkWHjEpAblf0SwIE=";
+    };
+    buildInputs = [ Test2Suite TestScript ];
+    propagatedBuildInputs = [ ArchiveLibarchive Filechdir PathTiny RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Archive::Libarchive::Extract";
+      description = "An archive extracting mechanism (using libarchive)";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  ArchiveLibarchivePeek = buildPerlPackage {
+    pname = "Archive-Libarchive-Peek";
+    version = "0.04";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Archive-Libarchive-Peek-0.04.tar.gz";
+      hash = "sha256-DYhJ4xG2RsozWz6gGodTtAIkK5XOgAo7zNXHCC4nJPo=";
+    };
+    buildInputs = [ Filechdir Test2Suite TestScript ];
+    propagatedBuildInputs = [ ArchiveLibarchive PathTiny RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Archive::Libarchive::Peek";
+      description = "Peek into archives without extracting them";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   ArrayCompare = buildPerlModule {
     pname = "Array-Compare";
     version = "3.0.7";
@@ -9187,6 +9261,23 @@ with self; {
     };
   };
 
+  FFIC = buildPerlPackage {
+    pname = "FFI-C";
+    version = "0.15";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/FFI-C-0.15.tar.gz";
+      hash = "sha256-63BgfmZzvMsY3yf0zuRZ+23EGODak+aSzcNVX+QNL04=";
+    };
+    buildInputs = [ CaptureTiny PathTiny Test2Suite ];
+    propagatedBuildInputs = [ ClassInspector FFIPlatypus FFIPlatypusTypeEnum RefUtil SubIdentify SubInstall ];
+    meta = {
+      homepage = "https://metacpan.org/pod/FFI::C";
+      description = "C data types for FFI";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   FFICheckLib = buildPerlPackage {
     pname = "FFI-CheckLib";
     version = "0.27";
@@ -9202,6 +9293,74 @@ with self; {
     };
   };
 
+  FFICStat = buildPerlPackage {
+    pname = "FFI-C-Stat";
+    version = "0.02";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/FFI-C-Stat-0.02.tar.gz";
+      hash = "sha256-ThXY9vn5hAfGUtnTE7URUHcTkgGOBx18GShDrILBvlk=";
+    };
+    buildInputs = [ Filechdir PathTiny Test2Suite TestScript ];
+    propagatedBuildInputs = [ FFIPlatypus RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/FFI::C::Stat";
+      description = "Object-oriented FFI interface to native stat and lstat";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  FFIPlatypus = buildPerlPackage {
+    pname = "FFI-Platypus";
+    version = "2.08";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/FFI-Platypus-2.08.tar.gz";
+      hash = "sha256-EbOrEU7ZY1YxzYWzjSKXhuFEv5Sjr5rAnD17s0M2uSQ=";
+    };
+    buildInputs = [ AlienFFI Test2Suite ];
+    propagatedBuildInputs = [ CaptureTiny FFICheckLib ];
+    meta = {
+      homepage = "https://pl.atypus.org";
+      description = "Write Perl bindings to non-Perl libraries with FFI. No XS required";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  FFIPlatypusTypePtrObject = buildPerlPackage {
+    pname = "FFI-Platypus-Type-PtrObject";
+    version = "0.03";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/FFI-Platypus-Type-PtrObject-0.03.tar.gz";
+      hash = "sha256-4elJB++QtANgqabAPSlaEwR9T2ybVqyvHfK1TRcwf3Q=";
+    };
+    buildInputs = [ Test2Suite Test2ToolsFFI ];
+    propagatedBuildInputs = [ FFIPlatypus RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/FFI::Platypus::Type::PtrObject";
+      description = "Platypus custom type for an object wrapped around an opaque pointer";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  FFIPlatypusTypeEnum = buildPerlPackage {
+    pname = "FFI-Platypus-Type-Enum";
+    version = "0.06";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/FFI-Platypus-Type-Enum-0.06.tar.gz";
+      hash = "sha256-yVSmBPfWkpYk+pQT2NDh2DtL2XfQVifKznPtU6lcd98=";
+    };
+    buildInputs = [ FFIPlatypus Test2Suite ];
+    propagatedBuildInputs = [ RefUtil ];
+    meta = {
+      homepage = "https://metacpan.org/pod/FFI::Platypus::Type::Enum";
+      description = "Custom platypus type for dealing with C enumerated types";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   FennecLite = buildPerlModule {
     pname = "Fennec-Lite";
     version = "0.004";
@@ -9810,6 +9969,21 @@ with self; {
     };
   };
 
+  FileShareDirDist = buildPerlPackage {
+    pname = "File-ShareDir-Dist";
+    version = "0.07";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/File-ShareDir-Dist-0.07.tar.gz";
+      hash = "sha256-jX/l0O4iNR9B75Wtwi29VsMf+iqbLBmEMA6S/36f6G0=";
+    };
+    meta = {
+      homepage = "https://metacpan.org/pod/File::ShareDir::Dist";
+      description = "Locate per-dist shared files";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   FileShareDirInstall = buildPerlPackage {
     pname = "File-ShareDir-Install";
     version = "0.13";
@@ -14099,6 +14273,21 @@ with self; {
     };
   };
 
+  LogfileRotate = buildPerlPackage {
+    pname = "Logfile-Rotate";
+    version = "1.04";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PA/PAULG/Logfile-Rotate-1.04.tar.gz";
+      hash = "sha256-gQ+LfM2GV9Ox71PNR1glR4Rc67WCArBVObNAhjjK2j4=";
+    };
+    meta = {
+      description = "Perl module to rotate logfiles";
+      homepage = "https://metacpan.org/dist/Logfile-Rotate";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   Logger = buildPerlPackage {
     pname = "Log-ger";
     version = "0.037";
@@ -15452,6 +15641,23 @@ with self; {
     };
   };
 
+  MinionBackendRedis = buildPerlModule {
+    pname = "Minion-Backend-Redis";
+    version = "0.003";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/D/DF/DFUG/Minion-Backend-Redis-0.003.tar.gz";
+      hash = "sha256-zXZRIQbfHKmQF75fObSmXgSCawzZQxe3GsAWGzXzI6A=";
+    };
+    buildInputs = [ ModuleBuildTiny ];
+    propagatedBuildInputs = [ Minion MojoRedis Mojolicious SortVersions ];
+    meta = {
+      homepage = "https://github.com/Difegue/Minion-Backend-Redis";
+      description = "Redis backend for Minion job queue";
+      license = with lib.licenses; [ artistic2 ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   MinionBackendSQLite = buildPerlModule {
     pname = "Minion-Backend-SQLite";
     version = "5.0.6";
@@ -16258,6 +16464,22 @@ with self; {
     };
   };
 
+  MojoliciousPluginRenderFile = buildPerlPackage {
+    pname = "Mojolicious-Plugin-RenderFile";
+    version = "0.12";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/K/KO/KOORCHIK/Mojolicious-Plugin-RenderFile-0.12.tar.gz";
+      hash = "sha256-AT5CoswGvHBBuxPJ3ziK8kAQ5peTqN8PCrHSQKphFz8=";
+    };
+    propagatedBuildInputs = [ Mojolicious ];
+    meta = {
+      description = "\"render_file\" helper for Mojolicious";
+      homepage = "https://github.com/koorchik/Mojolicious-Plugin-RenderFile";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   MojoliciousPluginStatus = buildPerlPackage {
     pname = "Mojolicious-Plugin-Status";
     version = "1.17";
@@ -16290,6 +16512,23 @@ with self; {
     };
   };
 
+  MojoliciousPluginTemplateToolkit = buildPerlModule {
+    pname = "Mojolicious-Plugin-TemplateToolkit";
+    version = "0.006";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/D/DB/DBOOK/Mojolicious-Plugin-TemplateToolkit-0.006.tar.gz";
+      hash = "sha256-dBoFAmtTArtrKc+I3KICC3rv0iNHgWELpZNaqPCXNKY=";
+    };
+    buildInputs = [ ModuleBuildTiny ];
+    propagatedBuildInputs = [ ClassMethodModifiers Mojolicious TemplateToolkit ];
+    meta = {
+      homepage = "https://github.com/Grinnz/Mojolicious-Plugin-TemplateToolkit";
+      description = "Template Toolkit renderer plugin for Mojolicious";
+      license = with lib.licenses; [ artistic2 ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   MojoliciousPluginTextExceptions = buildPerlPackage {
     pname = "Mojolicious-Plugin-TextExceptions";
     version = "0.02";
@@ -16856,6 +17095,22 @@ with self; {
     };
   };
 
+  TestArchiveLibarchive = buildPerlPackage {
+    pname = "Test-Archive-Libarchive";
+    version = "0.02";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Test-Archive-Libarchive-0.02.tar.gz";
+      hash = "sha256-KxkYZx4F2i2dIiwQx9kXWFpiQYb+r7j4SQhZnDRwJ1E=";
+    };
+    propagatedBuildInputs = [ RefUtil Test2Suite ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Test::Archive::Libarchive";
+      description = "Testing tools for Archive::Libarchive";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   TestPostgreSQL = buildPerlModule {
     pname = "Test-PostgreSQL";
     version = "1.29";
@@ -17783,6 +18038,20 @@ with self; {
     };
   };
 
+  NetDNSNative = buildPerlPackage {
+    pname = "Net-DNS-Native";
+    version = "0.22";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/O/OL/OLEG/Net-DNS-Native-0.22.tar.gz";
+      hash = "sha256-EI2d7bq5/69qDQFSVSbeGJSITpUL/YM3F+XNOJBcMNU=";
+    };
+    meta = {
+      description = "Non-blocking system DNS resolver";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   NetIdent = buildPerlPackage {
     pname = "Net-Ident";
     version = "1.25";
@@ -19225,6 +19494,22 @@ with self; {
     };
   };
 
+  ParallelLoops = buildPerlPackage {
+    pname = "Parallel-Loops";
+    version = "0.10";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PM/PMORCH/Parallel-Loops-0.10.tar.gz";
+      hash = "sha256-b5Z7RuejY7FocbmZHDWeFC3Dsigc/psa85kEcEyL0qo=";
+    };
+    propagatedBuildInputs = [ ParallelForkManager ];
+    meta = {
+      description = "Execute loops using parallel forked subprocesses";
+      homepage = "https://github.com/pmorch/perl-Parallel-Loops";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   ParallelPipes = buildPerlModule {
     pname = "Parallel-Pipes";
     version = "0.102";
@@ -23128,6 +23413,21 @@ with self; {
     };
   };
 
+  SysCpuAffinity = buildPerlModule {
+    pname = "Sys-CpuAffinity";
+    version = "1.12";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/M/MO/MOB/Sys-CpuAffinity-1.12.tar.gz";
+      hash = "sha256-/jLAXz6wWXCMZH8ruFslBFhZHyupBR2Nhm9Uajh+6Eg=";
+    };
+    doCheck = false; # Would run checks for all supported systems
+    meta = {
+      description = "Set CPU affinity for processes";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   SysHostnameLong = buildPerlPackage {
     pname = "Sys-Hostname-Long";
     version = "1.5";
@@ -23848,6 +24148,40 @@ with self; {
     };
   };
 
+  Test2ToolsFFI = buildPerlPackage {
+    pname = "Test2-Tools-FFI";
+    version = "0.06";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Test2-Tools-FFI-0.06.tar.gz";
+      hash = "sha256-MA28QKEubG+7y7lv05uQK+bZZXJtrx5qtzuKCv0lLy8=";
+    };
+    buildInputs = [ FileShareDirInstall Test2Suite ];
+    propagatedBuildInputs = [ CaptureTiny FFICheckLib FFIPlatypus FileShareDirDist ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Test2::Tools::FFI";
+      description = "Tools for testing FFI";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
+  Test2ToolsMemoryCycle = buildPerlPackage {
+    pname = "Test2-Tools-MemoryCycle";
+    version = "0.01";
+    src = fetchurl {
+      url = "mirror://cpan/authors/id/P/PL/PLICEASE/Test2-Tools-MemoryCycle-0.01.tar.gz";
+      hash = "sha256-U1s9ylQqMyUVEq3ktafb6+PESNg/iA0ZjkPcEnl5aYs=";
+    };
+    buildInputs = [ Test2Suite ];
+    propagatedBuildInputs = [ DevelCycle PadWalker ];
+    meta = {
+      homepage = "https://metacpan.org/pod/Test2::Tools::MemoryCycle";
+      description = "Check for memory leaks and circular memory references";
+      license = with lib.licenses; [ artistic1 gpl1Plus ];
+      maintainers = with maintainers; [ tomasajt ];
+    };
+  };
+
   TestAbortable = buildPerlPackage {
     pname = "Test-Abortable";
     version = "0.002";