purple-ssh 3.29.2

Free, open-source terminal SSH manager and SSH config editor in Rust for macOS and Linux that keeps ~/.ssh/config in sync with 18 cloud providers, monitors live SSH tunnels and manages Docker and Podman containers fleet-wide. Spin up a VM, it shows up. Destroy it, it dims. Plus scp, Vault SSH certs and an MCP server for AI agents. MIT licensed.
Documentation
{
  description = "Open-source terminal SSH manager and SSH config editor for macOS and Linux.";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    crane.url = "github:ipetkov/crane";
  };

  outputs =
    { self, nixpkgs, rust-overlay, crane }:
    let
      supportedSystems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];

      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;

      # Memoized: each system's nixpkgs is imported once and reused
      # across packages, devShells, formatter and checks.
      pkgsFor = forAllSystems (system: import nixpkgs {
        inherit system;
        overlays = [ (import rust-overlay) ];
      });

      # Whitelist source filter: skips docs, screenshots, goldens and
      # other churn that would invalidate the cache for nothing.
      filteredSrc = pkgs: pkgs.lib.cleanSourceWith {
        src = ./.;
        name = "purple-source";
        filter = path: type:
          let
            rootStr = toString ./.;
            pathStr = toString path;
            relPath =
              if pathStr == rootStr
              then ""
              else pkgs.lib.removePrefix (rootStr + "/") pathStr;
          in
            (pkgs.lib.cleanSourceFilter path type)
            && (
              relPath == ""
              || pkgs.lib.elem relPath [
                   "Cargo.toml"
                   "Cargo.lock"
                   "build.rs"
                   "rust-toolchain.toml"
                   "CHANGELOG.md"
                 ]
              || relPath == "src"
              || pkgs.lib.hasPrefix "src/" relPath
              || relPath == "tests"
              || (pkgs.lib.hasPrefix "tests/" relPath
                  && !pkgs.lib.hasPrefix "tests/visual_golden" relPath)
            );
      };

      mkCraneLib = pkgs:
        let
          rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
        in
        (crane.mkLib pkgs).overrideToolchain rustToolchain;

      # Shared build arguments between the deps-only derivation and the
      # full package. Splitting them is what makes crane fast: the deps
      # layer is hashed on Cargo.lock and reused whenever lockfile and
      # toolchain are unchanged, so source-only pushes skip the 800+
      # dep recompile.
      mkCommonArgs = pkgs:
        let
          cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
        in {
          pname = "purple-ssh";
          version = cargoToml.package.version;

          src = filteredSrc pkgs;
          strictDeps = true;

          nativeBuildInputs = with pkgs; [ pkg-config ];
          buildInputs = [ pkgs.openssl ];

          # Use system openssl. Cargo.toml only vendors openssl for
          # cfg(target_env = "musl"); Nix builds against glibc/darwin.
          OPENSSL_NO_VENDOR = 1;

          # Tests need $HOME, ~/.ssh, a working ssh binary and serialized
          # PATH manipulation (vault_ssh_tests::PATH_LOCK). The Nix
          # sandbox provides none of those, so the test suite runs in CI
          # instead. `nix flake check` still verifies the package builds.
          doCheck = false;
        };

      mkPurple = pkgs:
        let
          craneLib = mkCraneLib pkgs;
          commonArgs = mkCommonArgs pkgs;
          cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);

          # Deps layer hashes on Cargo.toml verbatim; cargo requires
          # package.version so a release commit invalidates it once.
          cargoArtifacts = craneLib.buildDepsOnly commonArgs;
        in
        craneLib.buildPackage (commonArgs // {
          inherit cargoArtifacts;

          # Skip LTO inside Nix so the link phase parallelises. Release
          # binaries via cargo publish keep full LTO from Cargo.toml.
          CARGO_PROFILE_RELEASE_LTO = "false";

          meta = with pkgs.lib; {
            description = cargoToml.package.description;
            homepage = "https://getpurple.sh";
            license = licenses.mit;
            mainProgram = "purple";
            platforms = platforms.unix;
          };
        });
    in
    {
      packages = forAllSystems (system:
        let purple = mkPurple pkgsFor.${system};
        in {
          default = purple;
          purple-ssh = purple;
        });

      apps = forAllSystems (system: {
        default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/purple";
        };
      });

      devShells = forAllSystems (system:
        let
          pkgs = pkgsFor.${system};
          rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
        in {
          default = pkgs.mkShell {
            inputsFrom = [ self.packages.${system}.default ];
            packages = with pkgs; [
              rustToolchain
              cargo-audit
              cargo-deny
            ];
            OPENSSL_NO_VENDOR = 1;
          };
        });

      formatter = forAllSystems (system: pkgsFor.${system}.nixfmt-rfc-style);

      checks = forAllSystems (system:
        let
          pkgs = pkgsFor.${system};
          craneLib = mkCraneLib pkgs;
          commonArgs = mkCommonArgs pkgs;
        in {
          package = self.packages.${system}.default;

          fmt = craneLib.cargoFmt {
            inherit (commonArgs) src pname version;
          };
        });

      overlays.default = final: _prev: {
        purple-ssh = mkPurple final;
      };
    };
}