diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f40bb67..db25a04 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,6 +7,7 @@ on: permissions: contents: write + packages: write jobs: release-linux: @@ -48,3 +49,78 @@ jobs: args: release --clean --config .goreleaser-darwin.yaml --skip=validate env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release-containers: + runs-on: ubuntu-latest + strategy: + matrix: + variant: [alpine, debian, ubuntu, scratch] + include: + - variant: alpine + is_default: true + - variant: debian + is_default: false + - variant: ubuntu + is_default: false + - variant: scratch + is_default: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: DeterminateSystems/nix-installer-action@v17 + + - uses: nix-community/cache-nix-action@v6 + with: + primary-key: nix-${{ runner.os }}-${{ hashFiles('flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}- + + - name: login to ghcr + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: build container + run: nix build ".#snitch-${{ matrix.variant }}" --print-out-paths + + - name: load and push container + env: + VERSION: ${{ github.ref_name }} + REPO: ghcr.io/${{ github.repository_owner }}/snitch + VARIANT: ${{ matrix.variant }} + IS_DEFAULT: ${{ matrix.is_default }} + run: | + VERSION="${VERSION#v}" + + docker load < result + + IMAGE_TAG=$(docker images --format '{{.Repository}}:{{.Tag}}' | grep "snitch:.*-${VARIANT}" | head -1) + + if [ -z "$IMAGE_TAG" ]; then + echo "error: could not find loaded image for ${VARIANT}" + exit 1 + fi + + docker tag "$IMAGE_TAG" "${REPO}:${VERSION}-${VARIANT}" + docker tag "$IMAGE_TAG" "${REPO}:latest-${VARIANT}" + docker push "${REPO}:${VERSION}-${VARIANT}" + docker push "${REPO}:latest-${VARIANT}" + + if [ "$IS_DEFAULT" = "true" ]; then + docker tag "$IMAGE_TAG" "${REPO}:${VERSION}" + docker tag "$IMAGE_TAG" "${REPO}:latest" + docker push "${REPO}:${VERSION}" + docker push "${REPO}:latest" + fi + + - name: summary + env: + VERSION: ${{ github.ref_name }} + REPO: ghcr.io/${{ github.repository_owner }}/snitch + VARIANT: ${{ matrix.variant }} + run: | + VERSION="${VERSION#v}" + echo "pushed ${REPO}:${VERSION}-${VARIANT}" >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index ff139aa..19fa645 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,47 @@ curl -sSL https://raw.githubusercontent.com/karol-broda/snitch/master/install.sh > **macos:** the install script automatically removes the quarantine attribute (`com.apple.quarantine`) from the binary to allow it to run without gatekeeper warnings. to disable this, set `KEEP_QUARANTINE=1`. +### docker + +pre-built oci images available from github container registry: + +```bash +# pull from ghcr.io +docker pull ghcr.io/karol-broda/snitch:latest # alpine (default) +docker pull ghcr.io/karol-broda/snitch:latest-alpine # alpine (~17MB) +docker pull ghcr.io/karol-broda/snitch:latest-scratch # minimal, binary only (~9MB) +docker pull ghcr.io/karol-broda/snitch:latest-debian # debian trixie +docker pull ghcr.io/karol-broda/snitch:latest-ubuntu # ubuntu 24.04 + +# or use a specific version +docker pull ghcr.io/karol-broda/snitch:0.2.0-alpine +``` + +alternatively, build locally via nix flake: + +```bash +nix build github:karol-broda/snitch#snitch-alpine +docker load < result +``` + +**running the container:** + +```bash +# basic usage - sees host sockets but not process names +docker run --rm --net=host snitch:latest ls + +# full info - includes PID, process name, user +docker run --rm --net=host --pid=host --cap-add=SYS_PTRACE snitch:latest ls +``` + +| flag | purpose | +|------|---------| +| `--net=host` | share host network namespace (required to see host connections) | +| `--pid=host` | share host pid namespace (needed for process info) | +| `--cap-add=SYS_PTRACE` | read process details from `/proc/` | + +> **note:** `CAP_NET_ADMIN` and `CAP_NET_RAW` are not required. snitch reads from `/proc/net/*` which doesn't need special network capabilities. + ### binary download from [releases](https://github.com/karol-broda/snitch/releases): diff --git a/flake.nix b/flake.nix index 47711b2..803cb9b 100644 --- a/flake.nix +++ b/flake.nix @@ -80,11 +80,18 @@ in { packages = eachSystem (system: - let pkgs = pkgsFor system; in - { - default = mkSnitch pkgs; + let + pkgs = pkgsFor system; snitch = mkSnitch pkgs; - } + # containers only available on linux + containers = if pkgs.stdenv.isLinux + then import ./nix/containers.nix { inherit pkgs snitch; } + else { }; + in + { + default = snitch; + inherit snitch; + } // containers ); devShells = eachSystem (system: @@ -94,7 +101,7 @@ in { default = pkgs.mkShell { - packages = [ go pkgs.git pkgs.vhs ]; + packages = [ go pkgs.git pkgs.vhs pkgs.nix-prefetch-docker ]; env.GOTOOLCHAIN = "local"; shellHook = '' echo "go toolchain: $(go version)" diff --git a/nix/containers.nix b/nix/containers.nix new file mode 100644 index 0000000..a904df6 --- /dev/null +++ b/nix/containers.nix @@ -0,0 +1,121 @@ +# oci container definitions for snitch +# builds containers based on different base images: alpine, debian trixie, ubuntu +# +# base images are pinned by imageDigest (immutable content hash), not by tag. +# even if the upstream tag gets a new image, builds remain reproducible. +# +# to update base image hashes, run: +# nix-prefetch-docker --image-name alpine --image-tag 3.21 +# nix-prefetch-docker --image-name debian --image-tag trixie-slim +# nix-prefetch-docker --image-name ubuntu --image-tag 24.04 +# +# this outputs both imageDigest and sha256 values needed below +{ pkgs, snitch }: +let + commonConfig = { + name = "snitch"; + tag = snitch.version; + config = { + Entrypoint = [ "${snitch}/bin/snitch" ]; + Env = [ "PATH=/bin" ]; + Labels = { + "org.opencontainers.image.title" = "snitch"; + "org.opencontainers.image.description" = "a friendlier ss/netstat for humans"; + "org.opencontainers.image.source" = "https://github.com/karol-broda/snitch"; + "org.opencontainers.image.licenses" = "MIT"; + }; + }; + }; + + # alpine-based container + alpine = pkgs.dockerTools.pullImage { + imageName = "alpine"; + imageDigest = "sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c"; + sha256 = "sha256-WNbRh44zld3lZtKARhdeWFte9JKgD2bgCuKzETWgGr8="; + finalImageName = "alpine"; + finalImageTag = "3.21"; + }; + + # debian trixie (testing) based container + debianTrixie = pkgs.dockerTools.pullImage { + imageName = "debian"; + imageDigest = "sha256:e711a7b30ec1261130d0a121050b4ed81d7fb28aeabcf4ea0c7876d4e9f5aca2"; + sha256 = "sha256-W/9A7aaPXFCmmg+NTSrFYL+QylsAgfnvkLldyI18tqU="; + finalImageName = "debian"; + finalImageTag = "trixie-slim"; + }; + + # ubuntu based container + ubuntu = pkgs.dockerTools.pullImage { + imageName = "ubuntu"; + imageDigest = "sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54"; + sha256 = "sha256-0j8xM+mECrBBHv7ZqofiRaeSoOXFBtLYjgnKivQztS0="; + finalImageName = "ubuntu"; + finalImageTag = "24.04"; + }; + + # scratch container (minimal, just the snitch binary) + scratch = pkgs.dockerTools.buildImage { + name = "snitch"; + tag = "${snitch.version}-scratch"; + copyToRoot = pkgs.buildEnv { + name = "snitch-root"; + paths = [ snitch ]; + pathsToLink = [ "/bin" ]; + }; + config = commonConfig.config; + }; + +in +{ + snitch-alpine = pkgs.dockerTools.buildImage { + name = "snitch"; + tag = "${snitch.version}-alpine"; + fromImage = alpine; + copyToRoot = pkgs.buildEnv { + name = "snitch-root"; + paths = [ snitch ]; + pathsToLink = [ "/bin" ]; + }; + config = commonConfig.config; + }; + + snitch-debian = pkgs.dockerTools.buildImage { + name = "snitch"; + tag = "${snitch.version}-debian"; + fromImage = debianTrixie; + copyToRoot = pkgs.buildEnv { + name = "snitch-root"; + paths = [ snitch ]; + pathsToLink = [ "/bin" ]; + }; + config = commonConfig.config; + }; + + snitch-ubuntu = pkgs.dockerTools.buildImage { + name = "snitch"; + tag = "${snitch.version}-ubuntu"; + fromImage = ubuntu; + copyToRoot = pkgs.buildEnv { + name = "snitch-root"; + paths = [ snitch ]; + pathsToLink = [ "/bin" ]; + }; + config = commonConfig.config; + }; + + snitch-scratch = scratch; + + oci-default = pkgs.dockerTools.buildImage { + name = "snitch"; + tag = snitch.version; + fromImage = alpine; + copyToRoot = pkgs.buildEnv { + name = "snitch-root"; + paths = [ snitch ]; + pathsToLink = [ "/bin" ]; + }; + config = commonConfig.config; + }; +} +