{ description = "Federated Forge Discovery Indexer"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small"; flake-utils.url = "github:numtide/flake-utils"; gomod2nix = { url = "github:tweag/gomod2nix"; inputs.nixpkgs.follows = "nixpkgs"; inputs.flake-utils.follows = "flake-utils"; }; }; outputs = { self, nixpkgs, flake-utils, gomod2nix, }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs { inherit system; overlays = [ gomod2nix.overlays.default ]; }; go = pkgs.go_1_27; version = "0.0.1"; # Git commit (full + short) used for container labels. Inside a real # flake build (including CI's shallow clone) `self.rev` is populated; # on a dirty working tree it is null and we fall back to empty so the # label is simply omitted. rev = self.rev or ""; shortRev = if rev != "" then builtins.substring 0 7 rev else (self.dirtyShortRev or ""); # Inject version + commit into the binaries via the Go linker, and # reuse the same values for the OCI image labels. goLdflags = [ "-X git.xengi.de/ffd/indexer/internal/version.Version=${version}" "-X git.xengi.de/ffd/indexer/internal/version.Commit=${shortRev}" ]; ffd-indexer = pkgs.buildGoApplication { inherit go; pname = "ffd-indexer"; inherit version; ldflags = goLdflags; src = ./.; modules = ./gomod2nix.toml; # buildGoApplication runs `go install ./...`, which names each binary # after the last element of its package path: # git.xengi.de/ffd/indexer -> "indexer" # git.xengi.de/ffd/indexer/cmd/ffd-metrics -> "ffd-metrics" # Rename "indexer" to the service/container name "ffd-indexer". postInstall = '' mv "$out/bin/indexer" "$out/bin/ffd-indexer" ''; }; # Long-running Prometheus metrics daemon. It is a separate binary but is # built in the same derivation; keep it as its own output alias for the # NixOS module and containers. ffd-metrics = pkgs.runCommand "ffd-metrics" { } '' mkdir -p "$out/bin" ln -s "${ffd-indexer}/bin/ffd-metrics" "$out/bin/ffd-metrics" ''; go-test = pkgs.stdenv.mkDerivation { name = "go-test"; dontBuild = true; src = ./.; doCheck = true; nativeBuildInputs = [ go pkgs.writableTmpDirAsHomeHook ]; checkPhase = '' go test -v ./... ''; installPhase = '' mkdir "$out" ''; }; go-lint = pkgs.stdenv.mkDerivation { name = "go-lint"; dontBuild = true; src = ./.; doCheck = true; nativeBuildInputs = [ pkgs.golangci-lint go pkgs.writableTmpDirAsHomeHook ]; checkPhase = '' golangci-lint run ''; installPhase = '' mkdir "$out" ''; }; in { checks = { inherit go-test go-lint; }; formatter = pkgs.nixfmt-tree; devShells.default = pkgs.mkShell { buildInputs = [ go pkgs.go-tools # staticcheck gomod2nix.packages.${system}.default ]; packages = with pkgs; [ mkcert sqlite # debugging the index db typesense skopeo # copy OCI/container images to a registry (CI publishing) ]; shellHook = '' # Local dev Typesense defaults (see packages.typesense-server). export TYPESENSE_API_KEY="''${TYPESENSE_API_KEY:-dev-key}" export TYPESENSE_URL="''${TYPESENSE_URL:-http://localhost:8108}" echo "Typesense dev: run 'typesense-server' (port 8108, api key $TYPESENSE_API_KEY)" ''; }; packages = { default = ffd-indexer; inherit ffd-metrics; # Runs a local Typesense instance for development. typesense-server = pkgs.writeScriptBin "typesense-server" '' #!${pkgs.bash}/bin/bash set -euo pipefail ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")" DATA_DIR="$ROOT/.typesense/data" API_KEY="''${TYPESENSE_API_KEY:-dev-key}" PORT="''${TYPESENSE_PORT:-8108}" # Peering port follows the API port so multiple dev instances # (different TYPESENSE_PORT) do not collide on the default 8107. PEERING_PORT=$((PORT - 1)) mkdir -p "$DATA_DIR" echo "Starting Typesense on 127.0.0.1:$PORT (api key: $API_KEY, data: $DATA_DIR)" exec ${pkgs.typesense}/bin/typesense-server \ --data-dir="$DATA_DIR" \ --api-key="$API_KEY" \ --api-port="$PORT" \ --peering-port="$PEERING_PORT" \ --api-address=127.0.0.1 \ --enable-cors ''; container = pkgs.dockerTools.buildImage { name = "ffd-indexer"; contents = [ ffd-indexer pkgs.cacert # TLS CA for HTTPS requests to the forges ]; config = { # The user must provide their own config file and mount it at # /etc/ffd-indexer/config.yaml, e.g.: # docker run -v $PWD/config.yaml:/etc/ffd-indexer/config.yaml ffd-indexer Labels = { "org.opencontainers.image.title" = "ffd-indexer"; "org.opencontainers.image.description" = "Federated Forge Discovery Indexer (scraper)"; "org.opencontainers.image.licenses" = "AGPL-3.0-or-later"; "org.opencontainers.image.source" = "https://git.xengi.de/ffd/indexer"; "org.opencontainers.image.vendor" = "ffd"; "org.opencontainers.image.version" = version; "org.opencontainers.image.revision" = shortRev; }; Volumes = { "/etc/ffd-indexer" = { }; }; Cmd = [ "${ffd-indexer}/bin/ffd-indexer" "-config" "/etc/ffd-indexer/config.yaml" ]; }; }; # Long-running metrics image. It shares the same config file shape and # reads the same DB volume as the indexer. container-metrics = pkgs.dockerTools.buildImage { name = "ffd-metrics"; contents = [ ffd-indexer pkgs.cacert ]; config = { Labels = { "org.opencontainers.image.title" = "ffd-metrics"; "org.opencontainers.image.description" = "Federated Forge Discovery Indexer (Prometheus metrics daemon)"; "org.opencontainers.image.licenses" = "AGPL-3.0-or-later"; "org.opencontainers.image.source" = "https://git.xengi.de/ffd/indexer"; "org.opencontainers.image.vendor" = "ffd"; "org.opencontainers.image.version" = version; "org.opencontainers.image.revision" = shortRev; }; Volumes = { "/etc/ffd-indexer" = { }; }; ExposedPorts = { "9090/tcp" = { }; }; Cmd = [ "${ffd-indexer}/bin/ffd-metrics" "-config" "/etc/ffd-indexer/config.yaml" ]; }; }; }; nixosModules.default = import ./option.nix; } ) // { # Overlay so the NixOS module's default package (`pkgs.ffd-indexer`) # resolves to this flake's package. overlays.default = final: prev: { ffd-indexer = self.packages.${final.system}.default; ffd-metrics = self.packages.${final.system}.ffd-metrics; }; }; }