From b684ac06680117b61a6155915380002addd385d8 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 28 Mar 2023 18:43:33 +0200 Subject: [PATCH] Simplify goreleaser, package deb and rpm This commit simplifies the goreleaser configuration and then adds nfpm support which allows us to build .deb and .rpm for each of the ARCH we support. The deb and rpm packages adds systemd services and users, creates directories etc and should in general give the user a working environment. We should be able to remove a lot of the complicated, PEBCAK inducing documentation after this. Signed-off-by: Kristoffer Dalby --- .github/workflows/release-docker.yml | 138 +++++++++++++++++++++++ .github/workflows/release.yml | 129 --------------------- .gitignore | 1 + .goreleaser.yml | 111 +++++++++--------- CHANGELOG.md | 2 + cmd/headscale/headscale_test.go | 4 +- config-example.yaml | 18 +-- docs/packaging/README.md | 5 + docs/packaging/headscale.systemd.service | 52 +++++++++ docs/packaging/postinstall.sh | 85 ++++++++++++++ docs/packaging/postremove.sh | 15 +++ flake.nix | 1 + 12 files changed, 364 insertions(+), 197 deletions(-) create mode 100644 .github/workflows/release-docker.yml create mode 100644 docs/packaging/README.md create mode 100644 docs/packaging/headscale.systemd.service create mode 100644 docs/packaging/postinstall.sh create mode 100644 docs/packaging/postremove.sh diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml new file mode 100644 index 0000000000..d82f3268e1 --- /dev/null +++ b/.github/workflows/release-docker.yml @@ -0,0 +1,138 @@ +--- +name: Release Docker + +on: + push: + tags: + - "*" # triggers only if push new tag version + workflow_dispatch: + +jobs: + docker-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU for multiple platforms + uses: docker/setup-qemu-action@master + with: + platforms: arm64,amd64 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + # list of Docker images to use as base name for tags + images: | + ${{ secrets.DOCKERHUB_USERNAME }}/headscale + ghcr.io/${{ github.repository_owner }}/headscale + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + type=raw,value=develop + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + context: . + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + build-args: | + VERSION=${{ steps.meta.outputs.version }} + - name: Prepare cache for next build + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + docker-debug-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Set up QEMU for multiple platforms + uses: docker/setup-qemu-action@master + with: + platforms: arm64,amd64 + - name: Cache Docker layers + uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache-debug + key: ${{ runner.os }}-buildx-debug-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx-debug- + - name: Docker meta + id: meta-debug + uses: docker/metadata-action@v3 + with: + # list of Docker images to use as base name for tags + images: | + ${{ secrets.DOCKERHUB_USERNAME }}/headscale + ghcr.io/${{ github.repository_owner }}/headscale + flavor: | + suffix=-debug,onlatest=true + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + type=raw,value=develop + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + push: true + context: . + file: Dockerfile.debug + tags: ${{ steps.meta-debug.outputs.tags }} + labels: ${{ steps.meta-debug.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=local,src=/tmp/.buildx-cache-debug + cache-to: type=local,dest=/tmp/.buildx-cache-debug-new + build-args: | + VERSION=${{ steps.meta-debug.outputs.version }} + - name: Prepare cache for next build + run: | + rm -rf /tmp/.buildx-cache-debug + mv /tmp/.buildx-cache-debug-new /tmp/.buildx-cache-debug diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d135241d80..553a70c9ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,132 +22,3 @@ jobs: run: nix develop --command -- goreleaser release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - docker-release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Set up QEMU for multiple platforms - uses: docker/setup-qemu-action@master - with: - platforms: arm64,amd64 - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - # list of Docker images to use as base name for tags - images: | - ${{ secrets.DOCKERHUB_USERNAME }}/headscale - ghcr.io/${{ github.repository_owner }}/headscale - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,value=develop - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Login to GHCR - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - push: true - context: . - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache-new - build-args: | - VERSION=${{ steps.meta.outputs.version }} - - name: Prepare cache for next build - run: | - rm -rf /tmp/.buildx-cache - mv /tmp/.buildx-cache-new /tmp/.buildx-cache - - docker-debug-release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Set up QEMU for multiple platforms - uses: docker/setup-qemu-action@master - with: - platforms: arm64,amd64 - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache-debug - key: ${{ runner.os }}-buildx-debug-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx-debug- - - name: Docker meta - id: meta-debug - uses: docker/metadata-action@v3 - with: - # list of Docker images to use as base name for tags - images: | - ${{ secrets.DOCKERHUB_USERNAME }}/headscale - ghcr.io/${{ github.repository_owner }}/headscale - flavor: | - suffix=-debug,onlatest=true - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - type=raw,value=develop - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Login to GHCR - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v2 - with: - push: true - context: . - file: Dockerfile.debug - tags: ${{ steps.meta-debug.outputs.tags }} - labels: ${{ steps.meta-debug.outputs.labels }} - platforms: linux/amd64,linux/arm64 - cache-from: type=local,src=/tmp/.buildx-cache-debug - cache-to: type=local,dest=/tmp/.buildx-cache-debug-new - build-args: | - VERSION=${{ steps.meta-debug.outputs.version }} - - name: Prepare cache for next build - run: | - rm -rf /tmp/.buildx-cache-debug - mv /tmp/.buildx-cache-debug-new /tmp/.buildx-cache-debug diff --git a/.gitignore b/.gitignore index c6a2cf261b..8256f8608a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ # Dependency directories (remove the comment below to include it) # vendor/ +dist/ /headscale config.json config.yaml diff --git a/.goreleaser.yml b/.goreleaser.yml index d4c12c88cc..0feaaf669e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,15 +7,21 @@ release: prerelease: auto builds: - - id: darwin-amd64 + - id: headscale main: ./cmd/headscale/headscale.go mod_timestamp: "{{ .CommitTimestamp }}" env: - CGO_ENABLED=0 - goos: - - darwin - goarch: - - amd64 + targets: + - darwin_amd64 + - darwin_arm64 + - freebsd_amd64 + - linux_386 + - linux_amd64 + - linux_arm64 + - linux_arm_5 + - linux_arm_6 + - linux_arm_7 flags: - -mod=readonly ldflags: @@ -23,60 +29,59 @@ builds: tags: - ts2019 - - id: darwin-arm64 - main: ./cmd/headscale/headscale.go - mod_timestamp: "{{ .CommitTimestamp }}" - env: - - CGO_ENABLED=0 - goos: - - darwin - goarch: - - arm64 - flags: - - -mod=readonly - ldflags: - - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} - tags: - - ts2019 - - - id: linux-amd64 - mod_timestamp: "{{ .CommitTimestamp }}" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - main: ./cmd/headscale/headscale.go - ldflags: - - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} - tags: - - ts2019 - - - id: linux-arm64 - mod_timestamp: "{{ .CommitTimestamp }}" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - arm64 - main: ./cmd/headscale/headscale.go - ldflags: - - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} - tags: - - ts2019 - archives: - id: golang-cross builds: - - darwin-amd64 - - darwin-arm64 - - linux-amd64 - - linux-arm64 + - darwin_amd64 + - darwin_arm64 + - freebsd_amd64 + - linux_386 + - linux_amd64 + - linux_arm64 + - linux_arm_5 + - linux_arm_6 + - linux_arm_7 name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" format: binary +nfpms: + # Configure nFPM for .deb and .rpm releases + # + # See https://nfpm.goreleaser.com/configuration/ + # and https://goreleaser.com/customization/nfpm/ + # + # Useful tools for debugging .debs: + # List file contents: dpkg -c dist/headscale...deb + # Package metadata: dpkg --info dist/headscale....deb + # + - builds: + - headscale + package_name: headscale + priority: optional + vendor: headscale + maintainer: Kristoffer Dalby + homepage: https://github.com/juanfont/headscale + license: BSD + bindir: /usr/bin + formats: + - deb + - rpm + contents: + - src: ./config-example.yaml + dst: /etc/headscale/config.yaml + type: config|noreplace + file_info: + mode: 0644 + - src: ./docs/packaging/headscale.systemd.service + dst: /etc/systemd/system/headscale.service + - dst: /var/lib/headscale + type: dir + - dst: /var/run/headscale + type: dir + scripts: + postinstall: ./docs/packaging/postinstall.sh + postremove: ./docs/packaging/postremove.sh + checksum: name_template: "checksums.txt" snapshot: diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8b44f9d8..b6f681fac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Changes +- Add `.deb` and `.rpm` packages to release process [#1297](https://github.com/juanfont/headscale/pull/1297) +- Add 32-bit Arm platforms to release process [#1297](https://github.com/juanfont/headscale/pull/1297) - Fix longstanding bug that would prevent "\*" from working properly in ACLs (issue [#699](https://github.com/juanfont/headscale/issues/699)) [#1279](https://github.com/juanfont/headscale/pull/1279) ## 0.21.0 (2023-03-20) diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index e84e11e2ac..c7b332aac0 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -58,7 +58,7 @@ func (*Suite) TestConfigFileLoading(c *check.C) { c.Assert(viper.GetString("listen_addr"), check.Equals, "127.0.0.1:8080") c.Assert(viper.GetString("metrics_listen_addr"), check.Equals, "127.0.0.1:9090") c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3") - c.Assert(viper.GetString("db_path"), check.Equals, "./db.sqlite") + c.Assert(viper.GetString("db_path"), check.Equals, "/var/lib/headscale/db.sqlite") c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "") c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") @@ -101,7 +101,7 @@ func (*Suite) TestConfigLoading(c *check.C) { c.Assert(viper.GetString("listen_addr"), check.Equals, "127.0.0.1:8080") c.Assert(viper.GetString("metrics_listen_addr"), check.Equals, "127.0.0.1:9090") c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3") - c.Assert(viper.GetString("db_path"), check.Equals, "./db.sqlite") + c.Assert(viper.GetString("db_path"), check.Equals, "/var/lib/headscale/db.sqlite") c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "") c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") diff --git a/config-example.yaml b/config-example.yaml index 249a0442e5..93aa797ac1 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -44,9 +44,7 @@ grpc_allow_insecure: false # and Tailscale clients. # The private key file will be autogenerated if it's missing. # -# For production: -# /var/lib/headscale/private.key -private_key_path: ./private.key +private_key_path: /var/lib/headscale/private.key # The Noise section includes specific configuration for the # TS2021 Noise protocol @@ -55,10 +53,7 @@ noise: # traffic between headscale and Tailscale clients when # using the new Noise-based protocol. It must be different # from the legacy private key. - # - # For production: - # private_key_path: /var/lib/headscale/noise_private.key - private_key_path: ./noise_private.key + private_key_path: /var/lib/headscale/noise_private.key # List of IP prefixes to allocate tailaddresses from. # Each prefix consists of either an IPv4 or IPv6 address, @@ -137,8 +132,7 @@ node_update_check_interval: 10s db_type: sqlite3 # For production: -# db_path: /var/lib/headscale/db.sqlite -db_path: ./db.sqlite +db_path: /var/lib/headscale/db.sqlite # # Postgres config # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank. @@ -172,8 +166,7 @@ tls_letsencrypt_hostname: "" # Path to store certificates and metadata needed by # letsencrypt # For production: -# tls_letsencrypt_cache_dir: /var/lib/headscale/cache -tls_letsencrypt_cache_dir: ./cache +tls_letsencrypt_cache_dir: /var/lib/headscale/cache # Type of ACME challenge to use, currently supported types: # HTTP-01 or TLS-ALPN-01 @@ -263,8 +256,7 @@ dns_config: # Unix socket used for the CLI to connect without authentication # Note: for production you will want to set this to something like: -# unix_socket: /var/run/headscale.sock -unix_socket: ./headscale.sock +unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770" # # headscale supports experimental OpenID connect support, diff --git a/docs/packaging/README.md b/docs/packaging/README.md new file mode 100644 index 0000000000..c3a80893a1 --- /dev/null +++ b/docs/packaging/README.md @@ -0,0 +1,5 @@ +# Packaging + +We use [nFPM](https://nfpm.goreleaser.com/) for making `.deb`, `.rpm` and `.apk`. + +This folder contains files we need to package with these releases. diff --git a/docs/packaging/headscale.systemd.service b/docs/packaging/headscale.systemd.service new file mode 100644 index 0000000000..954ab37fd9 --- /dev/null +++ b/docs/packaging/headscale.systemd.service @@ -0,0 +1,52 @@ +[Unit] +After=syslog.target +After=network.target +Description=headscale coordination server for Tailscale +X-Restart-Triggers=/etc/headscale/config.yaml + +[Service] +Type=simple +User=headscale +Group=headscale +ExecStart=/usr/bin/headscale serve +Restart=always +RestartSec=5 + +WorkingDirectory=/var/lib/headscale +ReadWritePaths=/var/lib/headscale /var/run + +AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_CHOWN +CapabilityBoundingSet=CAP_CHOWN +LockPersonality=true +NoNewPrivileges=true +PrivateDevices=true +PrivateMounts=true +PrivateTmp=true +ProcSubset=pid +ProtectClock=true +ProtectControlGroups=true +ProtectHome=true +ProtectHome=yes +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible +ProtectSystem=strict +RemoveIPC=true +RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +RuntimeDirectory=headscale +RuntimeDirectoryMode=0750 +StateDirectory=headscale +StateDirectoryMode=0750 +SystemCallArchitectures=native +SystemCallFilter=@chown +SystemCallFilter=@system-service +SystemCallFilter=~@privileged +UMask=0077 + +[Install] +WantedBy=multi-user.target diff --git a/docs/packaging/postinstall.sh b/docs/packaging/postinstall.sh new file mode 100644 index 0000000000..225de614e4 --- /dev/null +++ b/docs/packaging/postinstall.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# Determine OS platform +# shellcheck source=/dev/null +. /etc/os-release + +HEADSCALE_EXE="/usr/bin/headscale" +BSD_HIER="" +HEADSCALE_RUN_DIR="/var/run/headscale" +HEADSCALE_USER="headscale" +HEADSCALE_GROUP="headscale" + +ensure_sudo() { + if [ "$(id -u)" = "0" ]; then + echo "Sudo permissions detected" + else + echo "No sudo permission detected, please run as sudo" + exit 1 + fi +} + +ensure_headscale_path() { + if [ ! -f "$HEADSCALE_EXE" ]; then + echo "headscale not in default path, exiting..." + exit 1 + fi + + printf "Found headscale %s\n" "$HEADSCALE_EXE" +} + +create_headscale_user() { + printf "PostInstall: Adding headscale user %s\n" "$HEADSCALE_USER" + useradd -s /bin/sh -c "headscale default user" headscale +} + +create_headscale_group() { + if command -V systemctl >/dev/null 2>&1; then + printf "PostInstall: Adding headscale group %s\n" "$HEADSCALE_GROUP" + groupadd "$HEADSCALE_GROUP" + + printf "PostInstall: Adding headscale user %s to group %s\n" "$HEADSCALE_USER" "$HEADSCALE_GROUP" + usermod -a -G "$HEADSCALE_GROUP" "$HEADSCALE_USER" + fi + + if [ "$ID" = "alpine" ]; then + printf "PostInstall: Adding headscale group %s\n" "$HEADSCALE_GROUP" + addgroup "$HEADSCALE_GROUP" + + printf "PostInstall: Adding headscale user %s to group %s\n" "$HEADSCALE_USER" "$HEADSCALE_GROUP" + addgroup "$HEADSCALE_USER" "$HEADSCALE_GROUP" + fi +} + +create_run_dir() { + printf "PostInstall: Creating headscale run directory \n" + mkdir -p "$HEADSCALE_RUN_DIR" + + printf "PostInstall: Modifying group ownership of headscale run directory \n" + chown "$HEADSCALE_USER":"$HEADSCALE_GROUP" "$HEADSCALE_RUN_DIR" +} + +summary() { + echo "----------------------------------------------------------------------" + echo " headscale package has been successfully installed." + echo "" + echo " Please follow the next steps to start the software:" + echo "" + echo " sudo systemctl start headscale" + echo "" + echo " Configuration settings can be adjusted here:" + echo " ${BSD_HIER}/etc/headscale/config.yaml" + echo "" + echo "----------------------------------------------------------------------" +} + +# +# Main body of the script +# +{ + ensure_sudo + ensure_headscale_path + create_headscale_user + create_headscale_group + create_run_dir + summary +} diff --git a/docs/packaging/postremove.sh b/docs/packaging/postremove.sh new file mode 100644 index 0000000000..ed480bbf1f --- /dev/null +++ b/docs/packaging/postremove.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Determine OS platform +# shellcheck source=/dev/null +. /etc/os-release + +if command -V systemctl >/dev/null 2>&1; then + echo "Stop and disable headscale service" + systemctl stop headscale >/dev/null 2>&1 || true + systemctl disable headscale >/dev/null 2>&1 || true + echo "Running daemon-reload" + systemctl daemon-reload || true +fi + +echo "Removing run directory" +rm -rf "/var/run/headscale.sock" diff --git a/flake.nix b/flake.nix index 9282c8bc02..1918b3de84 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,7 @@ golines nodePackages.prettier goreleaser + nfpm gotestsum # Protobuf dependencies