Ctrl K
ComunidadGuías…

flake.nix

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
{
  description = "RSSHub - Make RSS Great Again!";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    devenv.url = "github:cachix/devenv";
  };

  outputs = inputs@{ self, nixpkgs, flake-utils, devenv }:
    let
      # Helper to define the RSSHub package
      makeRSSHub = pkgs:
        let
          pnpm = pkgs.pnpm_12;
          deps = pkgs.fetchPnpmDeps {
            pname = "rsshub";
            src = ./.;
            hash = "sha256-4UA3Hac/YMHX5qMT3pmBbUAgXK7RDGdZKW7fMVpkP5w=";
            fetcherVersion = 4;
          };
        in
        pkgs.stdenv.mkDerivation rec {
          pname = "rsshub";
          version = "1.0.0";

          src = ./.;

          nativeBuildInputs = with pkgs; [
            nodejs_26
            pnpm
            pnpmConfigHook
            git
          ];

        buildInputs = with pkgs; [
          # Optional: Add chromium for routes that need browser automation
          # chromium
        ];

        pnpmDeps = deps;

        # 修补构建脚本以支持离线构建(Nix 构建环境无网络访问)
        postPatch = ''
          # 在 registry.ts 中添加 BUILD_ROUTES 模式,使用 directoryImport 但不实际导入模块
          substituteInPlace lib/registry.ts \
            --replace-fail 'if (config.isPackage)' \
                          'if (process.env.BUILD_ROUTES_MODE) {
    modules = directoryImport({
        targetDirectoryPath: path.join(__dirname, "./routes"),
        importPattern: /\.tsx?$/,
    }) as typeof modules;
} else if (config.isPackage)'
        '';

        # The build phase
        buildPhase = ''
          runHook preBuild

          # 先构建路由元数据(使用 directoryImport 但避免执行模块顶层代码)
          export BUILD_ROUTES_MODE=1
          pnpm run build:routes
          unset BUILD_ROUTES_MODE

          # 然后构建应用
          export NODE_ENV=production
          ${pnpm}/bin/pnpm run build

          runHook postBuild
        '';

        # The install phase
        installPhase = ''
          runHook preInstall
          mkdir -p $out/lib/rsshub
          cp -r dist $out/lib/rsshub/
          cp -r node_modules $out/lib/rsshub/
          cp package.json $out/lib/rsshub/

          mkdir -p $out/bin
          cat > $out/bin/rsshub <<EOF
          #!${pkgs.bash}/bin/bash
          export NODE_ENV=production
          export NODE_OPTIONS='--max-http-header-size=32768'
          exec ${pkgs.nodejs_26}/bin/node $out/lib/rsshub/dist/index.mjs "\$@"
          EOF
          chmod +x $out/bin/rsshub
          runHook postInstall
        '';

        meta = with pkgs.lib; {
          description = "Everything is RSSible";
          homepage = "https://github.com/DIYgod/RSSHub";
          license = licenses.mit;
          maintainers = [ ];
          platforms = platforms.all;
        };
      };

      # NixOS module definition
      makeNixOSModule = { lib, pkgs, config, ... }:
        with lib;
        let
          cfg = config.services.rsshub;
        in
        {
          options.services.rsshub = {
            enable = mkEnableOption "RSSHub service";

            package = mkOption {
              type = types.package;
              default = makeRSSHub pkgs;
              defaultText = literalExpression "pkgs.rsshub";
              description = "The RSSHub package to use.";
            };

            port = mkOption {
              type = types.port;
              default = 1200;
              description = "Port on which RSSHub will listen.";
            };

            listenAddress = mkOption {
              type = types.str;
              default = "0.0.0.0";
              description = "Address on which RSSHub will listen.";
            };

            openFirewall = mkOption {
              type = types.bool;
              default = false;
              description = "Whether to open the firewall for the specified port.";
            };

            user = mkOption {
              type = types.str;
              default = "rsshub";
              description = "User account under which RSSHub runs.";
            };

            group = mkOption {
              type = types.str;
              default = "rsshub";
              description = "Group under which RSSHub runs.";
            };

            dataDir = mkOption {
              type = types.path;
              default = "/var/lib/rsshub";
              description = "Directory for RSSHub data.";
            };

            environment = mkOption {
              type = types.attrsOf types.str;
              default = { };
              example = literalExpression ''
                {
                  PORT = "1200";
                  CACHE_TYPE = "redis";
                  REDIS_URL = "redis://localhost:6379/";
                  ALLOW_LOCALHOST = "true";
                }
              '';
              description = ''
                Environment variables for RSSHub.
                See https://docs.rsshub.app/deploy/config for available options.
              '';
            };

            environmentFiles = mkOption {
              type = types.listOf types.path;
              default = [ ];
              example = literalExpression ''
                [ config.sops.secrets.rsshub.path ]
              '';
              description = ''
                Environment variables stored in files for RSSHub.
                It can be used for secrets like agenix, sops-nix, etc.
                See https://docs.rsshub.app/deploy/config for available options.
              '';
            };

            redis = {
              enable = mkOption {
                type = types.bool;
                default = false;
                description = "Whether to enable and configure Redis for caching.";
              };

              createLocally = mkOption {
                type = types.bool;
                default = true;
                description = "Whether to create a local Redis instance.";
              };

              url = mkOption {
                type = types.str;
                default = "redis://localhost:6379/";
                description = "Redis connection URL.";
              };
            };
          };

          config = mkIf cfg.enable (
            let
              baseEnv = cfg.environment;
              redisEnv = optionalAttrs cfg.redis.enable {
                CACHE_TYPE = "redis";
                REDIS_URL = cfg.redis.url;
              };
              derivedEnv =
                {
                  PORT = toString cfg.port;
                }
                // optionalAttrs (cfg.listenAddress == "0.0.0.0") {
                  LISTEN_INADDR_ANY = "1";
                };
              finalEnv = baseEnv // redisEnv // derivedEnv;
              environmentFile = pkgs.writeText "rsshub.env" (
                concatStringsSep "\n" (
                  mapAttrsToList (name: value: "${name}=${toString value}") finalEnv
                )
              );
            in
            {
              # Set up Redis if enabled
              services.redis.servers.rsshub = mkIf (cfg.redis.enable && cfg.redis.createLocally) {
                enable = true;
                port = 6379;
              };

              # Create user and group
              users.users.${cfg.user} = {
                isSystemUser = true;
                group = cfg.group;
                home = cfg.dataDir;
                createHome = true;
                description = "RSSHub service user";
              };

              users.groups.${cfg.group} = { };

              # SystemD service
              systemd.services.rsshub = {
                description = "RSSHub - Everything is RSSible";
                wantedBy = [ "multi-user.target" ];
                after = [ "network.target" ] ++ optional (cfg.redis.enable && cfg.redis.createLocally) "redis-rsshub.service";
                requires = optional (cfg.redis.enable && cfg.redis.createLocally) "redis-rsshub.service";

                serviceConfig = {
                  Type = "simple";
                  User = cfg.user;
                  Group = cfg.group;
                  WorkingDirectory = cfg.dataDir;
                  EnvironmentFile = [environmentFile] ++ cfg.environmentFiles;
                  ExecStart = "${cfg.package}/bin/rsshub";
                  Restart = "on-failure";
                  RestartSec = "5s";

                  # Hardening
                  NoNewPrivileges = true;
                  PrivateTmp = true;
                  ProtectSystem = "strict";
                  ProtectHome = true;
                  ReadWritePaths = [ cfg.dataDir ];
                  ProtectKernelTunables = true;
                  ProtectKernelModules = true;
                  ProtectControlGroups = true;
                };
              };

              # Open firewall if requested
              networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
            }
          );
        };

    in
    {
      # NixOS module
      nixosModules.default = makeNixOSModule;
      nixosModules.rsshub = makeNixOSModule;

      # Overlay
      overlays.default = final: prev: {
        rsshub = makeRSSHub final;
      };
    }
    //
    # Per-system outputs
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        # Package
        packages = {
          default = makeRSSHub pkgs;
          rsshub = makeRSSHub pkgs;
        };

        # Development shell using devenv
        devShells.default = devenv.lib.mkShell {
          inherit inputs pkgs;
          modules = [
            {
              # devenv requires knowing the project root
              # https://devenv.sh/guides/using-with-flakes/
              packages = [ ];
            }
            ./devenv.nix
          ];
        };

        # Apps
        apps.default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/rsshub";
        };
      }
    );
}