Explorar el Código

feat(CI): overhaul and update build-system

The tooling to build Kilo is out of date and is preventing CI from
running. This is, in turn, is preventing reviewing and merging PRs,
leading the project to stagnate. This commit overhauls the build-system
for Kilo, locks every single piece of tooling in a flake.lock file,
enables dependabot to promote automatic updates to the code, and uses
Nix for reproducible builds.

Signed-off-by: squat <lserven@gmail.com>
squat hace 2 meses
padre
commit
40a3daa76d
Se han modificado 14 ficheros con 713 adiciones y 431 borrados
  1. 7 1
      .dockerignore
  2. 1 0
      .envrc
  3. 19 0
      .github/dependabot.yml
  4. 125 108
      .github/workflows/ci.yml
  5. 14 13
      .github/workflows/release.yaml
  6. 27 0
      .github/workflows/update.yaml
  7. 5 6
      .gitignore
  8. 26 9
      Dockerfile
  9. 21 275
      Makefile
  10. 3 11
      cmd/kgctl/connect_linux.go
  11. 21 8
      cmd/kgctl/main.go
  12. 28 0
      cmd/kgctl/showconf.go
  13. 121 0
      flake.lock
  14. 295 0
      flake.nix

+ 7 - 1
.dockerignore

@@ -1,3 +1,9 @@
 **
 
-!/bin/linux
+!/flake.lock
+!/flake.nix
+!/cmd
+!/pkg
+!/go.mod
+!/go.sum
+!/vendor

+ 1 - 0
.envrc

@@ -0,0 +1 @@
+use flake

+ 19 - 0
.github/dependabot.yml

@@ -0,0 +1,19 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+  - package-ecosystem: "docker"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+  - package-ecosystem: "gomod"
+    directory: "/"
+    schedule:
+      interval: "weekly"

+ 125 - 108
.github/workflows/ci.yml

@@ -2,163 +2,180 @@ name: CI
 
 on:
   push:
-    branches: [ main ]
+    branches: [main]
     tags:
       - "*"
   pull_request:
   schedule:
-  - cron:  '0 0 * * *'
+    - cron: "0 0 * * *"
   workflow_dispatch:
 
-jobs:
+env:
+  IMAGE_NAME: ${{ github.repository }}
 
+jobs:
   vendor:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Vendor
-      run: |
-        make vendor
-        git diff --exit-code
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: |
+          go mod tidy
+          go mod vendor
+          git diff --exit-code
 
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build
-      run: make
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix build
 
   docs:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build docs
-      run: |
-        make gen-docs
-        git diff --exit-code
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - name: Build docs
+        run: |
+          nix develop . --command make gen-docs
+          git diff --exit-code
 
   linux:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build kg and kgctl for all Linux Architectures
-      run: make all-build
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix build .#kilo-cross-linux-amd64 .#kilo-cross-linux-arm64 .#kilo-cross-linux-arm
 
   darwin:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build kgctl for Darwin amd64
-      run: make OS=darwin ARCH=amd64
-    - name: Build kgctl for Darwin arm64
-      run: make OS=darwin ARCH=arm64
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix build .#kgctl-cross-darwin-amd64 .#kgctl-cross-darwin-arm64
 
   windows:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build kgctl for Windows
-      run: make OS=windows
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix build .#kgctl-cross-windows-amd64
 
   unit:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Run Unit Tests
-      run: make unit
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix develop . --command go test -mod=vendor --race ./...
 
   e2e:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Run e2e Tests
-      run: make e2e
+      - uses: actions/checkout@v6
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Build
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          platforms: linux/amd64
+          tags: squat/kilo:test
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+          load: "true"
+          build-args: |
+            VERSION=${{ github.sha }}
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix develop . --command make e2e
 
   lint:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Lint Code
-      run: make lint
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: nix flake check -L --show-trace
 
   container:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Container
-      run: make container
+      - uses: actions/checkout@v6
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Extract Docker metadata
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ghcr.io/${{ env.IMAGE_NAME }},docker.io/${{ env.IMAGE_NAME }}
+          tags: type=sha,prefix=
+          flavor: latest=true
+      - name: Build
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          platforms: linux/amd64, linux/arm64, linux/arm
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+          build-args: |
+            VERSION=${{ github.sha }}
 
   push:
     if: github.event_name != 'pull_request'
     needs:
-    - vendor
-    - build
-    - linux
-    - darwin
-    - windows
-    - unit
-    - lint
-    - container
+      - vendor
+      - build
+      - linux
+      - darwin
+      - windows
+      - unit
+      - lint
+      - container
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Set up QEMU
-      uses: docker/setup-qemu-action@v2
-      with:
-        platforms: all
-    - name: Login to DockerHub
-      if: github.event_name != 'pull_request'
-      uses: docker/login-action@v2
-      with:
-        username: ${{ secrets.DOCKER_USERNAME }}
-        password: ${{ secrets.DOCKER_PASSWORD }}
-    - name: Build and push
-      if: github.event_name != 'pull_request'
-      run: make manifest
-    - name: Build and push latest
-      if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
-      run: make manifest-latest
+      - uses: actions/checkout@v6
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v3
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Login to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKER_USERNAME }}
+          password: ${{ secrets.DOCKER_PASSWORD }}
+      - name: Extract Docker metadata
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ghcr.io/${{ env.IMAGE_NAME }},docker.io/${{ env.IMAGE_NAME }}
+          tags: type=sha,prefix=
+          flavor: latest=true
+      - name: Build and push
+        id: push
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          push: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
+          platforms: linux/amd64, linux/arm64, linux/arm
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+          build-args: |
+            VERSION=${{ github.sha }}
+      - name: Determine digest
+        run: echo ${{ steps.push.outputs.digest }}

+ 14 - 13
.github/workflows/release.yaml

@@ -6,16 +6,17 @@ jobs:
   kgctl:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - name: Set up Go
-      uses: actions/setup-go@v4
-      with:
-        go-version: 1.19
-    - name: Build kgctl Binaries to Be Released
-      run: make release
-    - name: Publish Release
-      uses: skx/github-action-publish-binaries@master
-      env:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      with:
-        args: 'bin/release/kgctl-*'
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/magic-nix-cache-action@v13
+      - run: |
+          nix build .#kgctl-cross-linux-amd64 .#kgctl-cross-linux-arm64 .#kgctl-cross-linux-arm .#kgctl-cross-darwin-amd64 .#kgctl-cross-darwin-arm64 .#kgctl-cross-windows-amd64
+          for result in $(find -L . -name 'kgctl*' | grep result); do
+            cp "$result" "$(echo "$result" | sed 's|.*bin/\(.\+\)_\(.\+\)/kgctl\(.*\)|kgctl-\1-\2\3|g' | sed 's|.*bin/kgctl|kgctl-amd64|g')"
+          done
+      - name: Publish Release
+        uses: skx/github-action-publish-binaries@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          args: "kgctl-*"

+ 27 - 0
.github/workflows/update.yaml

@@ -0,0 +1,27 @@
+name: "Flake.lock: update Nix dependencies"
+
+on:
+  workflow_dispatch: # allows manual triggering
+  schedule:
+    - cron: "0 0 * * 0" # runs weekly on Sunday at 00:00
+
+jobs:
+  nix-flake-update:
+    permissions:
+      contents: write
+      id-token: write
+      issues: write
+      pull-requests: write
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v6
+      - uses: DeterminateSystems/determinate-nix-action@v3.15.1
+      - uses: DeterminateSystems/update-flake-lock@v28
+        with:
+          pr-title: Update Nix flake inputs
+          pr-labels: |
+            dependencies
+            automated
+          sign-commits: true
+          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
+          token: ${{ secrets.GH_TOKEN_FOR_FLAKE_LOCK_UPDATES }}

+ 5 - 6
.gitignore

@@ -1,7 +1,6 @@
-.cache/
-.container*
-.manifest*
-.push*
-bin/
-tmp/
 e2e/kind.yaml*
+.direnv/
+/.pre-commit-config.yaml
+/result*
+/e2e-*/
+/help.txt

+ 26 - 9
Dockerfile

@@ -1,13 +1,30 @@
-ARG FROM=alpine
-FROM $FROM AS cni
-ARG GOARCH=amd64
-ARG CNI_PLUGINS_VERSION=v1.1.1
+FROM --platform=$BUILDPLATFORM docker.io/nixos/nix:2.33.0 AS builder
+
+COPY . /tmp/build
+WORKDIR /tmp/build
+
+ARG BUILDOS
+ARG BUILDARCH
+ARG TARGETOS
+ARG TARGETARCH
+ARG VERSION
+
+RUN VERSION="$VERSION" nix \
+    --extra-experimental-features "nix-command flakes" \
+    --option filter-syscalls false \
+    build --impure ".#kilo-cross-$TARGETOS-$TARGETARCH"
+RUN ln -s ../bin result/bin/"$BUILDOS"_"$BUILDARCH"
+
+FROM alpine:3.20 AS cni
+ARG TARGETARCH
+ARG CNI_PLUGINS_VERSION=v1.9.0
 RUN apk add --no-cache curl && \
-    curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/$CNI_PLUGINS_VERSION/cni-plugins-linux-$GOARCH-$CNI_PLUGINS_VERSION.tgz && \
+    curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/$CNI_PLUGINS_VERSION/cni-plugins-linux-$TARGETARCH-$CNI_PLUGINS_VERSION.tgz && \
     tar -xf cni.tar.gz
 
-FROM $FROM
-ARG GOARCH
+FROM alpine:3.20
+ARG TARGETOS
+ARG TARGETARCH
 ARG ALPINE_VERSION=v3.20
 LABEL maintainer="squat <lserven@gmail.com>"
 RUN echo -e "https://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/main\nhttps://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/community" > /etc/apk/repositories && \
@@ -15,6 +32,6 @@ RUN echo -e "https://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/main\nh
 COPY --from=cni bridge host-local loopback portmap /opt/cni/bin/
 ADD https://raw.githubusercontent.com/kubernetes-sigs/iptables-wrappers/e139a115350974aac8a82ec4b815d2845f86997e/iptables-wrapper-installer.sh /
 RUN chmod 700 /iptables-wrapper-installer.sh && /iptables-wrapper-installer.sh --no-sanity-check
-COPY bin/linux/$GOARCH/kg /opt/bin/
-COPY bin/linux/$GOARCH/kgctl /opt/bin/
+COPY --from=builder /tmp/build/result/bin/"$TARGETOS"_"$TARGETARCH"/kg /opt/bin/kg
+COPY --from=builder /tmp/build/result/bin/"$TARGETOS"_"$TARGETARCH"/kgctl /opt/bin/kgctl
 ENTRYPOINT ["/opt/bin/kg"]

+ 21 - 275
Makefile

@@ -1,91 +1,22 @@
-export GO111MODULE=on
-.PHONY: push container clean container-name container-latest push-latest fmt lint test unit vendor header generate crd client deepcopy informer lister manifest manfest-latest manifest-annotate manifest manfest-latest manifest-annotate release gen-docs e2e
+.PHONY: fmt lint test unit generate crd client deepcopy informer lister gen-docs e2e
 
-OS ?= $(shell go env GOOS)
-ARCH ?= $(shell go env GOARCH)
-ALL_ARCH := amd64 arm arm64
-DOCKER_ARCH := "amd64" "arm v7" "arm64 v8"
-ifeq ($(OS),linux)
-    BINS := bin/$(OS)/$(ARCH)/kg bin/$(OS)/$(ARCH)/kgctl
-else
-    BINS := bin/$(OS)/$(ARCH)/kgctl
-endif
-RELEASE_BINS := $(addprefix bin/release/kgctl-, $(addprefix linux-, $(ALL_ARCH)) darwin-amd64 darwin-arm64 windows-amd64)
 PROJECT := kilo
 PKG := github.com/squat/$(PROJECT)
-REGISTRY ?= index.docker.io
-IMAGE ?= squat/$(PROJECT)
-FULLY_QUALIFIED_IMAGE := $(REGISTRY)/$(IMAGE)
-
-TAG := $(shell git describe --abbrev=0 --tags HEAD 2>/dev/null)
-COMMIT := $(shell git rev-parse HEAD)
-VERSION := $(COMMIT)
-ifneq ($(TAG),)
-    ifeq ($(COMMIT), $(shell git rev-list -n1 $(TAG)))
-        VERSION := $(TAG)
-    endif
-endif
-DIRTY := $(shell test -z "$$(git diff --shortstat 2>/dev/null)" || echo -dirty)
-VERSION := $(VERSION)$(DIRTY)
-LD_FLAGS := -ldflags '-X $(PKG)/pkg/version.Version=$(VERSION)'
-SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
 GO_FILES ?= $$(find . -name '*.go' -not -path './vendor/*')
 GO_PKGS ?= $$(go list ./... | grep -v "$(PKG)/vendor")
 
-CONTROLLER_GEN_BINARY := bin/controller-gen
-CLIENT_GEN_BINARY := bin/client-gen
-DOCS_GEN_BINARY := bin/docs-gen
-DEEPCOPY_GEN_BINARY := bin/deepcopy-gen
-INFORMER_GEN_BINARY := bin/informer-gen
-LISTER_GEN_BINARY := bin/lister-gen
-STATICCHECK_BINARY := bin/staticcheck
-EMBEDMD_BINARY := bin/embedmd
-KIND_BINARY := $(shell pwd)/bin/kind
-KUBECTL_BINARY := $(shell pwd)/bin/kubectl
-BASH_UNIT := $(shell pwd)/bin/bash_unit
-BASH_UNIT_FLAGS :=
-
-BUILD_IMAGE ?= golang:1.19.0
-BASE_IMAGE ?= alpine:3.20
-
-build: $(BINS)
-
-build-%:
-	@$(MAKE) --no-print-directory OS=$(word 1,$(subst -, ,$*)) ARCH=$(word 2,$(subst -, ,$*)) build
-
-container-latest-%:
-	@$(MAKE) --no-print-directory ARCH=$* container-latest
-
-container-%:
-	@$(MAKE) --no-print-directory ARCH=$* container
-
-push-latest-%:
-	@$(MAKE) --no-print-directory ARCH=$* push-latest
-
-push-%:
-	@$(MAKE) --no-print-directory ARCH=$* push
-
-all-build: $(addprefix build-$(OS)-, $(ALL_ARCH))
-
-all-container: $(addprefix container-, $(ALL_ARCH))
-
-all-push: $(addprefix push-, $(ALL_ARCH))
-
-all-container-latest: $(addprefix container-latest-, $(ALL_ARCH))
-
-all-push-latest: $(addprefix push-latest-, $(ALL_ARCH))
-
 generate: client deepcopy informer lister crd
 
 crd: manifests/crds.yaml
-manifests/crds.yaml: pkg/k8s/apis/kilo/v1alpha1/types.go $(CONTROLLER_GEN_BINARY)
-	$(CONTROLLER_GEN_BINARY) crd \
+manifests/crds.yaml: pkg/k8s/apis/kilo/v1alpha1/types.go
+	go tool controller-gen crd \
 	paths=./pkg/k8s/apis/kilo/... \
 	output:crd:stdout > $@
+	yamlfmt --formatter indentless_arrays=true manifests/crds.yaml
 
 client: pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
-pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(CLIENT_GEN_BINARY)
-	$(CLIENT_GEN_BINARY) \
+pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go
+	go tool client-gen \
 	--clientset-name versioned \
 	--input-base "" \
 	--input $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
@@ -99,8 +30,8 @@ pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/ki
 	go fmt ./pkg/k8s/clientset/...
 
 deepcopy: pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go
-pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(DEEPCOPY_GEN_BINARY)
-	$(DEEPCOPY_GEN_BINARY) \
+pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go
+	go tool deepcopy-gen \
 	--input-dirs ./$(@D) \
 	--go-header-file=.header \
 	--logtostderr \
@@ -111,8 +42,8 @@ pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go: .header pkg/k8s/apis/kilo/v
 	go fmt $@
 
 informer: pkg/k8s/informers/kilo/v1alpha1/peer.go
-pkg/k8s/informers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(INFORMER_GEN_BINARY)
-	$(INFORMER_GEN_BINARY) \
+pkg/k8s/informers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go
+	go tool informer-gen \
 	--input-dirs $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
 	--go-header-file=.header \
 	--logtostderr \
@@ -127,8 +58,8 @@ pkg/k8s/informers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/type
 	go fmt ./pkg/k8s/informers/...
 
 lister: pkg/k8s/listers/kilo/v1alpha1/peer.go
-pkg/k8s/listers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(LISTER_GEN_BINARY)
-	$(LISTER_GEN_BINARY) \
+pkg/k8s/listers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go
+	go tool lister-gen \
 	--input-dirs $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
 	--go-header-file=.header \
 	--logtostderr \
@@ -140,99 +71,27 @@ pkg/k8s/listers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.
 	go fmt ./pkg/k8s/listers/...
 
 gen-docs: generate docs/api.md docs/kg.md
-docs/api.md: pkg/k8s/apis/kilo/v1alpha1/types.go $(DOCS_GEN_BINARY)
-	$(DOCS_GEN_BINARY) $< > $@
-
-$(BINS): $(SRC) go.mod
-	@mkdir -p bin/$(word 2,$(subst /, ,$@))/$(word 3,$(subst /, ,$@))
-	@echo "building: $@"
-	@docker run --rm \
-	    -u $$(id -u):$$(id -g) \
-	    -v $$(pwd):/$(PROJECT) \
-	    -w /$(PROJECT) \
-	    $(BUILD_IMAGE) \
-	    /bin/sh -c " \
-	        GOARCH=$(word 3,$(subst /, ,$@)) \
-	        GOOS=$(word 2,$(subst /, ,$@)) \
-	        GOCACHE=/$(PROJECT)/.cache \
-		CGO_ENABLED=0 \
-		go build -mod=vendor -o $@ \
-		    $(LD_FLAGS) \
-		    ./cmd/$(@F)/... \
-	    "
+docs/api.md: pkg/k8s/apis/kilo/v1alpha1/types.go
+	go run ./cmd/docs-gen/... $< > $@
 
 fmt:
 	@echo $(GO_PKGS)
 	gofmt -w -s $(GO_FILES)
 
-lint: header $(STATICCHECK_BINARY)
-	@echo 'go vet $(GO_PKGS)'
-	@vet_res=$$(GO111MODULE=on go vet -mod=vendor $(GO_PKGS) 2>&1); if [ -n "$$vet_res" ]; then \
-		echo ""; \
-		echo "Go vet found issues. Please check the reported issues"; \
-		echo "and fix them if necessary before submitting the code for review:"; \
-		echo "$$vet_res"; \
-		exit 1; \
-	fi
-	@echo '$(STATICCHECK_BINARY) $(GO_PKGS)'
-	@lint_res=$$($(STATICCHECK_BINARY) $(GO_PKGS)); if [ -n "$$lint_res" ]; then \
-		echo ""; \
-		echo "Staticcheck found style issues. Please check the reported issues"; \
-		echo "and fix them if necessary before submitting the code for review:"; \
-		echo "$$lint_res"; \
-		exit 1; \
-	fi
-	@echo 'gofmt -d -s $(GO_FILES)'
-	@fmt_res=$$(gofmt -d -s $(GO_FILES)); if [ -n "$$fmt_res" ]; then \
-		echo ""; \
-		echo "Gofmt found style issues. Please check the reported issues"; \
-		echo "and fix them if necessary before submitting the code for review:"; \
-		echo "$$fmt_res"; \
-		exit 1; \
-	fi
+lint:
+	pre-commit run --all
 
 unit:
 	go test -mod=vendor --race ./...
 
 test: lint unit e2e
 
-$(KIND_BINARY):
-	curl -Lo $@ https://kind.sigs.k8s.io/dl/v0.11.1/kind-linux-$(ARCH)
-	chmod +x $@
-
-$(KUBECTL_BINARY):
-	curl -Lo $@ https://dl.k8s.io/release/v1.21.0/bin/linux/$(ARCH)/kubectl
-	chmod +x $@
-
-$(BASH_UNIT):
-	curl -Lo $@ https://raw.githubusercontent.com/pgrange/bash_unit/v1.7.2/bash_unit
-	chmod +x $@
+e2e:
+	KILO_IMAGE=squat/kilo:test bash_unit $(BASH_UNIT_FLAGS) ./e2e/setup.sh ./e2e/full-mesh.sh ./e2e/location-mesh.sh ./e2e/multi-cluster.sh ./e2e/handlers.sh ./e2e/kgctl.sh ./e2e/teardown.sh
 
-e2e: container $(KIND_BINARY) $(KUBECTL_BINARY) $(BASH_UNIT) bin/$(OS)/$(ARCH)/kgctl
-	KILO_IMAGE=$(IMAGE):$(ARCH)-$(VERSION) KIND_BINARY=$(KIND_BINARY) KUBECTL_BINARY=$(KUBECTL_BINARY) KGCTL_BINARY=$(shell pwd)/bin/$(OS)/$(ARCH)/kgctl $(BASH_UNIT) $(BASH_UNIT_FLAGS) ./e2e/setup.sh ./e2e/full-mesh.sh ./e2e/location-mesh.sh ./e2e/multi-cluster.sh ./e2e/handlers.sh ./e2e/kgctl.sh ./e2e/teardown.sh
-
-header: .header
-	@HEADER=$$(cat .header); \
-	HEADER_LEN=$$(wc -l .header | awk '{print $$1}'); \
-	FILES=; \
-	for f in $(GO_FILES); do \
-		for i in 0 1 2 3 4 5; do \
-			FILE=$$(t=$$(mktemp) && tail -n +$$i $$f > $$t && head -n $$HEADER_LEN $$t | sed "s/[0-9]\{4\}/YEAR/"); \
-			[ "$$FILE" = "$$HEADER" ] && continue 2; \
-		done; \
-		FILES="$$FILES$$f "; \
-	done; \
-	if [ -n "$$FILES" ]; then \
-		printf 'the following files are missing the license header: %s\n' "$$FILES"; \
-		exit 1; \
-	fi
-
-tmp/help.txt: bin/$(OS)/$(ARCH)/kg
-	mkdir -p tmp
-	bin//$(OS)/$(ARCH)/kg --help 2>&1 | head -n -1 > $@
-
-docs/kg.md: $(EMBEDMD_BINARY) tmp/help.txt
-	$(EMBEDMD_BINARY) -w $@
+docs/kg.md:
+	go run ./cmd/kg/... --help | head -n -2 > help.txt
+	go tool embedmd -w docs/kg.md
 
 website/docs/README.md: README.md
 	rm -rf website/static/img/graphs
@@ -250,116 +109,3 @@ website/docs/README.md: README.md
 website/build/index.html: website/docs/README.md docs/api.md
 	yarn --cwd website install
 	yarn --cwd website build
-
-container: .container-$(ARCH)-$(VERSION) container-name
-.container-$(ARCH)-$(VERSION): bin/linux/$(ARCH)/kg bin/linux/$(ARCH)/kgctl Dockerfile
-	@i=0; for a in $(ALL_ARCH); do [ "$$a" = $(ARCH) ] && break; i=$$((i+1)); done; \
-	ia=""; iv=""; \
-	j=0; for a in $(DOCKER_ARCH); do \
-	    [ "$$i" -eq "$$j" ] && ia=$$(echo "$$a" | awk '{print $$1}') && iv=$$(echo "$$a" | awk '{print $$2}') && break; j=$$((j+1)); \
-	done; \
-	SHA=$$(docker manifest inspect $(BASE_IMAGE) | jq '.manifests[] | select(.platform.architecture == "'$$ia'") | if .platform | has("variant") then select(.platform.variant == "'$$iv'") else . end | .digest' -r); \
-	docker build -t $(IMAGE):$(ARCH)-$(VERSION) --build-arg FROM=$(BASE_IMAGE)@$$SHA --build-arg GOARCH=$(ARCH) .
-	@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
-
-container-latest: .container-$(ARCH)-$(VERSION)
-	@docker tag $(IMAGE):$(ARCH)-$(VERSION) $(FULLY_QUALIFIED_IMAGE):$(ARCH)-latest
-	@echo "container: $(IMAGE):$(ARCH)-latest"
-
-container-name:
-	@echo "container: $(IMAGE):$(ARCH)-$(VERSION)"
-
-manifest: .manifest-$(VERSION) manifest-name
-.manifest-$(VERSION): Dockerfile $(addprefix push-, $(ALL_ARCH))
-	@docker manifest create --amend $(FULLY_QUALIFIED_IMAGE):$(VERSION) $(addsuffix -$(VERSION), $(addprefix $(FULLY_QUALIFIED_IMAGE):, $(ALL_ARCH)))
-	@$(MAKE) --no-print-directory manifest-annotate-$(VERSION)
-	@docker manifest push $(FULLY_QUALIFIED_IMAGE):$(VERSION) > $@
-
-manifest-latest: Dockerfile $(addprefix push-latest-, $(ALL_ARCH))
-	@docker manifest rm $(FULLY_QUALIFIED_IMAGE):latest || echo no old manifest
-	@docker manifest create --amend $(FULLY_QUALIFIED_IMAGE):latest $(addsuffix -latest, $(addprefix $(FULLY_QUALIFIED_IMAGE):, $(ALL_ARCH)))
-	@$(MAKE) --no-print-directory manifest-annotate-latest
-	@docker manifest push $(FULLY_QUALIFIED_IMAGE):latest
-	@echo "manifest: $(IMAGE):latest"
-
-manifest-annotate: manifest-annotate-$(VERSION)
-
-manifest-annotate-%:
-	@i=0; \
-	for a in $(ALL_ARCH); do \
-	    annotate=; \
-	    j=0; for da in $(DOCKER_ARCH); do \
-		if [ "$$j" -eq "$$i" ] && [ -n "$$da" ]; then \
-		    annotate="docker manifest annotate $(FULLY_QUALIFIED_IMAGE):$* $(FULLY_QUALIFIED_IMAGE):$$a-$* --os linux --arch"; \
-		    k=0; for ea in $$da; do \
-			[ "$$k" = 0 ] && annotate="$$annotate $$ea"; \
-			[ "$$k" != 0 ] && annotate="$$annotate --variant $$ea"; \
-			k=$$((k+1)); \
-		    done; \
-		    $$annotate; \
-		fi; \
-		j=$$((j+1)); \
-	    done; \
-	    i=$$((i+1)); \
-	done
-
-manifest-name:
-	@echo "manifest: $(IMAGE):$(VERSION)"
-
-push: .push-$(ARCH)-$(VERSION) push-name
-.push-$(ARCH)-$(VERSION): .container-$(ARCH)-$(VERSION)
-ifneq ($(REGISTRY),index.docker.io)
-	@docker tag $(IMAGE):$(ARCH)-$(VERSION) $(FULLY_QUALIFIED_IMAGE):$(ARCH)-$(VERSION)
-endif
-	@docker push $(FULLY_QUALIFIED_IMAGE):$(ARCH)-$(VERSION)
-	@docker images -q $(IMAGE):$(ARCH)-$(VERSION) > $@
-
-push-latest: container-latest
-	@docker push $(FULLY_QUALIFIED_IMAGE):$(ARCH)-latest
-	@echo "pushed: $(IMAGE):$(ARCH)-latest"
-
-push-name:
-	@echo "pushed: $(IMAGE):$(ARCH)-$(VERSION)"
-
-release: $(RELEASE_BINS)
-$(RELEASE_BINS):
-	@make OS=$(word 2,$(subst -, ,$(@F))) ARCH=$(word 3,$(subst -, ,$(@F)))
-	mkdir -p $(@D)
-	cp bin/$(word 2,$(subst -, ,$(@F)))/$(word 3,$(subst -, ,$(@F)))/kgctl $@
-
-clean: container-clean bin-clean
-	rm -rf .cache
-
-container-clean:
-	rm -rf .container-* .manifest-* .push-*
-
-bin-clean:
-	rm -rf bin
-
-vendor:
-	go mod tidy
-	go mod vendor
-
-$(CONTROLLER_GEN_BINARY):
-	go build -mod=vendor -o $@ sigs.k8s.io/controller-tools/cmd/controller-gen
-
-$(CLIENT_GEN_BINARY):
-	go build -mod=vendor -o $@ k8s.io/code-generator/cmd/client-gen
-
-$(DEEPCOPY_GEN_BINARY):
-	go build -mod=vendor -o $@ k8s.io/code-generator/cmd/deepcopy-gen
-
-$(INFORMER_GEN_BINARY):
-	go build -mod=vendor -o $@ k8s.io/code-generator/cmd/informer-gen
-
-$(LISTER_GEN_BINARY):
-	go build -mod=vendor -o $@ k8s.io/code-generator/cmd/lister-gen
-
-$(DOCS_GEN_BINARY): cmd/docs-gen/main.go
-	go build -mod=vendor -o $@ ./cmd/docs-gen
-
-$(STATICCHECK_BINARY):
-	go build -mod=vendor -o $@ honnef.co/go/tools/cmd/staticcheck
-
-$(EMBEDMD_BINARY):
-	go build -mod=vendor -o $@ github.com/campoy/embedmd

+ 3 - 11
cmd/kgctl/connect_linux.go

@@ -75,7 +75,8 @@ func connect() *cobra.Command {
 	}
 	cmd.Flags().IPNetVarP(&connectOpts.allowedIP, "allowed-ip", "a", *takeIPNet(net.ParseCIDR("10.10.10.10/32")), "Allowed IP of the peer.")
 	cmd.Flags().StringSliceVar(&allowedIPs, "allowed-ips", []string{}, "Additional allowed IPs of the cluster, e.g. the service CIDR.")
-	cmd.Flags().StringVar(&logLevel, "log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
+	cmd.Flags().StringVar(&logLevel, "log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", strings.Join(availableLogLevels, ", ")))
+	_ = cmd.RegisterFlagCompletionFunc("log-level", cobra.FixedCompletions(availableLogLevels, cobra.ShellCompDirectiveNoFileComp))
 	cmd.Flags().StringVar(&connectOpts.privateKey, "private-key", "", "Path to an existing WireGuard private key file.")
 	cmd.Flags().BoolVar(&connectOpts.cleanUp, "clean-up", true, "Should Kilo clean up the routes and interface when it shuts down?")
 	cmd.Flags().UintVar(&connectOpts.mtu, "mtu", uint(1420), "The MTU for the WireGuard interface.")
@@ -83,15 +84,6 @@ func connect() *cobra.Command {
 	cmd.Flags().StringVarP(&connectOpts.interfaceName, "interface", "i", mesh.DefaultKiloInterface, "Name of the Kilo interface to use; if it does not exist, it will be created.")
 	cmd.Flags().IntVar(&connectOpts.persistentKeepalive, "persistent-keepalive", 10, "How often should WireGuard send keepalives? Setting to 0 will disable sending keepalives.")
 
-	availableLogLevels = strings.Join([]string{
-		logLevelAll,
-		logLevelDebug,
-		logLevelInfo,
-		logLevelWarn,
-		logLevelError,
-		logLevelNone,
-	}, ", ")
-
 	return cmd
 }
 
@@ -114,7 +106,7 @@ func runConnect(cmd *cobra.Command, args []string) error {
 	case logLevelNone:
 		logger = level.NewFilter(logger, level.AllowNone())
 	default:
-		return fmt.Errorf("log level %s unknown; possible values are: %s", logLevel, availableLogLevels)
+		return fmt.Errorf("log level %s unknown; possible values are: %s", logLevel, strings.Join(availableLogLevels, ", "))
 	}
 	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
 	logger = log.With(logger, "caller", log.DefaultCaller)

+ 21 - 8
cmd/kgctl/main.go

@@ -43,22 +43,22 @@ const (
 )
 
 var (
-	availableBackends = strings.Join([]string{
+	availableBackends = []string{
 		k8s.Backend,
-	}, ", ")
-	availableGranularities = strings.Join([]string{
+	}
+	availableGranularities = []string{
 		string(mesh.LogicalGranularity),
 		string(mesh.FullGranularity),
 		string(mesh.AutoGranularity),
-	}, ", ")
-	availableLogLevels = strings.Join([]string{
+	}
+	availableLogLevels = []string{
 		logLevelAll,
 		logLevelDebug,
 		logLevelInfo,
 		logLevelWarn,
 		logLevelError,
 		logLevelNone,
-	}, ", ")
+	}
 	opts struct {
 		backend     mesh.Backend
 		granularity mesh.Granularity
@@ -72,6 +72,17 @@ var (
 )
 
 func runRoot(c *cobra.Command, _ []string) error {
+	p := c
+	for {
+		if p.Name() == "completion" || strings.HasPrefix(p.Name(), cobra.ShellCompRequestCmd) {
+			return nil
+		}
+		if !p.HasParent() {
+			break
+		}
+		p = p.Parent()
+	}
+
 	if opts.port < 1 || opts.port > 1<<16-1 {
 		return fmt.Errorf("invalid port: port mus be in range [%d:%d], but got %d", 1, 1<<16-1, opts.port)
 	}
@@ -118,8 +129,10 @@ func main() {
 		Version:           version.Version,
 		SilenceErrors:     true,
 	}
-	cmd.PersistentFlags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", availableBackends))
-	cmd.PersistentFlags().StringVar(&granularity, "mesh-granularity", string(mesh.AutoGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", availableGranularities))
+	cmd.PersistentFlags().StringVar(&backend, "backend", k8s.Backend, fmt.Sprintf("The backend for the mesh. Possible values: %s", strings.Join(availableBackends, ", ")))
+	_ = cmd.RegisterFlagCompletionFunc("backend", cobra.FixedCompletions(availableBackends, cobra.ShellCompDirectiveNoFileComp))
+	cmd.PersistentFlags().StringVar(&granularity, "mesh-granularity", string(mesh.AutoGranularity), fmt.Sprintf("The granularity of the network mesh to create. Possible values: %s", strings.Join(availableGranularities, ", ")))
+	_ = cmd.RegisterFlagCompletionFunc("mesh-granularity", cobra.FixedCompletions(availableGranularities, cobra.ShellCompDirectiveNoFileComp))
 	defaultKubeconfig := os.Getenv("KUBECONFIG")
 	if _, err := os.Stat(defaultKubeconfig); os.IsNotExist(err) {
 		defaultKubeconfig = filepath.Join(os.Getenv("HOME"), ".kube/config")

+ 28 - 0
cmd/kgctl/showconf.go

@@ -101,6 +101,20 @@ func showConfNode() *cobra.Command {
 		Short: "Show the WireGuard configuration for a node in the Kilo network",
 		RunE:  runShowConfNode,
 		Args:  cobra.ExactArgs(1),
+		ValidArgsFunction: func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
+			ns, err := opts.backend.Nodes().List()
+			if err != nil {
+				cobra.CompError(err.Error())
+				return nil, cobra.ShellCompDirectiveNoFileComp
+			}
+			completions := make([]string, 0, len(ns))
+			for _, n := range ns {
+				if n.Ready() {
+					completions = append(completions, n.Name)
+				}
+			}
+			return completions, cobra.ShellCompDirectiveNoFileComp
+		},
 	}
 }
 
@@ -110,6 +124,20 @@ func showConfPeer() *cobra.Command {
 		Short: "Show the WireGuard configuration for a peer in the Kilo network",
 		RunE:  runShowConfPeer,
 		Args:  cobra.ExactArgs(1),
+		ValidArgsFunction: func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
+			ps, err := opts.backend.Peers().List()
+			if err != nil {
+				cobra.CompError(err.Error())
+				return nil, cobra.ShellCompDirectiveNoFileComp
+			}
+			completions := make([]string, 0, len(ps))
+			for _, p := range ps {
+				if p.Ready() {
+					completions = append(completions, p.Name)
+				}
+			}
+			return completions, cobra.ShellCompDirectiveNoFileComp
+		},
 	}
 }
 

+ 121 - 0
flake.lock

@@ -0,0 +1,121 @@
+{
+  "nodes": {
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1761588595,
+        "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": "nixpkgs-lib"
+      },
+      "locked": {
+        "lastModified": 1765495779,
+        "narHash": "sha256-MhA7wmo/7uogLxiewwRRmIax70g6q1U/YemqTGoFHlM=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "5635c32d666a59ec9a55cab87e898889869f7b71",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "git-hooks-nix": {
+      "inputs": {
+        "flake-compat": "flake-compat",
+        "gitignore": "gitignore",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1765464257,
+        "narHash": "sha256-dixPWKiHzh80PtD0aLuxYNQ0xP+843dfXG/yM3OzaYQ=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "09e45f2598e1a8499c3594fe11ec2943f34fe509",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
+      }
+    },
+    "gitignore": {
+      "inputs": {
+        "nixpkgs": [
+          "git-hooks-nix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1765472234,
+        "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-lib": {
+      "locked": {
+        "lastModified": 1761765539,
+        "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-parts": "flake-parts",
+        "git-hooks-nix": "git-hooks-nix",
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}

+ 295 - 0
flake.nix

@@ -0,0 +1,295 @@
+{
+  description = "Kilo is a multi-cloud network overlay built on WireGuard and designed for Kubernetes (k8s + wg = kg)";
+
+  inputs = {
+    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+    flake-parts.url = "github:hercules-ci/flake-parts";
+    git-hooks-nix = {
+      url = "github:cachix/git-hooks.nix";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+  };
+
+  outputs =
+    { self, ... }@inputs:
+    inputs.flake-parts.lib.mkFlake { inherit inputs; } {
+      imports = [
+        inputs.git-hooks-nix.flakeModule
+      ];
+      systems = [
+        "x86_64-linux"
+        "aarch64-linux"
+        "aarch64-darwin"
+      ];
+      perSystem =
+        {
+          pkgs,
+          system,
+          config,
+          ...
+        }:
+        {
+          packages =
+            let
+              _version = builtins.getEnv "VERSION";
+              homepage = "https://github.com/squat/kilo";
+              base = pkgs.buildGoModule rec {
+                pname = "kilo";
+                version = if _version != "" then _version else toString (self.rev or self.dirtyRev or "unknown");
+                src = ./.;
+                vendorHash = null;
+                env.CGO_ENABLED = 0;
+                ldflags = [
+                  "-X github.com/squat/kilo/pkg/version.Version=${version}"
+                ];
+                nativeBuildInputs = [ pkgs.installShellFiles ];
+                meta = {
+                  inherit homepage;
+                };
+              };
+              kg = base.overrideAttrs {
+                pname = "kg";
+                subPackages = [
+                  "cmd/kg"
+                ];
+                postInstall = ''
+                  installShellCompletion --cmd kg \
+                    --bash <($out/bin/kg completion bash) \
+                    --fish <($out/bin/kg completion fish) \
+                    --zsh <($out/bin/kg completion zsh)
+                '';
+                meta.mainProgram = "kg";
+                meta.description = "kg is the Kilo agent that runs on every Kubernetes node in a Kilo mesh";
+              };
+
+              kgctl = base.overrideAttrs {
+                pname = "kgctl";
+                subPackages = [
+                  "cmd/kgctl"
+                ];
+                postInstall = ''
+                  installShellCompletion --cmd kgctl \
+                    --bash <($out/bin/kgctl completion bash) \
+                    --fish <($out/bin/kgctl completion fish) \
+                    --zsh <($out/bin/kgctl completion zsh)
+                '';
+                meta.mainProgram = "kgctl";
+                meta.description = "kgctl is Kilo's command line tool for inspecting and interacting with clusters: kgctl. It can be used to understand a mesh's topology, get the WireGuard configuration for a peer, or graph a cluster";
+
+              };
+
+              kilo = pkgs.symlinkJoin {
+                name = "kilo";
+                paths = [
+                  kg
+                  kgctl
+                ];
+                meta = {
+                  inherit homepage;
+                  description = "Kilo is a multi-cloud network overlay built on WireGuard and designed for Kubernetes (k8s + wg = kg)";
+                };
+              };
+
+            in
+            {
+              inherit kg kgctl kilo;
+              default = kilo;
+            }
+            // (builtins.listToAttrs (
+              map
+                (target: {
+                  name = "kg-cross-${target.os}-${target.arch}";
+                  value = kg.overrideAttrs {
+                    env.GOOS = target.os;
+                    env.GOARCH = target.arch;
+                    env.CGO_ENABLED = 0;
+                    checkPhase = false;
+                    postInstall = "";
+                  };
+                })
+                [
+                  {
+                    os = "linux";
+                    arch = "amd64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm";
+                  }
+                ]
+            ))
+            // (builtins.listToAttrs (
+              map
+                (target: {
+                  name = "kgctl-cross-${target.os}-${target.arch}";
+                  value = kgctl.overrideAttrs {
+                    env.GOOS = target.os;
+                    env.GOARCH = target.arch;
+                    env.CGO_ENABLED = 0;
+                    checkPhase = false;
+                    postInstall = "";
+                  };
+                })
+                [
+                  {
+                    os = "linux";
+                    arch = "amd64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm";
+                  }
+                  {
+                    os = "darwin";
+                    arch = "amd64";
+                  }
+                  {
+                    os = "darwin";
+                    arch = "arm64";
+                  }
+                  {
+                    os = "windows";
+                    arch = "amd64";
+                  }
+                ]
+            ))
+            // (builtins.listToAttrs (
+              map
+                (target: {
+                  name = "kilo-cross-${target.os}-${target.arch}";
+                  value = kilo.overrideAttrs {
+                    paths = [
+                      config.packages."kg-cross-${target.os}-${target.arch}"
+                      config.packages."kgctl-cross-${target.os}-${target.arch}"
+                    ];
+                  };
+                })
+                [
+                  {
+                    os = "linux";
+                    arch = "amd64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm64";
+                  }
+                  {
+                    os = "linux";
+                    arch = "arm";
+                  }
+                ]
+            ));
+
+          pre-commit = {
+            check.enable = true;
+            settings = {
+              src = ./.;
+              hooks = {
+                actionlint.enable = true;
+                nixfmt.enable = true;
+                nixfmt.excludes = [ "vendor" ];
+                gofmt.enable = true;
+                gofmt.excludes = [ "vendor" ];
+                golangci-lint.enable = true;
+                golangci-lint.excludes = [ "vendor" ];
+                golangci-lint.extraPackages = [ pkgs.go ];
+                govet.enable = true;
+                govet.excludes = [ "vendor" ];
+                shellcheck.enable = true;
+                shellcheck.excludes = [
+                  ".envrc"
+                  "vendor"
+                ];
+                yamlfmt.enable = true;
+                yamlfmt.args = [
+                  "--formatter"
+                  "indentless_arrays=true"
+                ];
+                yamlfmt.excludes = [
+                  ".github"
+                  "vendor"
+                ];
+                header = {
+                  enable = true;
+                  name = "Header";
+                  entry =
+                    let
+                      headerCheck = pkgs.writeShellApplication {
+                        name = "header-check";
+                        text = ''
+                          HEADER=$(cat ${./.header})
+                          HEADER_LEN=$(wc -l ${./.header} | awk '{print $1}')
+                          FILES=
+                          for f in "$@"; do 
+                              for i in 0 1 2 3 4 5; do 
+                                  FILE=$(tail -n +$i "$f" | ( head -n "$HEADER_LEN"; cat > /dev/null ) | sed "s/[0-9]\{4\}/YEAR/")
+                                  [ "$FILE" = "$HEADER" ] && continue 2
+                              done
+                              FILES="$FILES$f "
+                          done
+                          if [ -n "$FILES" ]; then \
+                              printf 'the following files are missing the license header: %s\n' "$FILES"; \
+                              exit 1
+                          fi
+                        '';
+                      };
+                    in
+                    pkgs.lib.getExe headerCheck;
+                  files = "\\.(go)$";
+                  excludes = [ "vendor" ];
+                };
+                kgMDGen = {
+                  enable = true;
+                  name = "kg.md";
+                  entry =
+                    let
+                      kgMDGen = pkgs.writeShellApplication {
+                        name = "kgmdgen";
+                        text = ''
+                          go run ./cmd/kg/... --help | head -n -2 > help.txt
+                          go tool embedmd -d docs/kg.md
+                        '';
+                      };
+                    in
+                    pkgs.lib.getExe kgMDGen;
+                  files = "^README\\.md$";
+                  extraPackages = [ pkgs.go ];
+                };
+              };
+            };
+          };
+
+          devShells = {
+            default = pkgs.mkShell {
+              inherit (config.pre-commit.devShell) shellHook;
+              packages =
+                with pkgs;
+                [
+                  bash_unit
+                  (config.packages.kgctl.overrideAttrs rec {
+                    version = "dev";
+                    __intentionallyOverridingVersion = true;
+                    ldflags = [
+                      "-X github.com/squat/kilo/pkg/version.Version=${version}"
+                    ];
+                  })
+                  gettext # provides envsubst
+                  go
+                  kind
+                  kubectl
+                  yarn
+                ]
+                ++ config.pre-commit.settings.enabledPackages;
+            };
+          };
+        };
+    };
+}