From a428a525d3886cf120b62c4b398eab0bde1ec95c Mon Sep 17 00:00:00 2001 From: Leona Maroni Date: Fri, 10 Apr 2026 08:58:40 +0200 Subject: [PATCH 1/4] vinyl-cache_9: init at 9.0.0 This package is fairly similar to the varnish packages, but has no modules packages because the "varnish-modules" projects expects a varnish, not a vinyl cache. --- pkgs/servers/vinyl-cache/default.nix | 132 +++++++++++++++++++++++++++ pkgs/top-level/all-packages.nix | 6 ++ 2 files changed, 138 insertions(+) create mode 100644 pkgs/servers/vinyl-cache/default.nix diff --git a/pkgs/servers/vinyl-cache/default.nix b/pkgs/servers/vinyl-cache/default.nix new file mode 100644 index 000000000000..03aab5a100f3 --- /dev/null +++ b/pkgs/servers/vinyl-cache/default.nix @@ -0,0 +1,132 @@ +{ + lib, + stdenv, + fetchurl, + buildPackages, + pcre2, + jemalloc, + libunwind, + libxslt, + groff, + ncurses, + pkg-config, + readline, + libedit, + coreutils, + python3, + makeWrapper, + nixosTests, +}: + +let + generic = + { + version, + hash, + }: + stdenv.mkDerivation (finalAttrs: { + pname = "vinyl-cache"; + inherit version; + + src = fetchurl { + url = "https://vinyl-cache.org/downloads/${finalAttrs.pname}-${version}.tgz"; + inherit hash; + }; + + __structuredAttrs = true; + strictDeps = true; + + nativeBuildInputs = [ + pkg-config + python3.pkgs.docutils + python3.pkgs.sphinx + makeWrapper + ]; + + buildInputs = [ + libxslt + groff + ncurses + readline + libedit + pcre2 + python3 + ] + ++ lib.optional stdenv.hostPlatform.isDarwin libunwind + ++ lib.optional stdenv.hostPlatform.isLinux jemalloc; + + configureFlags = [ + # the checks behind those to not work when doing cross but for simplicity we always define them + "ac_cv_have_tcp_fastopen=yes" + "ac_cv_have_tcp_keep=yes" + "ac_cv_have_working_close_range=yes" + "PYTHON=${buildPackages.python3.interpreter}" + ]; + + buildFlags = [ "localstatedir=/var/run" ]; + + postPatch = '' + substituteInPlace bin/vinyltest/vtest2/src/vtc_main.c --replace-fail /bin/rm "${coreutils}/bin/rm" + ''; + + postConfigure = lib.optionalString (!stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + # prevent cache invalidation + substituteInPlace bin/vinyld/Makefile \ + --replace-fail "vhp_hufdec.h: vhp_gen_hufdec" "vhp_hufdec.h:" + + ln -s "${buildPackages.vinyl.vhp_hufdec_h}" bin/vinyld/vhp_hufdec.h + + substituteInPlace bin/vinylstat/Makefile \ + --replace-fail "vinylstat_curses_help.c: vinylstat_help_gen" "vinylstat_curses_help.c:" \ + --replace-fail "./vinylstat_help_gen" "${buildPackages.vinyl}/bin/vinylstat_help_gen" + + # the docs execute lots of commands to gather options and flags + substituteInPlace doc/Makefile \ + --replace-fail "SUBDIRS = graphviz sphinx" "SUBDIRS = graphviz" + substituteInPlace Makefile \ + --replace-fail "include lib bin vmod etc doc man contrib" "include lib bin vmod etc doc contrib" + ''; + + postInstall = '' + wrapProgram "$out/sbin/vinyld" --prefix PATH : "${lib.makeBinPath [ stdenv.cc ]}" + '' + + lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + cp bin/vinyld/vhp_hufdec.h $vhp_hufdec_h + ''; + + # https://github.com/varnishcache/varnish-cache/issues/1875 + env.NIX_CFLAGS_COMPILE = lib.optionalString stdenv.hostPlatform.isi686 "-fexcess-precision=standard"; + + outputs = [ + "out" + "dev" + ] + ++ lib.optionals (stdenv.buildPlatform.canExecute stdenv.hostPlatform) [ + "man" + "vhp_hufdec_h" # only used for cross compilation + ]; + + passthru = { + python = python3; + }; + + meta = { + description = "Web application accelerator also known as a caching HTTP reverse proxy"; + homepage = "https://vinyl-cache.org"; + license = lib.licenses.bsd2; + maintainers = [ + lib.maintainers.leona + lib.maintainers.osnyx + ]; + platforms = lib.platforms.unix; + broken = stdenv.hostPlatform.isDarwin; + }; + }); +in +{ + # EOL 2027-03-16 + vinyl-cache_9 = generic { + version = "9.0.0"; + hash = "sha256-l0DCEMKndifVD9CtbHWdw8oJT8tOZVW6VfynHPSDvRc="; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 0a5b07f5deb6..7acac5432b43 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -3557,6 +3557,12 @@ with pkgs; varnishPackages = varnish80Packages; varnish = varnishPackages.varnish; + inherit (callPackages ../servers/vinyl-cache { }) + vinyl-cache_9 + ; + + vinyl-cache = vinyl-cache_9; + vncdo = with python3Packages; toPythonApplication vncdo; # An alias to work around the splicing incidents From d7de32990ced51752ec8c60ffe8835c490244b92 Mon Sep 17 00:00:00 2001 From: Leona Maroni Date: Fri, 10 Apr 2026 08:58:40 +0200 Subject: [PATCH 2/4] nixos/vinyl-cache: init module This module is essentialy the same as services.varnish, but adapts to the changed project name. For the lifetime of NixOS 26.05, we still support Varnish to simplify the migration. --- nixos/modules/module-list.nix | 1 + .../web-servers/vinyl-cache/default.nix | 240 ++++++++++++++++++ nixos/tests/all-tests.nix | 4 + nixos/tests/vinyl-cache.nix | 126 +++++++++ pkgs/servers/vinyl-cache/default.nix | 1 + 5 files changed, 372 insertions(+) create mode 100644 nixos/modules/services/web-servers/vinyl-cache/default.nix create mode 100644 nixos/tests/vinyl-cache.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index b23517fcce16..c77cca562a6c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1834,6 +1834,7 @@ ./services/web-servers/unit/default.nix ./services/web-servers/uwsgi.nix ./services/web-servers/varnish/default.nix + ./services/web-servers/vinyl-cache/default.nix ./services/x11/clight.nix ./services/x11/colord.nix ./services/x11/desktop-managers/default.nix diff --git a/nixos/modules/services/web-servers/vinyl-cache/default.nix b/nixos/modules/services/web-servers/vinyl-cache/default.nix new file mode 100644 index 000000000000..ba498537edc8 --- /dev/null +++ b/nixos/modules/services/web-servers/vinyl-cache/default.nix @@ -0,0 +1,240 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) + types + mkOption + hasPrefix + concatMapStringsSep + optionalString + concatMap + ; + + cfg = config.services.vinyl-cache; + + # Vinyl Cache has very strong opinions and very complicated code around handling + # the stateDir. After a lot of back and forth, we decided that we a) + # do not want a configurable option here, as most of the handling depends + # on the version and the compile time options. Putting everything into + # /var/run (RAM backed) is absolutely recommended by Vinyl Cache anyways. + # We do need to pay attention to the version-dependend variations, though! + stateDir = "/var/run/vinyld"; + + # from --help: + # -a [=]address[:port][,proto] # HTTP listen address and port + # [,user=][,group=] # Can be specified multiple times. + # [,mode=] # default: ":80,HTTP" + # # Proto can be "PROXY" or "HTTP" (default) + # # user, group and mode set permissions for + # # a Unix domain socket. + commandLineAddresses = ( + concatMapStringsSep " " ( + a: + "-a " + + optionalString (!isNull a.name) "${a.name}=" + + a.address + + optionalString (!isNull a.port) ":${toString a.port}" + + optionalString (!isNull a.proto) ",${a.proto}" + + optionalString (!isNull a.user) ",user=${a.user}" + + optionalString (!isNull a.group) ",group=${a.group}" + + optionalString (!isNull a.mode) ",mode=${a.mode}" + ) cfg.listen + ); + + addressSubmodule = types.submodule { + options = { + name = mkOption { + description = "Name is referenced in logs. If name is not specified, 'a0', 'a1', etc. is used."; + default = null; + type = with types; nullOr str; + }; + address = mkOption { + description = '' + If given an IP address, it can be a host name ("localhost"), an IPv4 dotted-quad + ("127.0.0.1") or an IPv6 address enclosed in square brackets ("[::1]"). + + (VCL4.1 and higher) If given an absolute Path ("/path/to/listen.sock") or "@" + followed by the name of an abstract socket ("@myvinyld") accept connections + on a Unix domain socket. + + The user, group and mode sub-arguments may be used to specify the permissions + of the socket file. These sub-arguments do not apply to abstract sockets. + ''; + type = types.str; + }; + port = mkOption { + description = "The port to use for IP sockets. If port is not specified, port 80 (http) is used."; + default = null; + type = with types; nullOr port; + }; + proto = mkOption { + description = "PROTO can be 'HTTP' (the default) or 'PROXY'. Both version 1 and 2 of the proxy protocol can be used."; + type = types.enum [ + "HTTP" + "PROXY" + ]; + default = "HTTP"; + }; + user = mkOption { + description = "User name who owns the socket file."; + default = null; + type = with lib.types; nullOr str; + }; + group = mkOption { + description = "Group name who owns the socket file."; + default = null; + type = with lib.types; nullOr str; + }; + mode = mkOption { + description = "Permission of the socket file (3-digit octal value)."; + default = null; + type = with types; nullOr str; + }; + }; + }; + checkedAddressModule = types.addCheck addressSubmodule ( + m: + ( + if ((hasPrefix "@" m.address) || (hasPrefix "/" m.address)) then + # this is a unix socket + (m.port != null) + else + # this is not a path-based unix socket + if !(hasPrefix "/" m.address) && (m.group != null) || (m.user != null) || (m.mode != null) then + false + else + true + ) + ); + commandLine = + "-f ${pkgs.writeText "default.vcl" cfg.config}" + + + lib.optionalString (cfg.extraModules != [ ]) + " -p vmod_path='${ + lib.makeSearchPathOutput "lib" "lib/vinyl/vmods" ([ cfg.package ] ++ cfg.extraModules) + }' -r vmod_path"; +in +{ + meta.maintainers = [ + lib.maintainers.leona + lib.maintainers.osnyx + ]; + options = { + services.vinyl-cache = { + enable = lib.mkEnableOption "Vinyl Cache"; + + enableConfigCheck = lib.mkEnableOption "checking the config during build time" // { + default = true; + }; + + package = lib.mkPackageOption pkgs "vinyl-cache" { }; + + listen = lib.mkOption { + description = "Accept for client requests on the specified listen addresses."; + type = lib.types.listOf checkedAddressModule; + defaultText = lib.literalExpression ''[ { address="*"; port=6081; } ]''; + default = [ + { + address = "*"; + port = 6081; + } + ]; + }; + + config = lib.mkOption { + type = lib.types.lines; + description = '' + Verbatim default.vcl configuration. + ''; + }; + + extraModules = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + description = '' + Vinyl Cache modules (except 'std'). + ''; + }; + + extraCommandLine = lib.mkOption { + type = lib.types.str; + default = ""; + example = "-s malloc,256M"; + description = '' + Command line switches for vinyld (run 'vinyld -?' to get list of options) + ''; + }; + + enableFileLogging = lib.mkEnableOption "file based logging"; + }; + + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + systemd.services.vinyl-cache = { + description = "Vinyl Cache"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${cfg.package}/bin/vinyld ${commandLineAddresses} -n ${stateDir} -F ${cfg.extraCommandLine} ${commandLine}"; + Restart = "always"; + RestartSec = "5s"; + User = "vinyl-cache"; + Group = "vinyl-cache"; + DynamicUser = true; + RuntimeDirectory = lib.removePrefix "/var/run/" stateDir; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + NoNewPrivileges = true; + LimitNOFILE = 131072; + }; + }; + + environment.systemPackages = [ cfg.package ]; + + # check .vcl syntax at compile time (e.g. before nixops deployment) + system.checks = lib.mkIf cfg.enableConfigCheck [ + (pkgs.runCommand "check-vinyl-cache-syntax" { } '' + ${cfg.package}/bin/vinyld -C ${commandLine} 2> $out || (cat $out; exit 1) + '') + ]; + + (lib.mkIf (cfg.enable && cfg.enableFileLogging) { + systemd.services = { + vinylncsa = { + after = [ "vinyl-cache.service" ]; + requires = [ "vinyl-cache.service" ]; + description = "Vinyl Cache logging daemon"; + wantedBy = [ "multi-user.target" ]; + # We want to reopen logs with HUP. vinylncsa must run in daemon mode for that. + serviceConfig = { + Type = "forking"; + Restart = "always"; + RuntimeDirectory = "vinylncsa"; + LogsDirectory = "vinyl-cache"; + PIDFile = "/run/vinylncsa/vinylncsa.pid"; + User = "vinyl-cache"; + Group = "vinyl-cache"; + ExecStart = "${cfg.package}/bin/vinylncsa -D -a -w /var/log/vinyl-cache/vinyl-cache.log -P /run/vinylncsa/vinylncsa.pid"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + }; + }; + + services.logrotate.settings.vinyl-cache = lib.mapAttrs (_: lib.mkDefault) { + files = [ "/var/log/vinyl-cache/*.log" ]; + frequency = "daily"; + rotate = 14; + compress = true; + delaycompress = true; + postrotate = "systemctl reload vinylncsa"; + }; + }) + ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 929e8362e8f3..68ece27faa63 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1743,6 +1743,10 @@ in victoriametrics = import ./victoriametrics { inherit runTest; }; victoriatraces = import ./victoriatraces { inherit runTest; }; vikunja = runTest ./vikunja.nix; + vinyl-cache_9 = runTest { + imports = [ ./vinyl-cache.nix ]; + _module.args.package = pkgs.vinyl-cache_9; + }; virtualbox = handleTestOn [ "x86_64-linux" ] ./virtualbox.nix { }; vm-variant = handleTest ./vm-variant.nix { }; vscode-remote-ssh = handleTestOn [ "x86_64-linux" ] ./vscode-remote-ssh.nix { }; diff --git a/nixos/tests/vinyl-cache.nix b/nixos/tests/vinyl-cache.nix new file mode 100644 index 000000000000..aec9415b333f --- /dev/null +++ b/nixos/tests/vinyl-cache.nix @@ -0,0 +1,126 @@ +{ + pkgs, + package, + lib, + ... +}: +let + testPath = pkgs.hello; +in +{ + name = "vinyl"; + meta = { + maintainers = [ + lib.maintainers.leona + lib.maintainers.osnyx + ]; + }; + + nodes = { + vinyl = + { + config, + pkgs, + lib, + ... + }: + { + services.nix-serve = { + enable = true; + }; + + services.vinyl-cache = { + inherit package; + enable = true; + enableFileLogging = true; + listen = [ + { + address = "0.0.0.0"; + port = 80; + proto = "HTTP"; + } + { + name = "proxyport"; + address = "0.0.0.0"; + port = 8080; + proto = "PROXY"; + } + { + address = "/var/run/vinyld/client.http.sock"; + user = "vinyl-cache"; + group = "vinyl-cache"; + mode = "660"; + } + ] + ++ lib.optionals (lib.versionAtLeast package.version "7.3") [ + # Support added in 7.3.0 + { address = "@asdf"; } + ]; + config = '' + vcl 4.1; + + backend nix-serve { + .host = "127.0.0.1"; + .port = "${toString config.services.nix-serve.port}"; + } + ''; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + system.extraDependencies = [ testPath ]; + + assertions = + let + cmdline = config.systemd.services.vinyl-cache.serviceConfig.ExecStart; + in + map + (pattern: { + assertion = lib.hasInfix pattern cmdline; + message = "Address argument `${pattern}` missing in commandline `${cmdline}`."; + }) + ( + [ + " -a 0.0.0.0:80,HTTP " + " -a proxyport=0.0.0.0:8080,PROXY " + " -a /var/run/vinyld/client.http.sock,HTTP,user=vinyl-cache,group=vinyl-cache,mode=660 " + ] + ++ lib.optionals (lib.versionAtLeast package.version "7.3") [ + " -a @asdf,HTTP " + ] + ); + }; + + client = + { lib, ... }: + { + nix.settings = { + require-sigs = false; + substituters = lib.mkForce [ "http://vinyl" ]; + }; + }; + }; + + testScript = '' + from pathlib import Path + import os + + start_all() + vinyl.wait_for_open_port(80) + vinyl.wait_for_unit("vinylncsa") + + client.wait_until_succeeds("curl -f http://vinyl/nix-cache-info"); + + client.wait_until_succeeds("nix-store -r ${testPath}") + client.succeed("${testPath}/bin/hello") + + output = vinyl.succeed("vinyladm status") + print(output) + assert "Child in state running" in output, "Unexpected vinyladm response" + + vinyl.copy_from_machine("/var/log/vinyl-cache/vinyl-cache.log") + + out_dir = os.environ.get("out", os.getcwd()) + vinyl_log = (Path(out_dir) / "vinyl-cache.log").read_text() + assert "http://vinyl/nix-cache-info" in vinyl_log + ''; +} diff --git a/pkgs/servers/vinyl-cache/default.nix b/pkgs/servers/vinyl-cache/default.nix index 03aab5a100f3..11f4438ea787 100644 --- a/pkgs/servers/vinyl-cache/default.nix +++ b/pkgs/servers/vinyl-cache/default.nix @@ -108,6 +108,7 @@ let passthru = { python = python3; + tests = nixosTests."vinyl-cache_${lib.versions.major version}"; }; meta = { From 21318349b989af918031c0a41c7385792ae40996 Mon Sep 17 00:00:00 2001 From: Leona Maroni Date: Fri, 10 Apr 2026 08:58:40 +0200 Subject: [PATCH 3/4] nixos/{varnish,vinyl-cache}: add assertions to prevent users using wrong module Varnish Cache and Vinyl Cache are similar, so it could happen that users use the wrong module. This change adds assertions to both modules that only the supported packages can be used with it. --- .../services/web-servers/varnish/default.nix | 27 ++++++++++++------- .../web-servers/vinyl-cache/default.nix | 18 +++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix index 0f1217fa9b35..3d6a41498a70 100644 --- a/nixos/modules/services/web-servers/varnish/default.nix +++ b/nixos/modules/services/web-servers/varnish/default.nix @@ -223,16 +223,23 @@ in '') ]; - assertions = concatMap (m: [ - { - assertion = (hasPrefix "/" m.address) || (hasPrefix "@" m.address) -> m.port == null; - message = "Listen ports must not be specified with UNIX sockets: ${builtins.toJSON m}"; - } - { - assertion = !(hasPrefix "/" m.address) -> m.user == null && m.group == null && m.mode == null; - message = "Abstract UNIX sockets or IP sockets can not be used with user, group, and mode settings: ${builtins.toJSON m}"; - } - ]) cfg.listen; + assertions = + (concatMap (m: [ + { + assertion = (hasPrefix "/" m.address) || (hasPrefix "@" m.address) -> m.port == null; + message = "Listen ports must not be specified with UNIX sockets: ${builtins.toJSON m}"; + } + { + assertion = !(hasPrefix "/" m.address) -> m.user == null && m.group == null && m.mode == null; + message = "Abstract UNIX sockets or IP sockets can not be used with user, group, and mode settings: ${builtins.toJSON m}"; + } + ]) cfg.listen) + ++ [ + { + assertion = cfg.package.pname != "vinyl-cache"; + message = "services.varnish only supports Varnish Cache. Please use services.vinyl-cache."; + } + ]; warnings = lib.optional (!isNull cfg.http_address) diff --git a/nixos/modules/services/web-servers/vinyl-cache/default.nix b/nixos/modules/services/web-servers/vinyl-cache/default.nix index ba498537edc8..5efc681247e2 100644 --- a/nixos/modules/services/web-servers/vinyl-cache/default.nix +++ b/nixos/modules/services/web-servers/vinyl-cache/default.nix @@ -205,6 +205,24 @@ in '') ]; + assertions = + concatMap (m: [ + { + assertion = (hasPrefix "/" m.address) || (hasPrefix "@" m.address) -> m.port == null; + message = "Listen ports must not be specified with UNIX sockets: ${builtins.toJSON m}"; + } + { + assertion = !(hasPrefix "/" m.address) -> m.user == null && m.group == null && m.mode == null; + message = "Abstract UNIX sockets or IP sockets can not be used with user, group, and mode settings: ${builtins.toJSON m}"; + } + ]) cfg.listen + ++ [ + { + assertion = cfg.package.pname == "vinyl-cache"; + message = "services.vinyl-cache only supports Vinyl Cache. Please use services.varnish."; + } + ]; + }) (lib.mkIf (cfg.enable && cfg.enableFileLogging) { systemd.services = { vinylncsa = { From 27a4cf14c7d8a338fbab84dd06989caecc579977 Mon Sep 17 00:00:00 2001 From: Leona Maroni Date: Fri, 10 Apr 2026 08:58:40 +0200 Subject: [PATCH 4/4] doc: add release notes for vinyl cache 9 --- doc/release-notes/rl-2605.section.md | 7 +++++++ nixos/doc/manual/release-notes/rl-2605.section.md | 2 ++ 2 files changed, 9 insertions(+) diff --git a/doc/release-notes/rl-2605.section.md b/doc/release-notes/rl-2605.section.md index c59d72f7da45..2eb1c598e665 100644 --- a/doc/release-notes/rl-2605.section.md +++ b/doc/release-notes/rl-2605.section.md @@ -132,6 +132,13 @@ - Varnish Cache has been updated to major version 8, `varnish` now refers to `varnish80`. That release contains breaking changes, see [Upgrading to Varnish-Cache 8.0](https://vinyl-cache.org/docs/8.0/whats-new/upgrading-8.0.html). Note that the Varnish 6 LTS release remains available as `varnish60`. + The Varnish Cache open-source project renamed itself to Vinyl Cache. Please migrate to `vinyl-cache`. See the `vinyl-cache` release notes entry for more information. + Varnish 8 is not supported for the entire NixOS 26.05 release cycle. + +- Vinyl Cache has been introduced with the major version 9 as the Varnish Cache open source project renamed itself to Vinyl Cache. Please migrate to Vinyl Cache 9 when you still use Varnish Cache. + A new module has also been introduced for this migration: `services.vinyl-cache`. + This release contains breaking changes, see [Upgrading to Vinyl Cache 9.0](https://vinyl-cache.org/docs/9.0/whats-new/upgrading-9.0.html). + The `varnish-modules` project is currently not packaged for Vinyl Cache, as it is incompatible. - `eslint` has been updated from version 9 to version 10. Please see https://eslint.org/blog/2026/02/eslint-v10.0.0-released/ for details about the breaking changes included in the update. diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 3c17c6f3d764..56f7f703610f 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -62,6 +62,8 @@ - [reaction](https://reaction.ppom.me/), a daemon that scans program outputs for repeated patterns, and takes action. A common usage is to scan ssh and webserver logs, and to ban hosts that cause multiple authentication errors. A modern alternative to fail2ban. Available as [services.reaction](#opt-services.reaction.enable). +- [vinyl-cache] as the Varnish Cache project renamed itself. Available as [services.vinyl-cache](#opt-services.vinyl-cache.enable). To aid the migration, the old `services.varnish` module is still available. + - [papra](https://papra.app/), an open-source document management platform designed to help you organize, secure, and archive your files effortlessly. Available as [services.papra](#opt-services.papra.enable). - [rqbit](https://github.com/ikatson/rqbit), a bittorrent client written in Rust. It has HTTP API and Web UI, and can be used as a library. Available as [services.rqbit](#opt-services.rqbit.enable).