From 632a47d56f292ee19b84f6630d9236332613ec08 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Thu, 9 Jul 2020 19:54:53 +0300 Subject: [PATCH] CI revamp part 2 (#1875) * Added Makefile * Updated README with the new information * Updated Github Actions * Added FreeBSD ARM builds * Prepared to our internal CI * Improved the auto-update version check --- .github/workflows/build.yml | 139 +++++----------------- .github/workflows/gosum.yml | 44 ------- .github/workflows/lint.yml | 47 ++++++++ .gometalinter.json | 29 ----- .goreleaser.yml | 26 ++++- Dockerfile | 7 +- Makefile | 222 ++++++++++++++++++++++++++++++++++++ README.md | 38 +++--- go.sum | 1 + home/control_update.go | 215 ++++++++++++++++++---------------- home/home.go | 17 ++- 11 files changed, 472 insertions(+), 313 deletions(-) delete mode 100644 .github/workflows/gosum.yml create mode 100644 .github/workflows/lint.yml delete mode 100644 .gometalinter.json create mode 100644 Makefile diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae4eae46..0c2fdebb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,13 @@ name: build +env: + GO_VERSION: 1.14 + NODE_VERSION: 13 + on: push: branches: - - master + - '*' tags: - v* pull_request: @@ -26,16 +30,19 @@ jobs: - name: Checkout uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.14 + go-version: ${{ env.GO_VERSION }} + - name: Set up Node uses: actions/setup-node@v1 with: - node-version: 13 + node-version: ${{ env.NODE_VERSION }} - name: Set up Go modules cache uses: actions/cache@v2 @@ -58,33 +65,14 @@ jobs: restore-keys: | ${{ runner.os }}-node- - - name: Run node build-prod + name: Run make ci shell: bash run: | - npm --prefix client ci - npm --prefix client run build-prod - - - name: Download modules and generate - shell: bash - run: | - go mod download - go generate ./... - - - name: Golangci-lint - if: matrix.os != 'windows-latest' - shell: bash - run: | - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.23.8 - golangci-lint --version - golangci-lint run - - - name: Go test - run: | - go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... + make ci - name: Upload coverage uses: codecov/codecov-action@v1 - if: success() + if: success() && matrix.os == 'ubuntu-latest' with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.txt @@ -96,26 +84,18 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - - name: Prepare - id: prepare - run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then - echo ::set-output name=tag_name::${GITHUB_REF#refs/tags/} - echo ::set-output name=channel::release - else - echo ::set-output name=channel::none - fi + with: + fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.14 + go-version: ${{ env.GO_VERSION }} - name: Set up Node uses: actions/setup-node@v1 with: - node-version: 13 + node-version: ${{ env.NODE_VERSION }} - name: Set up Go modules cache uses: actions/cache@v2 @@ -142,98 +122,30 @@ jobs: run: | sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft - - name: Run node build-prod + name: Set up GoReleaser run: | - npm --prefix client ci - npm --prefix client run build-prod + curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh - - name: Snapcraft Login - if: success() && startsWith(github.ref, 'refs/tags/v') - env: - SNAPCRAFT_LOGIN: ${{ secrets.SNAPCRAFT_LOGIN }} + name: Run snapshot build run: | - snapcraft login --with <(echo "$SNAPCRAFT_LOGIN") - - - name: GoReleaser - uses: goreleaser/goreleaser-action@v2 - with: - version: latest - args: release --parallelism 2 - env: - CHANNEL: ${{ steps.prepare.outputs.channel }} - - - name: GitHub Release - uses: softprops/action-gh-release@v1 - if: success() && startsWith(github.ref, 'refs/tags/v') - with: - draft: true - files: | - dist/checksums.txt - dist/*.tar.gz - dist/*.zip - name: AdGuard Home ${{ steps.prepare.outputs.tag_name }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + make snapshot docker: runs-on: ubuntu-latest needs: test steps: - - - name: Prepare - id: prepare - run: | - DOCKER_IMAGE=adguard/adguardhome - DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le - VERSION=edge - CHANNEL=none - - if [[ $GITHUB_REF == refs/tags/* ]]; then - VERSION=${GITHUB_REF#refs/tags/v} - CHANNEL=release - fi - TAGS="--tag ${DOCKER_IMAGE}:${VERSION}" - if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then - TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest" - fi - - echo ::set-output name=docker_image::${DOCKER_IMAGE} - echo ::set-output name=version::${VERSION} - echo ::set-output name=channel::${CHANNEL} - echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \ - --build-arg VERSION=${VERSION} \ - --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ - --build-arg VCS_REF=${GITHUB_SHA::8} \ - ${TAGS} \ - --file Dockerfile . - name: Set up Docker Buildx uses: crazy-max/ghaction-docker-buildx@v1 - name: Checkout uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Docker Buildx (build) run: | - docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }} - - - name: Docker Login - if: success() && startsWith(github.ref, 'refs/tags/v') - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: | - echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin - - - name: Docker Buildx (push) - if: success() && startsWith(github.ref, 'refs/tags/v') - run: | - docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} - - - name: Docker Check Manifest - if: always() && startsWith(github.ref, 'refs/tags/v') - run: | - docker run --rm mplatform/mquery ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }} + make docker-multi-arch - name: Clear if: always() && startsWith(github.ref, 'refs/tags/v') @@ -242,7 +154,8 @@ jobs: notify: needs: [app, docker] - if: always() + # Secrets are not passed to workflows that are triggered by a pull request from a fork + if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} runs-on: ubuntu-latest steps: - diff --git a/.github/workflows/gosum.yml b/.github/workflows/gosum.yml deleted file mode 100644 index 77c23abd..00000000 --- a/.github/workflows/gosum.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: gosum - -on: - push: - branches: - - 'master' - paths: - - '.github/workflows/gosum.yml' - - 'go.mod' - - 'go.sum' - -jobs: - fix: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.14 - - - name: Tidy - run: | - rm -f go.sum - go mod tidy - - - name: Set up Git - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name GitHub - git config user.email noreply@github.com - git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git - - - name: Commit and push changes - run: | - git add . - if output=$(git status --porcelain) && [ ! -z "$output" ]; then - git commit --author "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>" --message "Fix go modules" - git push - fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..99fcefc4 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,47 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - '*' + pull_request: +jobs: + golangci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v1 + with: + # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. + version: v1.27 + + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: npm --prefix client ci + - name: Run ESLint + run: npm --prefix client run lint + + + notify: + needs: [golangci,eslint] + # Secrets are not passed to workflows that are triggered by a pull request from a fork + if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-latest + steps: + - + name: Conclusion + uses: technote-space/workflow-conclusion-action@v1 + - + name: Send Slack notif + uses: 8398a7/action-slack@v3 + with: + status: ${{ env.WORKFLOW_CONCLUSION }} + fields: repo,message,commit,author + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} \ No newline at end of file diff --git a/.gometalinter.json b/.gometalinter.json deleted file mode 100644 index 10de7f42..00000000 --- a/.gometalinter.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "Vendor": true, - "Test": true, - "Deadline": "2m", - "Sort": ["linter", "severity", "path", "line"], - "Exclude": [ - ".*generated.*", - "dnsfilter/rule_to_regexp.go" - ], - "EnableGC": true, - "Linters": { - "nakedret": { - "Command": "nakedret", - "Pattern": "^(?P.*?\\.go):(?P\\d+)\\s*(?P.*)$" - } - }, - "WarnUnmatchedDirective": true, - - "EnableAll": true, - "DisableAll": false, - "Disable": [ - "maligned", - "goconst", - "vetshadow" - ], - - "Cyclo": 20, - "LineLength": 200 -} \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index da879296..f698bcf1 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -24,23 +24,37 @@ builds: - amd64 - arm - arm64 + - mips + - mipsle + - mips64 + - mips64le goarm: + - 5 - 6 - 7 + gomips: + - softfloat ignore: - goos: freebsd - goarch: arm + goarch: mips - goos: freebsd - goarch: arm64 + goarch: mipsle archives: - - replacements: - 386: i386 - amd64: x86_64 + # Archive name template. + # Defaults: + # - if format is `tar.gz`, `tar.xz`, `gz` or `zip`: + # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` + # - if format is `binary`: + # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` + name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" + wrap_in_directory: "AdGuardHome" format_overrides: - goos: windows format: zip + - goos: darwin + format: zip files: - LICENSE.txt - README.md @@ -62,7 +76,7 @@ snapcrafts: of common code. grade: stable confinement: strict - publish: true + publish: false license: GPL-3.0 apps: adguard-home: diff --git a/Dockerfile b/Dockerfile index dfed8baf..49ff6118 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.14-alpine as builder ARG BUILD_DATE ARG VCS_REF ARG VERSION=dev -ARG CHANNEL=none +ARG CHANNEL=release ENV CGO_ENABLED 0 ENV GO111MODULE on @@ -21,12 +21,17 @@ RUN apk --update --no-cache add \ && rm -rf /tmp/* /var/cache/apk/* WORKDIR /app + COPY . ./ +# Prepare the client code RUN npm --prefix client ci && npm --prefix client run build-prod + +# Download go dependencies RUN go mod download RUN go generate ./... +# It's important to place TARGET* arguments here to avoid running npm and go mod download for every platform ARG TARGETPLATFORM ARG TARGETOS ARG TARGETARCH diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7de43f3a --- /dev/null +++ b/Makefile @@ -0,0 +1,222 @@ +# +# Available targets +# +# * build -- builds AdGuardHome for the current platform +# * client -- builds client-side code of AdGuard Home +# * client-watch -- builds client-side code of AdGuard Home and watches for changes there +# * docker -- builds a docker image for the current platform +# * clean -- clean everything created by previous builds +# * lint -- run all linters +# * test -- run all unit-tests +# * dependencies -- installs dependencies (go and npm modules) +# * ci -- installs dependencies, runs linters and tests, intended to be used by CI/CD +# +# Building releases: +# +# * release -- builds release version of AdGuard Home. CHANNEL must be specified (release or beta). +# * snapshot -- builds snapshot version of AdGuard Home. Use with CHANNEL=edge. +# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub, +# you must specify: +# * DOCKER_IMAGE_NAME - adguard/adguard-home +# * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true + +GOPATH := $(shell go env GOPATH) +PWD := $(shell pwd) +TARGET=AdGuardHome +BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)" + +# See release and snapshot targets +DIST_DIR=dist + +# Update channel. Can be release, beta or edge. Uses edge by default. +CHANNEL ?= edge + +# Validate channel +ifneq ($(CHANNEL),relese) +ifneq ($(CHANNEL),beta) +ifneq ($(CHANNEL),edge) +$(error CHANNEL value is not valid. Valid values are release,beta or edge) +endif +endif +endif + +# Version properties +COMMIT=$(shell git rev-parse --short HEAD) +TAG_NAME=$(shell git describe --abbrev=0) + +# Remove leading "v" from the tag name +RELEASE_VERSION=$(TAG_NAME:v%=%) +SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT) + +# Set proper version +VERSION= +ifeq ($(TAG_NAME),$(shell git describe --abbrev=4)) + VERSION=$(RELEASE_VERSION) +else + VERSION=$(SNAPSHOT_VERSION) +endif + +# Docker target parameters +DOCKER_IMAGE_NAME ?= adguardhome-dev +DOCKER_PLATFORMS=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le +DOCKER_OUTPUT ?= type=image,name=$(DOCKER_IMAGE_NAME),push=false +BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ') + +# Docker tags (can be redefined) +DOCKER_TAGS ?= +ifndef DOCKER_TAGS + ifeq ($(CHANNEL),release) + DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):latest + endif + ifeq ($(CHANNEL),beta) + DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):beta + endif + ifeq ($(CHANNEL),edge) + DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):edge + endif +endif + +# Validate docker build arguments +ifndef DOCKER_IMAGE_NAME +$(error DOCKER_IMAGE_NAME value is not set) +endif + +.PHONY: all build client client-watch docker lint test dependencies clean release snapshot docker-multi-arch +all: build + +build: dependencies client + go generate ./... + CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" + PATH=$(GOPATH)/bin:$(PATH) packr clean + +client: + npm --prefix client run build-prod + +client-watch: + npm --prefix client run watch + +docker: + DOCKER_CLI_EXPERIMENTAL=enabled \ + docker buildx build \ + --build-arg VERSION=$(VERSION) \ + --build-arg CHANNEL=$(CHANNEL) \ + --build-arg VCS_REF=$(COMMIT) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ + $(DOCKER_TAGS) \ + --load \ + -t "$(DOCKER_IMAGE_NAME)" -f ./Dockerfile . + + @echo Now you can run the docker image: + @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) + +lint: + @echo Running linters + golangci-lint run ./... + npm --prefix client run lint + +test: + @echo Running unit-tests + go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./... + +ci: dependencies client test + +dependencies: + npm --prefix client ci + go mod download + +clean: + # make build output + rm -f AdGuardHome + rm -f AdGuardHome.exe + # static build output + rm -rf build + # dist folder + rm -rf $(DIST_DIR) + # client deps + rm -rf client/node_modules + # packr-generated files + PATH=$(GOPATH)/bin:$(PATH) packr clean + +docker-multi-arch: + DOCKER_CLI_EXPERIMENTAL=enabled \ + docker buildx build \ + --platform $(DOCKER_PLATFORMS) \ + --build-arg VERSION=$(VERSION) \ + --build-arg CHANNEL=$(CHANNEL) \ + --build-arg VCS_REF=$(COMMIT) \ + --build-arg BUILD_DATE=$(BUILD_DATE) \ + $(DOCKER_TAGS) \ + --output "$(DOCKER_OUTPUT)" \ + -t "$(DOCKER_IMAGE_NAME):$(VERSION)" -f ./Dockerfile . + + @echo If the image was pushed to the registry, you can now run it: + @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) + +snapshot: dependencies client + @echo Starting snapshot build: version $(VERSION), channel $(CHANNEL) + CHANNEL=$(CHANNEL) goreleaser release --rm-dist --skip-publish --snapshot + $(call write_version_file,$(VERSION)) + PATH=$(GOPATH)/bin:$(PATH) packr clean + +release: dependencies client + @echo Starting release build: version $(VERSION), channel $(CHANNEL) + CHANNEL=$(CHANNEL) goreleaser release --rm-dist --skip-publish + $(call write_version_file,$(VERSION)) + PATH=$(GOPATH)/bin:$(PATH) packr clean + +define write_version_file + $(eval version := $(1)) + + @echo Writing version file: $(version) + + # Variables for CI + rm -f $(DIST_DIR)/version.txt + echo "version=v$(version)" > $(DIST_DIR)/version.txt + + # Prepare the version.json file + rm -f $(DIST_DIR)/version.json + echo "{" >> $(DIST_DIR)/version.json + echo " \"version\": \"v$(version)\"," >> $(DIST_DIR)/version.json + echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json + echo " \"announcement_url\": \"https://github.com/AdguardTeam/AdGuardHome/releases\"," >> $(DIST_DIR)/version.json + echo " \"selfupdate_min_version\": \"v0.0\"," >> $(DIST_DIR)/version.json + + # Windows builds + echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json + echo " \"download_windows_386\": \"$(BASE_URL)/AdGuardHome_windows_386.zip\"," >> $(DIST_DIR)/version.json + + # MacOS builds + echo " \"download_darwin_386\": \"$(BASE_URL)/AdGuardHome_darwin_386.zip\"," >> $(DIST_DIR)/version.json + echo " \"download_darwin_amd64\": \"$(BASE_URL)/AdGuardHome_darwin_amd64.zip\"," >> $(DIST_DIR)/version.json + + # Linux + echo " \"download_linux_amd64\": \"$(BASE_URL)/AdGuardHome_linux_amd64.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_386\": \"$(BASE_URL)/AdGuardHome_linux_386.tar.gz\"," >> $(DIST_DIR)/version.json + + # Linux, all kinds of ARM + echo " \"download_linux_arm\": \"$(BASE_URL)/AdGuardHome_linux_armv6.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_armv5\": \"$(BASE_URL)/AdGuardHome_linux_armv5.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_armv6\": \"$(BASE_URL)/AdGuardHome_linux_armv6.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_armv7\": \"$(BASE_URL)/AdGuardHome_linux_armv7.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_arm64\": \"$(BASE_URL)/AdGuardHome_linux_arm64.tar.gz\"," >> $(DIST_DIR)/version.json + + # Linux, MIPS + echo " \"download_linux_mips\": \"$(BASE_URL)/AdGuardHome_linux_mips_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_mipsle\": \"$(BASE_URL)/AdGuardHome_linux_mipsle_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_mips64\": \"$(BASE_URL)/AdGuardHome_linux_mips64_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_linux_mips64le\": \"$(BASE_URL)/AdGuardHome_linux_mips64le_softfloat.tar.gz\"," >> $(DIST_DIR)/version.json + + # FreeBSD + echo " \"download_freebsd_386\": \"$(BASE_URL)/AdGuardHome_freebsd_386.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_freebsd_amd64\": \"$(BASE_URL)/AdGuardHome_freebsd_amd64.tar.gz\"," >> $(DIST_DIR)/version.json + + # FreeBSD, all kinds of ARM + echo " \"download_freebsd_arm\": \"$(BASE_URL)/AdGuardHome_freebsd_armv6.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_freebsd_armv5\": \"$(BASE_URL)/AdGuardHome_freebsd_armv5.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_freebsd_armv6\": \"$(BASE_URL)/AdGuardHome_freebsd_armv6.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_freebsd_armv7\": \"$(BASE_URL)/AdGuardHome_freebsd_armv7.tar.gz\"," >> $(DIST_DIR)/version.json + echo " \"download_freebsd_arm64\": \"$(BASE_URL)/AdGuardHome_freebsd_arm64.tar.gz\"" >> $(DIST_DIR)/version.json + + # Finish + echo "}" >> $(DIST_DIR)/version.json +endef \ No newline at end of file diff --git a/README.md b/README.md index 83aa32fb..602c1be0 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,6 @@ Twitter | Telegram

- - Build status - Code Coverage @@ -153,17 +150,11 @@ Is there a chance to handle this in the future? DNS will never be enough to do t ### Prerequisites -You will need: +You will need this to build AdGuard Home: * [go](https://golang.org/dl/) v1.14 or later. * [node.js](https://nodejs.org/en/download/) v10 or later. -You can either install them via the provided links or use [brew.sh](https://brew.sh/) if you're on Mac: - -```bash -brew install go node -``` - ### Building Open Terminal and execute these commands: @@ -174,15 +165,26 @@ cd AdGuardHome make ``` -#### (For devs) Upload translations -``` -node upload.js -``` +Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands. -#### (For devs) Download translations -``` -node download.js -``` +#### Preparing release + +You'll need this to prepare a release build: + +* [goreleaser](https://goreleaser.com/) +* [snapcraft](https://snapcraft.io/) + +Run `make snapshot` or `make release` to build all AdGuard distrs. + +#### Docker image + +* Run `make docker` to build the Docker image locally. +* Run `make docker-multi-arch` to build the multi-arch Docker image (the one that we publish to Docker Hub). + +### Resources that we update periodically + +* `scripts/translations` +* `scripts/whotracksme` ## Contributing diff --git a/go.sum b/go.sum index 415f71a0..19938814 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/home/control_update.go b/home/control_update.go index 6115fac1..80ade843 100644 --- a/home/control_update.go +++ b/home/control_update.go @@ -18,61 +18,20 @@ import ( "time" "github.com/AdguardTeam/AdGuardHome/util" - "github.com/AdguardTeam/golibs/log" ) -// Convert version.json data to our JSON response -func getVersionResp(data []byte) []byte { - versionJSON := make(map[string]interface{}) - err := json.Unmarshal(data, &versionJSON) - if err != nil { - log.Error("version.json: %s", err) - return []byte{} - } - - ret := make(map[string]interface{}) - ret["can_autoupdate"] = false - - var ok1, ok2, ok3 bool - ret["new_version"], ok1 = versionJSON["version"].(string) - ret["announcement"], ok2 = versionJSON["announcement"].(string) - ret["announcement_url"], ok3 = versionJSON["announcement_url"].(string) - selfUpdateMinVersion, ok4 := versionJSON["selfupdate_min_version"].(string) - if !ok1 || !ok2 || !ok3 || !ok4 { - log.Error("version.json: invalid data") - return []byte{} - } - - // the key is download_linux_arm or download_linux_arm64 for regular ARM versions - dloadName := fmt.Sprintf("download_%s_%s", runtime.GOOS, runtime.GOARCH) - if runtime.GOARCH == "arm" && ARMVersion == "5" { - // the key is download_linux_armv5 for ARMv5 - dloadName = fmt.Sprintf("download_%s_%sv%s", runtime.GOOS, runtime.GOARCH, ARMVersion) - } - _, ok := versionJSON[dloadName] - if ok && ret["new_version"] != versionString && versionString >= selfUpdateMinVersion { - canUpdate := true - - tlsConf := tlsConfigSettings{} - Context.tls.WriteDiskConfig(&tlsConf) - - if runtime.GOOS != "windows" && - ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || tlsConf.PortDNSOverTLS < 1024)) || - config.BindPort < 1024 || - config.DNS.Port < 1024) { - // On UNIX, if we're running under a regular user, - // but with CAP_NET_BIND_SERVICE set on a binary file, - // and we're listening on ports <1024, - // we won't be able to restart after we replace the binary file, - // because we'll lose CAP_NET_BIND_SERVICE capability. - canUpdate, _ = util.HaveAdminRights() - } - ret["can_autoupdate"] = canUpdate - } - - d, _ := json.Marshal(ret) - return d +type updateInfo struct { + pkgURL string // URL for the new package + pkgName string // Full path to package file + newVer string // New version string + updateDir string // Full path to the directory containing unpacked files from the new package + backupDir string // Full path to backup directory + configName string // Full path to the current configuration file + updateConfigName string // Full path to the configuration file to check by the new binary + curBinName string // Full path to the current executable file + bkpBinName string // Full path to the current executable file in backup directory + newBinName string // Full path to the new executable file } type getVersionJSONRequest struct { @@ -81,7 +40,6 @@ type getVersionJSONRequest struct { // Get the latest available version from the Internet func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { - if Context.disableUpdate { return } @@ -103,7 +61,7 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { if cached { log.Tracef("Returning cached data") w.Header().Set("Content-Type", "application/json") - w.Write(getVersionResp(data)) + _, _ = w.Write(getVersionResp(data)) return } } @@ -146,6 +104,80 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) { } } +// Perform an update procedure to the latest available version +func handleUpdate(w http.ResponseWriter, r *http.Request) { + if len(config.versionCheckJSON) == 0 { + httpError(w, http.StatusBadRequest, "/update request isn't allowed now") + return + } + + u, err := getUpdateInfo(config.versionCheckJSON) + if err != nil { + httpError(w, http.StatusInternalServerError, "%s", err) + return + } + + err = doUpdate(u) + if err != nil { + httpError(w, http.StatusInternalServerError, "%s", err) + return + } + + returnOK(w) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + + go finishUpdate(u) +} + +// Convert version.json data to our JSON response +func getVersionResp(data []byte) []byte { + versionJSON := make(map[string]interface{}) + err := json.Unmarshal(data, &versionJSON) + if err != nil { + log.Error("version.json: %s", err) + return []byte{} + } + + ret := make(map[string]interface{}) + ret["can_autoupdate"] = false + + var ok1, ok2, ok3 bool + ret["new_version"], ok1 = versionJSON["version"].(string) + ret["announcement"], ok2 = versionJSON["announcement"].(string) + ret["announcement_url"], ok3 = versionJSON["announcement_url"].(string) + selfUpdateMinVersion, ok4 := versionJSON["selfupdate_min_version"].(string) + if !ok1 || !ok2 || !ok3 || !ok4 { + log.Error("version.json: invalid data") + return []byte{} + } + + _, ok := getDownloadURL(versionJSON) + if ok && ret["new_version"] != versionString && versionString >= selfUpdateMinVersion { + canUpdate := true + + tlsConf := tlsConfigSettings{} + Context.tls.WriteDiskConfig(&tlsConf) + + if runtime.GOOS != "windows" && + ((tlsConf.Enabled && (tlsConf.PortHTTPS < 1024 || tlsConf.PortDNSOverTLS < 1024)) || + config.BindPort < 1024 || + config.DNS.Port < 1024) { + // On UNIX, if we're running under a regular user, + // but with CAP_NET_BIND_SERVICE set on a binary file, + // and we're listening on ports <1024, + // we won't be able to restart after we replace the binary file, + // because we'll lose CAP_NET_BIND_SERVICE capability. + canUpdate, _ = util.HaveAdminRights() + } + ret["can_autoupdate"] = canUpdate + } + + d, _ := json.Marshal(ret) + return d +} + // Copy file on disk func copyFile(src, dst string) error { d, e := ioutil.ReadFile(src) @@ -159,19 +191,6 @@ func copyFile(src, dst string) error { return nil } -type updateInfo struct { - pkgURL string // URL for the new package - pkgName string // Full path to package file - newVer string // New version string - updateDir string // Full path to the directory containing unpacked files from the new package - backupDir string // Full path to backup directory - configName string // Full path to the current configuration file - updateConfigName string // Full path to the configuration file to check by the new binary - curBinName string // Full path to the current executable file - bkpBinName string // Full path to the current executable file in backup directory - newBinName string // Full path to the new executable file -} - // Fill in updateInfo object func getUpdateInfo(jsonData []byte) (*updateInfo, error) { var u updateInfo @@ -184,7 +203,12 @@ func getUpdateInfo(jsonData []byte) (*updateInfo, error) { return nil, fmt.Errorf("JSON parse: %s", err) } - u.pkgURL = versionJSON[fmt.Sprintf("download_%s_%s", runtime.GOOS, runtime.GOARCH)].(string) + pkgURL, ok := getDownloadURL(versionJSON) + if !ok { + return nil, fmt.Errorf("failed to get download URL") + } + + u.pkgURL = pkgURL u.newVer = versionJSON["version"].(string) if len(u.pkgURL) == 0 || len(u.newVer) == 0 { return nil, fmt.Errorf("invalid JSON") @@ -226,6 +250,33 @@ func getUpdateInfo(jsonData []byte) (*updateInfo, error) { return &u, nil } +// getDownloadURL - gets download URL for the current GOOS/GOARCH +// returns +func getDownloadURL(json map[string]interface{}) (string, bool) { + var key string + + if runtime.GOARCH == "arm" && ARMVersion != "" { + // the key is: + // download_linux_armv5 for ARMv5 + // download_linux_armv6 for ARMv6 + // download_linux_armv7 for ARMv7 + key = fmt.Sprintf("download_%s_%sv%s", runtime.GOOS, runtime.GOARCH, ARMVersion) + } + + u, ok := json[key] + if !ok { + // the key is download_linux_arm or download_linux_arm64 for regular ARM versions + key = fmt.Sprintf("download_%s_%s", runtime.GOOS, runtime.GOARCH) + u, ok = json[key] + } + + if !ok { + return "", false + } + + return u.(string), true +} + // Unpack all files from .zip file to the specified directory // Existing files are overwritten // Return the list of files (not directories) written @@ -526,31 +577,3 @@ func finishUpdate(u *updateInfo) { // Unreachable code } } - -// Perform an update procedure to the latest available version -func handleUpdate(w http.ResponseWriter, r *http.Request) { - - if len(config.versionCheckJSON) == 0 { - httpError(w, http.StatusBadRequest, "/update request isn't allowed now") - return - } - - u, err := getUpdateInfo(config.versionCheckJSON) - if err != nil { - httpError(w, http.StatusInternalServerError, "%s", err) - return - } - - err = doUpdate(u) - if err != nil { - httpError(w, http.StatusInternalServerError, "%s", err) - return - } - - returnOK(w) - if f, ok := w.(http.Flusher); ok { - f.Flush() - } - - go finishUpdate(u) -} diff --git a/home/home.go b/home/home.go index 077455e8..79cf0843 100644 --- a/home/home.go +++ b/home/home.go @@ -134,6 +134,15 @@ func Main(version string, channel string, armVer string) { run(args) } +// version - returns the current version string +func version() string { + msg := "AdGuard Home, version %s, channel %s, arch %s %s" + if ARMVersion != "" { + msg = msg + " v" + ARMVersion + } + return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH) +} + // run initializes configuration and runs the AdGuard Home // run is a blocking method! // nolint @@ -153,11 +162,7 @@ func run(args options) { configureLogger(args) // print the first message after logger is configured - msg := "AdGuard Home, version %s, channel %s, arch %s %s" - if ARMVersion != "" { - msg = msg + " v" + ARMVersion - } - log.Printf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH) + log.Println(version()) log.Debug("Current working directory is %s", Context.workDir) if args.runningAsService { log.Info("AdGuard Home is running as a service") @@ -564,7 +569,7 @@ func loadOptions() options { {"verbose", "v", "Enable verbose output", nil, func() { o.verbose = true }}, {"glinet", "", "Run in GL-Inet compatibility mode", nil, func() { o.glinetMode = true }}, {"version", "", "Show the version and exit", nil, func() { - fmt.Printf("AdGuardHome %s\n", versionString) + fmt.Println(version()) os.Exit(0) }}, {"help", "", "Print this help", nil, func() {