{ description = "Development environment and pre-commit checks for the workbench apps"; inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; # The Android SDK as a flake. Only the catbox-android package and # the android devShell depend on it. android-nixpkgs = { url = "github:tadfisher/android-nixpkgs"; inputs.nixpkgs.follows = "nixpkgs"; }; pre-commit-hooks = { url = "github:cachix/git-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, android-nixpkgs, pre-commit-hooks, }: let goVersion = 27; # Baked into both catbox binaries via -X main.version. version = self.shortRev or self.dirtyShortRev or "dev"; # Baked via -X main.buildTime: the commit's unix timestamp. buildTime = toString (self.lastModified or 0); supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems ( system: f { pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; config.allowUnfree = true; }; } ); goApps = [ "catbox" "go-healthcheck" "grafana-dashboards" "lastfm-scrobble-deduplicator" "rangemusique" ]; # Per-app hooks mirroring the pre-commit setups of the original repos # (gofmt, golangci-lint, govet). Each app is its own Go module, so the # tools run from the app directory. Staticcheck checks are enforced via # golangci-lint, which enables the staticcheck linter in .golangci.yaml. # Entries are absolute store paths so the hooks also work when invoked # from environments without the dev shell on PATH (e.g. VS Code git). goAppHooks = pkgs: app: let runInApp = pkgs.writeShellScript "precommit-${app}" '' export PATH="${pkgs.go}/bin:${pkgs.golangci-lint}/bin:$PATH" cd workbench/${app} exec "$@" ''; in { "${app}-golangci-lint" = { enable = true; entry = "${runInApp} golangci-lint run"; files = "^workbench/${app}/"; pass_filenames = false; extraPackages = with pkgs; [ go golangci-lint ]; }; "${app}-govet" = { enable = true; entry = "${runInApp} go vet ./..."; files = "^workbench/${app}/"; pass_filenames = false; extraPackages = [ pkgs.go ]; }; }; hooks = pkgs: { # Secret detection, replacing the previous .pre-commit-config.yaml. # Absolute store path so the hook also works outside the dev shell. gitleaks = { enable = true; entry = "${pkgs.gitleaks}/bin/gitleaks protect --staged --redact --verbose"; pass_filenames = false; extraPackages = [ pkgs.gitleaks ]; }; gofmt = { enable = true; files = "^workbench/.*\\.go$"; }; } // nixpkgs.lib.foldl' (acc: app: acc // goAppHooks pkgs app) { } goApps; # The .worktrees directory holds full repo copies that would otherwise # be linted and shadow the real sources in the check build. src = nixpkgs.lib.cleanSourceWith { src = ./.; filter = path: type: !( type == "directory" && builtins.elem (baseNameOf path) [ ".git" ".worktrees" ] ); }; in { overlays.default = final: prev: { go = final."go_1_${toString goVersion}"; }; devShells = forEachSupportedSystem ( { pkgs }: { default = pkgs.mkShell { inherit (self.checks.${pkgs.stdenv.hostPlatform.system}.pre-commit-check) shellHook; packages = with pkgs; [ gitleaks go gotools golangci-lint hugo uv vault ] ++ self.checks.${pkgs.stdenv.hostPlatform.system}.pre-commit-check.enabledPackages; }; } // ( # The Android app shell: nix develop .#android. The SDK # matches android/app/build.gradle.kts (compileSdk 37, # build-tools 36.0.0, AGP 9.4.1, JDK 17) — bump them # together. Hosts without an android-nixpkgs SDK composition # (aarch64-linux) don't get the shell. let system = pkgs.stdenv.hostPlatform.system; androidSystems = [ "x86_64-linux" "aarch64-darwin" ]; in nixpkgs.lib.optionalAttrs (builtins.elem system androidSystems) { android = let sdk = android-nixpkgs.sdk.${system} ( sdkPkgs: with sdkPkgs; [ cmdline-tools-latest platform-tools build-tools-36-0-0 platforms-android-37-0 emulator system-images-android-36-default-arm64-v8a ] ); in pkgs.mkShell rec { packages = [ sdk pkgs.jdk17 pkgs.gradle_9 pkgs.go ]; ANDROID_HOME = "${sdk}/share/android-sdk"; ANDROID_SDK_ROOT = "${sdk}/share/android-sdk"; # AGP downloads a prebuilt aapt2 from Maven that does not # run on Nix (unpatched ELF); point it at the SDK's own. GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; JAVA_HOME = pkgs.jdk17.home; }; } ) ); packages = forEachSupportedSystem ( { pkgs }: { # The peer CLI for laptops: nix build github.com/cterence/homelab-gitops#catbox catbox = pkgs.buildGo127Module { pname = "catbox"; version = "unstable"; src = ./workbench/catbox; vendorHash = "sha256-gATb9AZZjhcEwObWBSiTNc0Cl7C9npM90Oscr2sSOZs="; ldflags = [ "-s" "-w" "-X main.version=${version}" "-X main.buildTime=${buildTime}" ]; }; } // ( # The Android APK, hermetically built from # workbench/catbox/nix/android.nix. Only hosts with an # android-nixpkgs SDK composition can build it. let system = pkgs.stdenv.hostPlatform.system; androidSystems = [ "x86_64-linux" "aarch64-darwin" ]; in nixpkgs.lib.optionalAttrs (builtins.elem system androidSystems) ( let catboxAndroid = import ./workbench/catbox/nix/android.nix { inherit pkgs version buildTime; android-sdk = android-nixpkgs.sdk.${system}; # A path literal, like the catbox package above: the # git-filtered self.outPath would exclude untracked # new files and break the cross-build. src = ./workbench/catbox; vendorHash = "sha256-gATb9AZZjhcEwObWBSiTNc0Cl7C9npM90Oscr2sSOZs="; }; in { catbox-android = catboxAndroid.catbox-android; catbox-android-bin = catboxAndroid.catbox-android-bin; } ) ) ); checks = forEachSupportedSystem ( { pkgs }: { pre-commit-check = pre-commit-hooks.lib.${pkgs.stdenv.hostPlatform.system}.run { inherit src; # prek is the fast Rust reimplementation of pre-commit. package = pkgs.prek; hooks = hooks pkgs; }; } ); }; }