tailscale 0.2.0

A work-in-progress Tailscale implementation
Documentation
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
    flake-parts.url = "github:hercules-ci/flake-parts";

    crane.url = "github:ipetkov/crane";
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    rust-advisory-db = {
      url = "github:rustsec/advisory-db";
      flake = false;
    };
  };

  description = "tailscale-rs: tailscale client in rust";

  outputs = inputs: let
    systems = [
      "x86_64-linux"
      "aarch64-linux"
      "armv7l-linux"
      "aarch64-darwin"
    ];

    lib = inputs.nixpkgs.lib;

    # Use the Rust toolchain specified in rust-toolchain.toml.
    repoRustToolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;

    # Specifically used to provide `cargo +nightly fmt`
    nightlyFmtToolchain = pkgs: pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal.override {
      extensions = ["rustfmt"];
    });

    importNixpkgs = { system, cross ? null }: import inputs.nixpkgs ({
      system = system;
      crossSystem = cross;

      overlays = [
        (import inputs.rust-overlay)

        (final: prev: {
          repo_toolchain = repoRustToolchain prev;
          nightly_fmt_toolchain = nightlyFmtToolchain prev;
        })

        (final: prev: {
          craneLib = (inputs.crane.mkLib prev).overrideToolchain (p: p.repo_toolchain);
          craneLibNightlyFmt = (inputs.crane.mkLib prev).overrideToolchain (p: p.nightly_fmt_toolchain);
        })
      ];
    });

    # If you include other assets in the build (e.g. via include_bytes!,
    # include_str!, or a dependency used in a build.rs), you must update this
    # -- these files are all that the `cargo build` for the workspace will be
    # able to see.
    #
    # NB: this is obviously not very selective, meaning crates may rebuild
    # spuriously, but better for now to avoid premature optimization.
    rustsrc = let
      filter = lib.fileset.unions [
        ./deny.toml
        ./.rustfmt.toml
        ./ts_ffi/cbindgen.toml
        ./ts_netstack_smoltcp/examples/axum_tun
        ./examples/axum
        (lib.fileset.fileFilter (file: file.hasExt "rs") ./.)
        (lib.fileset.fileFilter (file: file.hasExt "json") ./.)
        (lib.fileset.fileFilter (file: file.name == "README.md") ./.)
        (lib.fileset.fileFilter (file: file.name == "prefixes.txt.gz") ./ts_bart)
        (lib.fileset.fileFilter (file: file.name == "Cargo.toml") ./.)
        (lib.fileset.fileFilter (file: file.name == "Cargo.lock") ./.)
        (lib.fileset.fileFilter (file: file.name == "config.toml") ./.)  # capture .cargo/config.toml
      ];

    in lib.fileset.toSource {
      root = ./.;
      fileset = filter;
    };

    # Output packages for a given `pkgs` expression.
    # As above, pulled out into a function so we can call it in the
    # packages.cross leaves.
    makePackages = pkgs: let
      deps = pkgs.callPackage ./nix/deps.nix {};

      rootToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
      workspaceMembers = lib.sort lib.lessThan (lib.unique rootToml.workspace.members) ++ [ "./" ];

      crates = lib.map (memberPath: let
        cargoToml = ./. + "/${memberPath}/Cargo.toml";
        meta = pkgs.craneLib.crateNameFromCargoToml { cargoToml = cargoToml; };
        builtPkg = pkgs.callPackage ./nix/crate.nix {
          deps = deps;
          cargoToml = cargoToml;
          rustsrc = rustsrc;
        };
      in {
        name = meta.pname;
        value = builtPkg;
      })
        workspaceMembers;

      docs = pkgs.craneLib.cargoDoc (deps.buildDeps // {
        pname = "tailscale-rs-wksp";
        version = "dev";

        cargoArtifacts = deps;
        src = rustsrc;

        cargoExtraArgs = "--locked --workspace --all-features";
      });

      crates' = builtins.listToAttrs crates;
      crates'' = crates' // {
        ts_ffi = let
          with_header = crates'.ts_ffi.overrideAttrs (prevAttrs: {
            doCheck = false;

            postInstall = ''
              mkdir -p $out/include
              cp -v ts_ffi/tailscale.h $out/include
            '';
          });

        in with_header.overrideAttrs (prevAttrs: {
          passthru = (prevAttrs.passthru or {} // {
            examples = pkgs.callPackage ./ts_ffi/examples {
              libtailscalers = with_header;
            };
          });
        });
      };

      workspace = pkgs.craneLib.buildPackage (deps.passthru.buildDeps // {
        pname = "tailscale-rs-wksp";
        version = "dev";

        src = rustsrc;

        cargoArtifacts = deps;
        cargoExtraArgs = "--workspace --all-targets --all-features";
        env.PYO3_NO_PYTHON = 1;
        env.PYO3_BUILD_EXTENSION_MODULE = 1;

        # don't run benches as part of check
        checkPhaseCargoCommand = "cargo test --profile release --all-features --workspace --examples --bins --lib --tests";

        passthru = {
          deps = deps;
        };
      });

    in crates'' // ({
      deps = deps;
      docs = docs;
      doc = docs;
      workspace = workspace;
      default = workspace;

      libtailscalers = crates''.ts_ffi;
    });

  in inputs.flake-parts.lib.mkFlake { inputs = inputs; } {
    systems = systems;

    perSystem = { pkgs, system, lib, ... }: let
      packages = makePackages pkgs;

      # All the systems that are not us. Doesn't account for systems that might be architecture-
      # compatible (e.g. i686 -> x64).
      foreignSystems = builtins.filter (sys: sys != system) systems;

    in {
      _module.args = {
        # override nixpkgs with above closure -- sets overlays needed for rust
        pkgs = importNixpkgs { system = system; };
      };

      # Reexport nixpkgs (mostly for debugging)
      legacyPackages = pkgs;


      apps = let
        tailscale = {
          type = "app";
          program = "${packages.tailscale}/lib/tailscale";
        };
      in {
        tailscale = tailscale;
        default = tailscale;
      };

      packages = packages // {
        # flake-parts doesn't want attribute sets in packages -- this is an invalid dummy derivation
        # we use to hack its check so that we can `nix build .#cross.$CROSS_SYSTEM.$PACKAGE`.
        cross = (builtins.derivation {
          system = system;

          name = "placeholder";
          builder = "empty";
        }) // lib.genAttrs foreignSystems (cross: let
          crossPkgs = importNixpkgs {
            cross = cross;
            system = system;
          };
        in makePackages crossPkgs);
      };

      # Run these (as well as checking the flake for valid structure) with `nix flake check`.
      checks = let
        common = {
          pname = "tailscale-rs-wksp";
          version = "dev";
          src = rustsrc;

          env.PYO3_NO_PYTHON = 1;
          env.PYO3_BUILD_EXTENSION_MODULE = 1;
        };

      in {
        # Lint lib targets
        clippy_libs = pkgs.craneLib.cargoClippy (common // packages.deps.buildDeps // {
          cargoArtifacts = packages.deps;
          cargoExtraArgs = "--locked --workspace";
          cargoClippyExtraArgs = "--lib --all-features --no-deps -- -D warnings";
        });

        # Lint all other targets: these are separated to skip enforcing missing_docs for
        # targets that can't be part of public API.
        clippy_other_targets = pkgs.craneLib.cargoClippy (common // packages.deps.buildDeps // {
          cargoArtifacts = packages.deps;
          cargoExtraArgs = "--locked --workspace";
          cargoClippyExtraArgs = "--bins --tests --examples --benches --all-features --no-deps -- -D warnings -A missing_docs";
        });

        # See deny.toml -- various checks on dependencies
        deny = pkgs.craneLib.cargoDeny common;

        # Check code style
        fmt = pkgs.craneLibNightlyFmt.cargoFmt common;

        # Consults rustsec advisory db for reported vulnerabilities in dependencies
        audit = pkgs.craneLib.cargoAudit (common // {
          advisory-db = inputs.rust-advisory-db;
        });

        # This does the same as `cargo test`, it's just a pretty harness
        nextest = pkgs.craneLib.cargoNextest (common // packages.deps.buildDeps // {
          cargoArtifacts = packages.deps;
            partitions = 1;
            partitionType = "count";
            cargoExtraArgs = "--locked --workspace --all-features";
            cargoNextestPartitionsExtraArgs = "--no-tests=pass --show-progress=counter --no-fail-fast";
        });

        # Nextest can't run doctests, so run them separately
        docTest = pkgs.craneLib.cargoDocTest (common // packages.deps.buildDeps // {
            cargoArtifacts = packages.deps;
            cargoExtraArgs = "--locked --workspace --all-features";
        });

        docs = packages.docs;
      };

      # Builds a dev shell which you can enter with `nix develop .#`, or automatically
      # using direnv with use_flake
      devShells.default = pkgs.craneLib.devShell {
        inputsFrom = [ packages.tailscale ];

        packages = with pkgs; [
          cargo-audit
          cargo-deny
          cargo-nextest

          repo_toolchain

          cargo-flamegraph
          heaptrack

          # for ffi
          gnumake
          stdenv.cc
        ];
      };
    };
  };
}