{ description = "Tailscale GitOps OCI image"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils, }: (flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; # Tailscale release to build. tailscaleVersion = "1.104.5"; tailscaleSrc = pkgs.fetchFromGitHub { owner = "tailscale"; repo = "tailscale"; rev = "v${tailscaleVersion}"; hash = "sha256-aq4pBe75wNRFDEa3kSDa/pBLsZws5mOUOjHDUycpmXM="; }; # Static, stripped gitops-pusher. gitops-pusher = pkgs.buildGo127Module { pname = "gitops-pusher"; version = tailscaleVersion; src = tailscaleSrc; vendorHash = "sha256-RvaouD88naVNOPJBVcqMaAn+ovjXI4g83ShCdRiR5/Q="; subPackages = [ "cmd/gitops-pusher" ]; doCheck = false; env.CGO_ENABLED = "0"; ldflags = [ "-s" "-w" ]; # Scrub Go stdlib store references (services, protocols, # mime.types, zoneinfo). The tool does not use them; the # references would add tzdata, iana-etc, and mailcap to the # closure. postFixup = '' ${pkgs.removeReferencesTo}/bin/remove-references-to \ -t ${pkgs.iana-etc} \ -t ${pkgs.mailcap} \ -t ${pkgs.tzdata} \ $out/bin/gitops-pusher ''; }; imageLabels = { "org.opencontainers.image.title" = "gitops-pusher"; "org.opencontainers.image.description" = "Tailscale ACL GitOps helper (apply/test/checksum), statically built with Nix"; "org.opencontainers.image.version" = tailscaleVersion; "org.opencontainers.image.source" = "https://github.com/tailscale/tailscale"; "org.opencontainers.image.licenses" = "BSD-3-Clause"; }; # Minimal static busybox (~300 KB). GitLab Runner executes # mkdir, touch, and a shell before script lines; a shell-only # image fails. busybox-static = pkgs.pkgsStatic.busybox.override { enableMinimal = true; extraConfig = '' # POSIX shell CONFIG_ASH y CONFIG_SH_IS_ASH y CONFIG_ASH_OPTIMIZE_FOR_SIZE y CONFIG_ASH_BASH_COMPAT y CONFIG_ASH_ECHO y CONFIG_ASH_PRINTF y CONFIG_ASH_TEST y CONFIG_ASH_INTERNAL_GLOB y CONFIG_ASH_CMDCMD y CONFIG_FEATURE_SH_STANDALONE y CONFIG_FEATURE_PREFER_APPLETS y CONFIG_FEATURE_FANCY_ECHO y CONFIG_FEATURE_SH_MATH y CONFIG_FEATURE_TEST_64 y # Applet symlinks for PATH lookup. CONFIG_INSTALL_APPLET_SYMLINKS y # Commands used by the GitLab Runner wrapper and job scripts. CONFIG_MKDIR y CONFIG_TOUCH y CONFIG_RM y CONFIG_MV y CONFIG_CP y CONFIG_LN y CONFIG_CHMOD y CONFIG_CHOWN y CONFIG_CAT y CONFIG_LS y CONFIG_ECHO y CONFIG_PRINTF y CONFIG_TEST y CONFIG_TRUE y CONFIG_FALSE y CONFIG_SLEEP y CONFIG_DATE y CONFIG_ENV y CONFIG_UNAME y CONFIG_DIRNAME y CONFIG_BASENAME y CONFIG_MKTEMP y CONFIG_ID y CONFIG_WHOAMI y CONFIG_HEAD y CONFIG_TAIL y CONFIG_WHICH y CONFIG_TAR y CONFIG_GZIP y CONFIG_GUNZIP y ''; }; # One image for CI and direct use. No entrypoint, CMD /bin/sh. # Direct runs name the binary. gitops-pusher-image = pkgs.dockerTools.buildLayeredImage { name = "gitops-pusher"; tag = tailscaleVersion; contents = [ (pkgs.buildEnv { name = "gitops-pusher-root"; paths = [ gitops-pusher busybox-static pkgs.cacert ]; pathsToLink = [ "/bin" "/etc" ]; }) ]; fakeRootCommands = '' mkdir -p ./data ./tmp ''; config = { Cmd = [ "/bin/sh" ]; WorkingDir = "/data"; Env = [ "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt" # TLS trust for the static binary "PATH=/bin" # OCI runtimes set no default PATH ]; Labels = imageLabels; }; }; in { packages = { inherit gitops-pusher gitops-pusher-image; default = gitops-pusher; }; devShells.default = pkgs.mkShellNoCC { packages = with pkgs; [ glab # GitLab CLI jq # JSON processing for glab api output podman # run the built images locally skopeo # OCI image inspection crane # OCI image building helpers dive # inspect image layers nixfmt # format the flake ]; }; } )); }