diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 96cc4301851d..38168b312e41 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -18,6 +18,8 @@ ## New Services {#sec-release-24.11-new-services} +- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service. + - [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI for LLMs. Available as [services.open-webui](#opt-services.open-webui.enable) service. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index de15aa8c4be6..14ff9fd8b68f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -732,6 +732,7 @@ ./services/misc/etesync-dav.nix ./services/misc/evdevremapkeys.nix ./services/misc/felix.nix + ./services/misc/flaresolverr.nix ./services/misc/forgejo.nix ./services/misc/freeswitch.nix ./services/misc/fstrim.nix diff --git a/nixos/modules/services/misc/flaresolverr.nix b/nixos/modules/services/misc/flaresolverr.nix new file mode 100644 index 000000000000..7967580307f9 --- /dev/null +++ b/nixos/modules/services/misc/flaresolverr.nix @@ -0,0 +1,58 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.services.flaresolverr; +in +{ + options = { + services.flaresolverr = { + enable = lib.mkEnableOption "FlareSolverr, a proxy server to bypass Cloudflare protection"; + + package = lib.mkPackageOption pkgs "flaresolverr" { }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Open the port in the firewall for FlareSolverr."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8191; + description = "The port on which FlareSolverr will listen for incoming HTTP traffic."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.flaresolverr = { + description = "FlareSolverr"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + HOME = "/run/flaresolverr"; + PORT = toString cfg.port; + }; + + serviceConfig = { + SyslogIdentifier = "flaresolverr"; + Restart = "always"; + RestartSec = 5; + Type = "simple"; + DynamicUser = true; + RuntimeDirectory = "flaresolverr"; + WorkingDirectory = "/run/flaresolverr"; + ExecStart = lib.getExe cfg.package; + TimeoutStopSec = 30; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 718aec882b4c..0852f2350e1d 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -332,6 +332,7 @@ in { firewall-nftables = handleTest ./firewall.nix { nftables = true; }; fish = handleTest ./fish.nix {}; flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {}; + flaresolverr = handleTest ./flaresolverr.nix {}; flood = handleTest ./flood.nix {}; floorp = handleTest ./firefox.nix { firefoxPackage = pkgs.floorp; }; fluentd = handleTest ./fluentd.nix {}; diff --git a/nixos/tests/flaresolverr.nix b/nixos/tests/flaresolverr.nix new file mode 100644 index 000000000000..0cec7adf6d6b --- /dev/null +++ b/nixos/tests/flaresolverr.nix @@ -0,0 +1,22 @@ +import ./make-test-python.nix ( + { lib, ... }: + { + name = "flaresolverr"; + meta.maintainers = with lib.maintainers; [ paveloom ]; + + nodes.machine = + { pkgs, ... }: + { + services.flaresolverr = { + enable = true; + port = 8888; + }; + }; + + testScript = '' + machine.wait_for_unit("flaresolverr.service") + machine.wait_for_open_port(8888) + machine.succeed("curl --fail http://localhost:8888/") + ''; + } +) diff --git a/pkgs/by-name/fl/flaresolverr/package.nix b/pkgs/by-name/fl/flaresolverr/package.nix new file mode 100644 index 000000000000..07e02231997c --- /dev/null +++ b/pkgs/by-name/fl/flaresolverr/package.nix @@ -0,0 +1,82 @@ +{ + lib, + stdenv, + fetchFromGitHub, + + makeWrapper, + + chromium, + python3, + undetected-chromedriver, + xorg, + + nixosTests, +}: + +let + python = python3.withPackages ( + ps: with ps; [ + bottle + func-timeout + prometheus-client + selenium + waitress + xvfbwrapper + + # For `undetected_chromedriver` + looseversion + requests + websockets + ] + ); +in +stdenv.mkDerivation (finalAttrs: { + pname = "flaresolverr"; + version = "3.3.21"; + + src = fetchFromGitHub { + owner = "FlareSolverr"; + repo = "FlareSolverr"; + rev = "v${finalAttrs.version}"; + hash = "sha256-M/snpYKZK3pgzlhYjRYEiAPlK9DUKYRiiu43KcrAy9g="; + }; + + nativeBuildInputs = [ makeWrapper ]; + + postPatch = '' + substituteInPlace src/undetected_chromedriver/patcher.py \ + --replace-fail \ + "from distutils.version import LooseVersion" \ + "from looseversion import LooseVersion" + + substituteInPlace src/utils.py \ + --replace-fail \ + 'CHROME_EXE_PATH = None' \ + 'CHROME_EXE_PATH = "${lib.getExe chromium}"' \ + --replace-fail \ + 'PATCHED_DRIVER_PATH = None' \ + 'PATCHED_DRIVER_PATH = "${lib.getExe undetected-chromedriver}"' + ''; + + installPhase = '' + mkdir -p $out/{bin,share/${finalAttrs.pname}-${finalAttrs.version}} + cp -r * $out/share/${finalAttrs.pname}-${finalAttrs.version}/. + + makeWrapper ${python}/bin/python $out/bin/flaresolverr \ + --add-flags "$out/share/${finalAttrs.pname}-${finalAttrs.version}/src/flaresolverr.py" \ + --prefix PATH : "${lib.makeBinPath [ xorg.xvfb ]}" + ''; + + passthru = { + tests.smoke-test = nixosTests.flaresolverr; + }; + + meta = with lib; { + description = "Proxy server to bypass Cloudflare protection"; + homepage = "https://github.com/FlareSolverr/FlareSolverr"; + license = licenses.mit; + mainProgram = "flaresolverr"; + maintainers = with maintainers; [ paveloom ]; + inherit (undetected-chromedriver.meta) platforms; + }; +}) diff --git a/pkgs/by-name/un/undetected-chromedriver/package.nix b/pkgs/by-name/un/undetected-chromedriver/package.nix new file mode 100644 index 000000000000..3976f3cc685b --- /dev/null +++ b/pkgs/by-name/un/undetected-chromedriver/package.nix @@ -0,0 +1,44 @@ +{ + lib, + stdenv, + + chromedriver, + python3, + + testers, + undetected-chromedriver, +}: + +stdenv.mkDerivation { + pname = "undetected-chromedriver"; + inherit (chromedriver) version; + + nativeBuildInputs = [ (python3.withPackages (ps: [ ps.undetected-chromedriver ])) ]; + + buildCommand = '' + export HOME=$(mktemp -d) + + cp ${chromedriver}/bin/chromedriver . + chmod +w chromedriver + + python <