diff --git a/doc/release-notes/rl-2605.section.md b/doc/release-notes/rl-2605.section.md index d4394cdbe944..38e9e28f52e7 100644 --- a/doc/release-notes/rl-2605.section.md +++ b/doc/release-notes/rl-2605.section.md @@ -145,6 +145,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 01f0994176a2..ed4559794686 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -80,6 +80,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). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index ac76163c2028..3545ca080531 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1841,6 +1841,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/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 new file mode 100644 index 000000000000..5efc681247e2 --- /dev/null +++ b/nixos/modules/services/web-servers/vinyl-cache/default.nix @@ -0,0 +1,258 @@ +{ + 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) + '') + ]; + + 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 = { + 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 d167ec0c90a5..783098ea4b2a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1753,6 +1753,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 new file mode 100644 index 000000000000..11f4438ea787 --- /dev/null +++ b/pkgs/servers/vinyl-cache/default.nix @@ -0,0 +1,133 @@ +{ + 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; + tests = nixosTests."vinyl-cache_${lib.versions.major version}"; + }; + + 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 2b4572fecd56..15dbbc8be801 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -3520,6 +3520,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