diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml new file mode 100644 index 00000000..d82f3268 --- /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 d135241d..553a70c9 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 c6a2cf26..8256f860 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 d4c12c88..0feaaf66 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 ba8b44f9..b6f681fa 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 e84e11e2..c7b332aa 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 249a0442..93aa797a 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 00000000..c3a80893 --- /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 00000000..954ab37f --- /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 00000000..225de614 --- /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 00000000..ed480bbf --- /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 9282c8bc..1918b3de 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,7 @@ golines nodePackages.prettier goreleaser + nfpm gotestsum # Protobuf dependencies