From ff23d7b6d73b2a138774ca0919692246f62c0177 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <ay.meshkov@gmail.com>
Date: Thu, 9 Jul 2020 11:53:41 +0300
Subject: [PATCH 01/12] CI Revamp by @crazy-max (#1873)

* CI revamped and other enhancements
GoReleaser for multi-arch binaries
Switch to GitHub Actions (more versatile) with 2 workflows
Docker multi-manifest image with buildx
Fix eslint run script
Go sum fixer
Use tools.go paradigm for external dependencies (packr)
Use go generate for packr

* Update workflows
* Add Slack notification
* Rebase
* Enhanced Dockerfile
* Fix go generate
* Fix Golangci lint
* Fix npm cache with v2 cache action
* Fix Dockerfile
* Trigger notif
* Issue with packr
* s390x platform not supported by packr
* Enhance layers
* Fix go modules

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
---
 .dockerignore                   |  40 +++++
 .github/workflows/build.yml     | 259 ++++++++++++++++++++++++++++
 .github/workflows/gosum.yml     |  44 +++++
 .gitignore                      |   4 +-
 .goreleaser.yml                 |  74 ++++++++
 .travis.yml                     | 121 -------------
 Dockerfile                      |  73 ++++++++
 Dockerfile.travis               |  23 ---
 Makefile                        |  43 -----
 build_docker.sh                 |  74 --------
 build_release.sh                |  73 --------
 build_snap.sh                   | 292 --------------------------------
 ci.sh                           |  36 ----
 go.sum                          |  21 +--
 main.go                         |   3 +
 packaging/docker/Dockerfile     |  32 ----
 packaging/docker/Dockerfile.hub |  23 ---
 packaging/docker/README.md      |   6 -
 packaging/snap/README.md        |   5 -
 packaging/snap/snapcraft.yaml   |  32 ----
 snapcraft.yaml                  |  41 -----
 tools.go                        |   8 +
 22 files changed, 511 insertions(+), 816 deletions(-)
 create mode 100644 .dockerignore
 create mode 100644 .github/workflows/build.yml
 create mode 100644 .github/workflows/gosum.yml
 create mode 100644 .goreleaser.yml
 delete mode 100644 .travis.yml
 create mode 100644 Dockerfile
 delete mode 100644 Dockerfile.travis
 delete mode 100644 Makefile
 delete mode 100755 build_docker.sh
 delete mode 100755 build_release.sh
 delete mode 100755 build_snap.sh
 delete mode 100755 ci.sh
 delete mode 100644 packaging/docker/Dockerfile
 delete mode 100644 packaging/docker/Dockerfile.hub
 delete mode 100644 packaging/docker/README.md
 delete mode 100644 packaging/snap/README.md
 delete mode 100644 packaging/snap/snapcraft.yaml
 delete mode 100644 snapcraft.yaml
 create mode 100644 tools.go

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..c200f675
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,40 @@
+.DS_Store
+/.git
+/.github
+/.vscode
+.idea
+/AdGuardHome
+/AdGuardHome.exe
+/AdGuardHome.yaml
+/AdGuardHome.log
+/data
+/build
+/dist
+/client/node_modules
+/.gitattributes
+/.gitignore
+/.goreleaser.yml
+/changelog.config.js
+/coverage.txt
+/Dockerfile
+/LICENSE.txt
+/Makefile
+/querylog.json
+/querylog.json.1
+/*.md
+
+# Test output
+dnsfilter/tests/top-1m.csv
+dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
+
+# Snapcraft build temporary files
+*.snap
+launchpad_credentials
+snapcraft_login
+snapcraft.yaml.bak
+
+# IntelliJ IDEA project files
+*.iml
+
+# Packr
+*-packr.go
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..ae4eae46
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,259 @@
+name: build
+
+on:
+  push:
+    branches:
+      - master
+    tags:
+      - v*
+  pull_request:
+
+jobs:
+
+  test:
+    runs-on: ${{ matrix.os }}
+    env:
+      GO111MODULE: on
+      GOPROXY: https://goproxy.io
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - ubuntu-latest
+          - macOS-latest
+          - windows-latest
+    steps:
+      -
+        name: Checkout
+        uses: actions/checkout@v2
+      -
+        name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.14
+      -
+        name: Set up Node
+        uses: actions/setup-node@v1
+        with:
+          node-version: 13
+      -
+        name: Set up Go modules cache
+        uses: actions/cache@v2
+        with:
+          path: ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
+      -
+        name: Get npm cache directory
+        id: npm-cache
+        run: |
+          echo "::set-output name=dir::$(npm config get cache)"
+      -
+        name: Set up npm cache
+        uses: actions/cache@v2
+        with:
+          path: ${{ steps.npm-cache.outputs.dir }}
+          key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-
+      -
+        name: Run node build-prod
+        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 ./...
+      -
+        name: Upload coverage
+        uses: codecov/codecov-action@v1
+        if: success()
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          file: ./coverage.txt
+
+  app:
+    runs-on: ubuntu-latest
+    needs: test
+    steps:
+      -
+        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
+      -
+        name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.14
+      -
+        name: Set up Node
+        uses: actions/setup-node@v1
+        with:
+          node-version: 13
+      -
+        name: Set up Go modules cache
+        uses: actions/cache@v2
+        with:
+          path: ~/go/pkg/mod
+          key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
+          restore-keys: |
+            ${{ runner.os }}-go-
+      -
+        name: Get npm cache directory
+        id: npm-cache
+        run: |
+          echo "::set-output name=dir::$(npm config get cache)"
+      -
+        name: Set up node_modules cache
+        uses: actions/cache@v2
+        with:
+          path: ${{ steps.npm-cache.outputs.dir }}
+          key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-
+      -
+        name: Set up Snapcraft
+        run: |
+          sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft
+      -
+        name: Run node build-prod
+        run: |
+          npm --prefix client ci
+          npm --prefix client run build-prod
+      -
+        name: Snapcraft Login
+        if: success() && startsWith(github.ref, 'refs/tags/v')
+        env:
+          SNAPCRAFT_LOGIN: ${{ secrets.SNAPCRAFT_LOGIN }}
+        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 }}
+
+  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
+      -
+        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 }}
+      -
+        name: Clear
+        if: always() && startsWith(github.ref, 'refs/tags/v')
+        run: |
+          rm -f ${HOME}/.docker/config.json
+
+  notify:
+    needs: [app, docker]
+    if: always()
+    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 }}
diff --git a/.github/workflows/gosum.yml b/.github/workflows/gosum.yml
new file mode 100644
index 00000000..77c23abd
--- /dev/null
+++ b/.github/workflows/gosum.yml
@@ -0,0 +1,44 @@
+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/.gitignore b/.gitignore
index d47eef0c..e7f55736 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@
 /client/node_modules/
 /querylog.json
 /querylog.json.1
-/a_main-packr.go
 coverage.txt
 
 # Test output
@@ -26,3 +25,6 @@ snapcraft.yaml.bak
 
 # IntelliJ IDEA project files
 *.iml
+
+# Packr
+*-packr.go
diff --git a/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 00000000..da879296
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,74 @@
+env:
+  - GO111MODULE=on
+  - GOPROXY=https://goproxy.io
+
+before:
+  hooks:
+    - go mod download
+    - go generate ./...
+
+builds:
+  -
+    main: ./main.go
+    ldflags:
+      - -s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}
+    env:
+      - CGO_ENABLED=0
+    goos:
+      - darwin
+      - linux
+      - freebsd
+      - windows
+    goarch:
+      - 386
+      - amd64
+      - arm
+      - arm64
+    goarm:
+      - 6
+      - 7
+    ignore:
+      - goos: freebsd
+        goarch: arm
+      - goos: freebsd
+        goarch: arm64
+
+archives:
+  -
+    replacements:
+      386: i386
+      amd64: x86_64
+    format_overrides:
+      - goos: windows
+        format: zip
+    files:
+      - LICENSE.txt
+      - README.md
+
+snapcrafts:
+  -
+    name: adguard-home
+    base: core18
+    name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
+    summary: Network-wide ads & trackers blocking DNS server
+    description: |
+      AdGuard Home is a network-wide software for blocking ads & tracking. After
+      you set it up, it'll cover ALL your home devices, and you don't need any
+      client-side software for that.
+
+      It operates as a DNS server that re-routes tracking domains to a "black hole,"
+      thus preventing your devices from connecting to those servers. It's based
+      on software we use for our public AdGuard DNS servers -- both share a lot
+      of common code.
+    grade: stable
+    confinement: strict
+    publish: true
+    license: GPL-3.0
+    apps:
+      adguard-home:
+        args: -w ${SNAP_DATA} --no-check-update
+        plugs: [ network-bind ]
+        daemon: simple
+
+checksum:
+  name_template: 'checksums.txt'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b2e5a2dd..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,121 +0,0 @@
-if: repo = AdguardTeam/AdGuardHome
-language: go
-sudo: false
-
-go:
-  - 1.14.x
-os:
-  - linux
-  - osx
-  - windows
-
-before_install:
-  - |-
-    case $TRAVIS_OS_NAME in
-      linux | osx)
-        nvm install node
-        npm install -g npm
-        curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.23.8
-        ;;
-      windows)
-        # Using NVS for managing Node.js versions on Windows
-        NVS_HOME="C:\ProgramData\nvs"
-        git clone --single-branch https://github.com/jasongin/nvs $NVS_HOME
-        source $NVS_HOME/nvs.sh
-        nvs add latest
-        nvs use latest
-        ;;
-    esac
-
-install:
-  - |-
-    case $TRAVIS_OS_NAME in
-      linux | osx)
-        node --version
-        npm --version
-        npm --prefix client ci
-        ;;
-      windows)
-        node --version
-        npm --version
-        nvs --version
-        npm --prefix client ci
-        ;;
-    esac
-
-cache:
-  directories:
-    - $HOME/.cache/go-build
-    - $HOME/gopath/pkg/mod
-    - $HOME/Library/Caches/go-build
-
-script:
-  - |-
-    case $TRAVIS_OS_NAME in
-      linux | osx)
-        /bin/bash ci.sh
-        ;;
-      windows)
-        npm --prefix client run build-prod
-        go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
-        ;;
-    esac
-
-after_success:
-  - |-
-    case $TRAVIS_OS_NAME in
-      linux)
-        bash <(curl -s https://codecov.io/bash)
-        ;;
-    esac
-
-notifications:
-  slack: performix:yXTihlSzsLFSZiqbXMNzvTSX
-
-matrix:
-  include:
-    # Release build configuration
-    - if: repo = AdguardTeam/AdGuardHome
-    - name: release
-      go:
-        - 1.14.x
-      os:
-        - linux
-
-      script:
-        - node -v
-        - npm -v
-        # Prepare releases
-        - ./build_release.sh
-        - ls -l dist
-
-      deploy:
-        provider: releases
-        api_key: $GITHUB_TOKEN
-        file:
-          - dist/AdGuardHome_*
-        on:
-          repo: AdguardTeam/AdGuardHome
-          tags: true
-        draft: true
-        file_glob: true
-        skip_cleanup: true
-
-    # Docker build configuration
-    - if: repo = AdguardTeam/AdGuardHome
-    - name: docker
-      if: type != pull_request AND (branch = master OR tag IS present) AND repo = AdguardTeam/AdGuardHome
-      go:
-        - 1.14.x
-      os:
-        - linux
-      services:
-        - docker
-      before_script:
-        - nvm install node
-        - npm install -g npm
-      script:
-        - docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD"
-        - ./build_docker.sh
-      after_script:
-        - docker images
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..dfed8baf
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,73 @@
+FROM --platform=${BUILDPLATFORM:-linux/amd64} tonistiigi/xx:golang AS xgo
+FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.14-alpine as builder
+
+ARG BUILD_DATE
+ARG VCS_REF
+ARG VERSION=dev
+ARG CHANNEL=none
+
+ENV CGO_ENABLED 0
+ENV GO111MODULE on
+ENV GOPROXY https://goproxy.io
+
+COPY --from=xgo / /
+RUN go env
+
+RUN apk --update --no-cache add \
+    build-base \
+    gcc \
+    git \
+    npm \
+  && rm -rf /tmp/* /var/cache/apk/*
+
+WORKDIR /app
+COPY . ./
+
+RUN npm --prefix client ci && npm --prefix client run build-prod
+RUN go mod download
+RUN go generate ./...
+
+ARG TARGETPLATFORM
+ARG TARGETOS
+ARG TARGETARCH
+RUN go build -ldflags="-s -w -X main.version=${VERSION} -X main.channel=${CHANNEL} -X main.goarm=${GOARM}"
+
+FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:latest
+
+ARG BUILD_DATE
+ARG VCS_REF
+ARG VERSION
+ARG CHANNEL
+
+LABEL maintainer="AdGuard Team <devteam@adguard.com>" \
+  org.opencontainers.image.created=$BUILD_DATE \
+  org.opencontainers.image.url="https://adguard.com/adguard-home.html" \
+  org.opencontainers.image.source="https://github.com/AdguardTeam/AdGuardHome" \
+  org.opencontainers.image.version=$VERSION \
+  org.opencontainers.image.revision=$VCS_REF \
+  org.opencontainers.image.vendor="AdGuard" \
+  org.opencontainers.image.title="AdGuard Home" \
+  org.opencontainers.image.description="Network-wide ads & trackers blocking DNS server" \
+  org.opencontainers.image.licenses="GPL-3.0"
+
+RUN apk --update --no-cache add \
+    ca-certificates \
+    libcap \
+    libressl \
+    tzdata \
+  && rm -rf /tmp/* /var/cache/apk/*
+
+COPY --from=builder --chown=nobody:nogroup /app/AdGuardHome /opt/adguardhome/AdGuardHome
+COPY --from=builder --chown=nobody:nogroup /usr/local/go/lib/time/zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip
+
+RUN /opt/adguardhome/AdGuardHome --version \
+  && mkdir -p /opt/adguardhome/conf /opt/adguardhome/work \
+  && chown -R nobody: /opt/adguardhome \
+  && setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
+
+EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
+WORKDIR /opt/adguardhome/work
+VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
+
+ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
+CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
diff --git a/Dockerfile.travis b/Dockerfile.travis
deleted file mode 100644
index 0e11574e..00000000
--- a/Dockerfile.travis
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM alpine:latest
-LABEL maintainer="AdGuard Team <devteam@adguard.com>"
-
-# Update CA certs
-RUN apk --no-cache --update add ca-certificates libcap && \
-    rm -rf /var/cache/apk/* && \
-    mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
-    chown -R nobody: /opt/adguardhome
-
-COPY --chown=nobody:nogroup ./AdGuardHome /opt/adguardhome/AdGuardHome
-
-RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
-
-EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
-
-VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
-
-WORKDIR /opt/adguardhome/work
-
-#USER nobody
-
-ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
-CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 65cea96f..00000000
--- a/Makefile
+++ /dev/null
@@ -1,43 +0,0 @@
-GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
-NATIVE_GOOS = $(shell unset GOOS; go env GOOS)
-NATIVE_GOARCH = $(shell unset GOARCH; go env GOARCH)
-GOPATH := $(shell go env GOPATH)
-JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js')
-STATIC = build/static/index.html
-CHANNEL ?= release
-DOCKER_IMAGE_DEV_NAME=adguardhome-dev
-DOCKERFILE=packaging/docker/Dockerfile
-DOCKERFILE_HUB=packaging/docker/Dockerfile.travis
-
-TARGET=AdGuardHome
-
-.PHONY: all build clean
-all: build
-
-build: $(TARGET)
-
-client/node_modules: client/package.json client/package-lock.json
-	npm --prefix client ci
-	touch client/node_modules
-
-$(STATIC): $(JSFILES) client/node_modules
-	npm --prefix client run build-prod
-
-$(TARGET): $(STATIC) *.go home/*.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
-	GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
-	PATH=$(GOPATH)/bin:$(PATH) packr -z
-	CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(GIT_VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
-	PATH=$(GOPATH)/bin:$(PATH) packr clean
-
-docker:
-	docker build -t "$(DOCKER_IMAGE_DEV_NAME)" -f "$(DOCKERFILE)" .
-	@echo Now you can run the docker image:
-	@echo docker run --name "$(DOCKER_IMAGE_DEV_NAME)" -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_DEV_NAME)
-
-clean:
-	$(MAKE) cleanfast
-	rm -rf build
-	rm -rf client/node_modules
-
-cleanfast:
-	rm -f $(TARGET)
diff --git a/build_docker.sh b/build_docker.sh
deleted file mode 100755
index 81173574..00000000
--- a/build_docker.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env bash
-
-set -eE
-set -o pipefail
-set -x
-
-DOCKERFILE="packaging/docker/Dockerfile.hub"
-IMAGE_NAME="adguard/adguardhome"
-
-if [[ "${TRAVIS_BRANCH}" == "master" ]]
-then
-  VERSION="edge"
-else
-  VERSION=`git describe --abbrev=4 --dirty --always --tags`
-fi
-
-build_image() {
-    from="$(awk '$1 == toupper("FROM") { print $2 }' ${DOCKERFILE})"
-
-    # See https://hub.docker.com/r/multiarch/alpine/tags
-    case "${GOARCH}" in
-        arm64)
-           alpineArch='arm64-edge'
-           imageArch='arm64'
-           ;;
-        arm)
-           alpineArch='armhf-edge'
-           imageArch='armhf'
-           ;;
-        386)
-           alpineArch='i386-edge'
-           imageArch='i386'
-           ;;
-        amd64)
-           alpineArch='amd64-edge'
-           ;;
-        *)
-           alpineArch='amd64-edge'
-           ;;
-    esac
-
-    if [[ "${GOOS}" == "linux" ]] && [[ "${GOARCH}" == "amd64" ]]
-    then
-        image="${IMAGE_NAME}:${VERSION}"
-    else
-        image="${IMAGE_NAME}:${imageArch}-${VERSION}"
-    fi
-
-    make cleanfast; CGO_DISABLED=1 make
-
-    docker pull "multiarch/alpine:${alpineArch}"
-    docker tag "multiarch/alpine:${alpineArch}" "$from"
-    docker build -t "${image}" -f ${DOCKERFILE} .
-    docker push ${image}
-    if [[ "${VERSION}" != "edge" ]]
-    then
-        latest=${image/$VERSION/latest}
-        docker tag "${image}" "${latest}"
-        docker push ${latest}
-        docker rmi ${latest}
-    fi
-    docker rmi "$from"
-}
-
-# prepare qemu
-docker run --rm --privileged multiarch/qemu-user-static:register --reset
-
-make clean
-
-# Prepare releases
-GOOS=linux GOARCH=amd64 build_image
-GOOS=linux GOARCH=386 build_image
-GOOS=linux GOARCH=arm GOARM=6 build_image
-GOOS=linux GOARCH=arm64 GOARM=6 build_image
diff --git a/build_release.sh b/build_release.sh
deleted file mode 100755
index 4f2a4c42..00000000
--- a/build_release.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env bash
-
-set -eE
-set -o pipefail
-set -x
-
-channel=${1:-release}
-baseUrl="https://static.adguard.com/adguardhome/$channel"
-dst=dist
-version=`git describe --abbrev=4 --dirty --always --tags`
-
-f() {
-	make cleanfast; CGO_DISABLED=1 make
-	if [[ $GOOS == darwin ]]; then
-		zip $dst/AdGuardHome_MacOS.zip AdGuardHome README.md LICENSE.txt
-	elif [[ $GOOS == windows ]]; then
-		zip $dst/AdGuardHome_Windows_"$GOARCH".zip AdGuardHome.exe README.md LICENSE.txt
-	else
-		rm -rf dist/AdguardHome
-		mkdir -p dist/AdGuardHome
-		cp -pv {AdGuardHome,LICENSE.txt,README.md} dist/AdGuardHome/
-		pushd dist
-		if [[ $GOARCH == arm ]] && [[ $GOARM != 6 ]]; then
-			tar zcvf AdGuardHome_"$GOOS"_armv"$GOARM".tar.gz AdGuardHome/
-		else
-			tar zcvf AdGuardHome_"$GOOS"_"$GOARCH".tar.gz AdGuardHome/
-		fi
-		popd
-		rm -rf dist/AdguardHome
-	fi
-}
-
-# Clean dist and build
-make clean
-rm -rf $dst
-
-# Prepare the dist folder
-mkdir -p $dst
-
-# Prepare releases
-CHANNEL=$channel GOOS=darwin GOARCH=amd64 f
-CHANNEL=$channel GOOS=linux GOARCH=amd64 f
-CHANNEL=$channel GOOS=linux GOARCH=386 GO386=387 f
-CHANNEL=$channel GOOS=linux GOARCH=arm GOARM=5 f
-CHANNEL=$channel GOOS=linux GOARCH=arm GOARM=6 f
-CHANNEL=$channel GOOS=linux GOARCH=arm64 GOARM=6 f
-CHANNEL=$channel GOOS=windows GOARCH=amd64 f
-CHANNEL=$channel GOOS=windows GOARCH=386 f
-CHANNEL=$channel GOOS=linux GOARCH=mipsle GOMIPS=softfloat f
-CHANNEL=$channel GOOS=linux GOARCH=mips GOMIPS=softfloat f
-CHANNEL=$channel GOOS=freebsd GOARCH=amd64 f
-
-# Variables for CI
-echo "version=$version" > $dst/version.txt
-
-# Prepare the version.json file
-echo "{" >> $dst/version.json
-echo "  \"version\": \"$version\"," >> $dst/version.json
-echo "  \"announcement\": \"AdGuard Home $version is now available!\"," >> $dst/version.json
-echo "  \"announcement_url\": \"https://github.com/AdguardTeam/AdGuardHome/releases\"," >> $dst/version.json
-echo "  \"download_windows_amd64\": \"$baseUrl/AdGuardHome_Windows_amd64.zip\"," >> $dst/version.json
-echo "  \"download_windows_386\": \"$baseUrl/AdGuardHome_Windows_386.zip\"," >> $dst/version.json
-echo "  \"download_darwin_amd64\": \"$baseUrl/AdGuardHome_MacOS.zip\"," >> $dst/version.json
-echo "  \"download_linux_amd64\": \"$baseUrl/AdGuardHome_linux_amd64.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_386\": \"$baseUrl/AdGuardHome_linux_386.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_arm\": \"$baseUrl/AdGuardHome_linux_arm.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_armv5\": \"$baseUrl/AdGuardHome_linux_armv5.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_arm64\": \"$baseUrl/AdGuardHome_linux_arm64.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_mips\": \"$baseUrl/AdGuardHome_linux_mips.tar.gz\"," >> $dst/version.json
-echo "  \"download_linux_mipsle\": \"$baseUrl/AdGuardHome_linux_mipsle.tar.gz\"," >> $dst/version.json
-echo "  \"download_freebsd_amd64\": \"$baseUrl/AdGuardHome_freebsd_amd64.tar.gz\"," >> $dst/version.json
-echo "  \"selfupdate_min_version\": \"v0.0\"" >> $dst/version.json
-echo "}" >> $dst/version.json
diff --git a/build_snap.sh b/build_snap.sh
deleted file mode 100755
index 13dae65f..00000000
--- a/build_snap.sh
+++ /dev/null
@@ -1,292 +0,0 @@
-#!/usr/bin/env bash
-
-set -eE
-set -o pipefail
-set -x
-
-BUILDER_IMAGE="adguard/snapcraft:1.0"
-SNAPCRAFT_TMPL="packaging/snap/snapcraft.yaml"
-SNAP_NAME="adguard-home"
-LAUNCHPAD_CREDENTIALS_DIR=".local/share/snapcraft/provider/launchpad"
-
-if [[ -z ${VERSION} ]]; then
-    VERSION=$(git describe --abbrev=4 --dirty --always --tags)
-    echo "VERSION env variable is not set, getting it from git: ${VERSION}"
-fi
-
-# If bash is interactive, set `-it` parameter for docker run
-INTERACTIVE=""
-if [ -t 0 ]; then
-    INTERACTIVE="-it"
-fi
-
-function usage() {
-    cat <<EOF
-    Usage: ${0##*/} command [options]
-
-    Please note that in order for the builds to work properly, you need to setup some env variables.
-
-    These are necessary for "remote-build' command.
-    Read this doc on how to generate them: https://uci.readthedocs.io/en/latest/oauth.html
-
-        * LAUNCHPAD_KEY -- launchpad CI key
-        * LAUNCHPAD_ACCESS_TOKEN -- launchpad access token
-        * LAUNCHPAD_ACCESS_SECRET -- launchpad access secret
-
-    These are necessary for snapcraft publish command to work.
-    They can be exported using "snapcraft export-login"
-
-        * SNAPCRAFT_MACAROON
-        * SNAPCRAFT_UBUNTU_DISCHARGE
-        * SNAPCRAFT_EMAIL
-
-    Examples:
-        ${0##*/} build-docker - builds snaps using remote-build inside a Docker environment
-        ${0##*/} build - builds snaps using remote-build
-        ${0##*/} publish-docker-beta - publishes snaps to the beta channel using Docker environment
-        ${0##*/} publish-docker-release - publishes snaps to the release channel using Docker environment
-        ${0##*/} publish-beta - publishes snaps to the beta channel
-        ${0##*/} publish-release - publishes snaps to the release channel
-        ${0##*/} cleanup - clean up temporary files that were created by the builds
-EOF
-    exit 1
-}
-
-#######################################
-# helper functions
-#######################################
-
-function prepare() {
-    if [ -z "${LAUNCHPAD_KEY}" ] || [ -z "${LAUNCHPAD_ACCESS_TOKEN}" ] || [ -z "${LAUNCHPAD_ACCESS_SECRET}" ]; then
-        echo "Launchpad oauth tokens are not set, exiting"
-        usage
-        exit 1
-    fi
-
-    if [ -z "${SNAPCRAFT_MACAROON}" ] || [ -z "${SNAPCRAFT_UBUNTU_DISCHARGE}" ] || [ -z "${SNAPCRAFT_EMAIL}" ]; then
-        echo "Snapcraft auth params are not set, exiting"
-        usage
-        exit 1
-    fi
-
-    # Launchpad oauth tokens data is necessary to run snapcraft remote-build
-    #
-    # Here's an instruction on how to generate launchpad OAuth tokens:
-    # https://uci.readthedocs.io/en/latest/oauth.html
-    #
-    # Launchpad credentials are necessary to run snapcraft remote-build command
-    echo "[1]
-    consumer_key = ${LAUNCHPAD_KEY}
-    consumer_secret =
-    access_token = ${LAUNCHPAD_ACCESS_TOKEN}
-    access_secret = ${LAUNCHPAD_ACCESS_SECRET}
-    " >launchpad_credentials
-
-    # Snapcraft login data
-    # It can be exported using snapcraft export-login command
-    echo "[login.ubuntu.com]
-    macaroon = ${SNAPCRAFT_MACAROON}
-    unbound_discharge = ${SNAPCRAFT_UBUNTU_DISCHARGE}
-    email = ${SNAPCRAFT_EMAIL}" >snapcraft_login
-
-    # Prepare the snap configuration
-    cp ${SNAPCRAFT_TMPL} ./snapcraft.yaml
-    sed -i.bak 's/dev_version/'"${VERSION}"'/g' ./snapcraft.yaml
-    rm -f snapcraft.yaml.bak
-}
-
-build_snap() {
-    # prepare credentials
-    prepare
-
-    # copy them to the directory where snapcraft will be able to read them
-    mkdir -p ~/${LAUNCHPAD_CREDENTIALS_DIR}
-    cp -f snapcraft_login ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
-    chmod 600 ~/${LAUNCHPAD_CREDENTIALS_DIR}/credentials
-
-    # run the build
-    snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
-
-    # remove the credentials - we don't need them anymore
-    rm -rf ~/${LAUNCHPAD_CREDENTIALS_DIR}
-
-    # remove version from the file name
-    rename_snap_file
-
-    # cleanup credentials
-    cleanup
-}
-
-build_snap_docker() {
-    # prepare credentials
-    prepare
-
-    docker run ${INTERACTIVE} --rm \
-        -v $(pwd):/build \
-        -v $(pwd)/launchpad_credentials:/root/${LAUNCHPAD_CREDENTIALS_DIR}/credentials:ro \
-        ${BUILDER_IMAGE} \
-        snapcraft remote-build --build-on=${ARCH} --launchpad-accept-public-upload
-
-    # remove version from the file name
-    rename_snap_file
-
-    # cleanup credentials
-    cleanup
-}
-
-rename_snap_file() {
-    # In order to make working with snaps easier later on
-    # we remove version from the file name
-
-    # Check that the snap file exists
-    snapFile="${SNAP_NAME}_${VERSION}_${ARCH}.snap"
-    if [ ! -f ${snapFile} ]; then
-        echo "Snap file ${snapFile} not found!"
-        exit 1
-    fi
-
-    mv -f ${snapFile} "${SNAP_NAME}_${ARCH}.snap"
-}
-
-publish_snap() {
-    # prepare credentials
-    prepare
-
-    # Check that the snap file exists
-    snapFile="${SNAP_NAME}_${ARCH}.snap"
-    if [ ! -f ${snapFile} ]; then
-        echo "Snap file ${snapFile} not found!"
-        exit 1
-    fi
-
-    # Login if necessary
-    snapcraft login --with=snapcraft_login
-
-    # Push to the channel
-    snapcraft push --release=${CHANNEL} ${snapFile}
-
-    # cleanup credentials
-    cleanup
-}
-
-publish_snap_docker() {
-    # prepare credentials
-    prepare
-
-    # Check that the snap file exists
-    snapFile="${SNAP_NAME}_${ARCH}.snap"
-    if [ ! -f ${snapFile} ]; then
-        echo "Snap file ${snapFile} not found!"
-        exit 1
-    fi
-
-    # Login and publish the snap
-    docker run ${INTERACTIVE} --rm \
-        -v $(pwd):/build \
-        ${BUILDER_IMAGE} \
-        sh -c "snapcraft login --with=/build/snapcraft_login && snapcraft push --release=${CHANNEL} /build/${snapFile}"
-
-    # cleanup credentials
-    cleanup
-}
-
-#######################################
-# main functions
-#######################################
-
-build() {
-    if [[ -n "$1" ]]; then
-        echo "ARCH is set to $1"
-        ARCH=$1 build_snap
-    else
-        ARCH=i386 build_snap
-        ARCH=arm64 build_snap
-        ARCH=armhf build_snap
-        ARCH=amd64 build_snap
-    fi
-}
-
-build_docker() {
-    if [[ -n "$1" ]]; then
-        echo "ARCH is set to $1"
-        ARCH=$1 build_snap_docker
-    else
-        ARCH=i386 build_snap_docker
-        ARCH=arm64 build_snap_docker
-        ARCH=armhf build_snap_docker
-        ARCH=amd64 build_snap_docker
-    fi
-}
-
-publish_docker() {
-    if [[ -z $1 ]]; then
-        echo "No channel specified"
-        exit 1
-    fi
-    CHANNEL="${1}"
-    if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
-        echo "$CHANNEL is an invalid value for the update channel!"
-        exit 1
-    fi
-
-    if [[ -n "$2" ]]; then
-        echo "ARCH is set to $2"
-        ARCH=$2 publish_snap_docker
-    else
-        ARCH=i386 publish_snap_docker
-        ARCH=arm64 publish_snap_docker
-        ARCH=armhf publish_snap_docker
-        ARCH=amd64 publish_snap_docker
-    fi
-}
-
-publish() {
-    if [[ -z $1 ]]; then
-        echo "No channel specified"
-        exit 1
-    fi
-    CHANNEL="${1}"
-    if [ "$CHANNEL" != "stable" ] && [ "$CHANNEL" != "beta" ]; then
-        echo "$CHANNEL is an invalid value for the update channel!"
-        exit 1
-    fi
-
-    if [[ -n "$2" ]]; then
-        echo "ARCH is set to $2"
-        ARCH=$2 publish_snap
-    else
-        ARCH=i386 publish_snap
-        ARCH=arm64 publish_snap
-        ARCH=armhf publish_snap
-        ARCH=amd64 publish_snap
-    fi
-}
-
-cleanup() {
-    rm -f launchpad_credentials
-    rm -f snapcraft.yaml
-    rm -f snapcraft.yaml.bak
-    rm -f snapcraft_login
-    git checkout snapcraft.yaml
-}
-
-#######################################
-# main
-#######################################
-if [[ -z $1 || $1 == "--help" || $1 == "-h" ]]; then
-    usage
-fi
-
-case "$1" in
-"build-docker") build_docker $2 ;;
-"build") build $2 ;;
-"publish-docker-beta") publish_docker beta $2 ;;
-"publish-docker-release") publish_docker stable $2 ;;
-"publish-beta") publish beta $2 ;;
-"publish-release") publish stable $2 ;;
-"prepare") prepare ;;
-"cleanup") cleanup ;;
-*) usage ;;
-esac
-
-exit 0
diff --git a/ci.sh b/ci.sh
deleted file mode 100755
index 544f0109..00000000
--- a/ci.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-set -e
-set -x
-
-echo "Starting AdGuard Home CI script"
-
-# Print the current directory contents
-ls -la
-
-# Check versions and current directory
-node -v
-npm -v
-go version
-golangci-lint --version
-
-# Run linter
-golangci-lint run
-
-# Make
-make clean
-make build/static/index.html
-make
-
-# Run tests
-go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
-
-# if [[ -z "$(git status --porcelain)" ]]; then
-#     # Working directory clean
-#     echo "Git status is clean"
-# else
-#     echo "Git status is not clean and contains uncommited changes"
-#     echo "Please make sure there are no changes"
-#     exit 1
-# fi
-
-echo "AdGuard Home CI script finished successfully"
\ No newline at end of file
diff --git a/go.sum b/go.sum
index 6d05c0a3..415f71a0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,5 @@
 github.com/AdguardTeam/dnsproxy v0.29.1 h1:Stc+JLh67C9K38vbrH2920+3FnbXKkFzYQqRiu5auUo=
 github.com/AdguardTeam/dnsproxy v0.29.1/go.mod h1:hOYFV9TW+pd5XKYz7KZf2FFD8SvSPqjyGTxUae86s58=
-github.com/AdguardTeam/golibs v0.4.0 h1:4VX6LoOqFe9p9Gf55BeD8BvJD6M6RDYmgEiHrENE9KU=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
@@ -42,15 +41,17 @@ github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc=
 github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
 github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
 github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
 github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
 github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
 github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
 github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
+github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
 github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
@@ -58,13 +59,13 @@ github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk
 github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
 github.com/kardianos/service v1.0.0 h1:HgQS3mFfOlyntWX8Oke98JcJLqt1DBcHR4kxShpYef0=
 github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
+github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
 github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -90,11 +91,13 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ=
 github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw=
 github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
@@ -102,9 +105,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -112,9 +113,7 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -124,29 +123,24 @@ golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
 golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -154,7 +148,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
diff --git a/main.go b/main.go
index 449e3834..eebcd5c4 100644
--- a/main.go
+++ b/main.go
@@ -1,3 +1,6 @@
+//go:generate go install -v github.com/gobuffalo/packr/packr
+//go:generate packr clean
+//go:generate packr -z
 package main
 
 import (
diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile
deleted file mode 100644
index 6677d928..00000000
--- a/packaging/docker/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-FROM golang:alpine AS build
-
-RUN apk add --update git make build-base npm && \
-    rm -rf /var/cache/apk/*
-
-WORKDIR /src/AdGuardHome
-COPY . /src/AdGuardHome
-RUN make
-
-FROM alpine:latest
-LABEL maintainer="AdGuard Team <devteam@adguard.com>"
-
-# Update CA certs
-RUN apk --no-cache --update add ca-certificates libcap && \
-    rm -rf /var/cache/apk/* && \
-    mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
-    chown -R nobody: /opt/adguardhome
-
-COPY --from=build --chown=nobody:nogroup /src/AdGuardHome/AdGuardHome /opt/adguardhome/AdGuardHome
-
-RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
-
-EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
-
-VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
-
-WORKDIR /opt/adguardhome/work
-
-#USER nobody
-
-ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
-CMD ["-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
diff --git a/packaging/docker/Dockerfile.hub b/packaging/docker/Dockerfile.hub
deleted file mode 100644
index 0e11574e..00000000
--- a/packaging/docker/Dockerfile.hub
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM alpine:latest
-LABEL maintainer="AdGuard Team <devteam@adguard.com>"
-
-# Update CA certs
-RUN apk --no-cache --update add ca-certificates libcap && \
-    rm -rf /var/cache/apk/* && \
-    mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
-    chown -R nobody: /opt/adguardhome
-
-COPY --chown=nobody:nogroup ./AdGuardHome /opt/adguardhome/AdGuardHome
-
-RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
-
-EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
-
-VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
-
-WORKDIR /opt/adguardhome/work
-
-#USER nobody
-
-ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
-CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]
diff --git a/packaging/docker/README.md b/packaging/docker/README.md
deleted file mode 100644
index 0d7ff06f..00000000
--- a/packaging/docker/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-## Docker images
-
-* `Dockerfile` is used for local development. Build it using `make docker` command.
-
-* `Dockerfile.hub` is used to publish AdGuard images to Docker Hub: https://hub.docker.com/r/adguard/adguardhome
-    Check out `build_docker.sh` for the details.
\ No newline at end of file
diff --git a/packaging/snap/README.md b/packaging/snap/README.md
deleted file mode 100644
index 764100ff..00000000
--- a/packaging/snap/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-## Snapcraft
-
-Configuration for our snap.
-
-Check out `build_snap.sh` for more details.
\ No newline at end of file
diff --git a/packaging/snap/snapcraft.yaml b/packaging/snap/snapcraft.yaml
deleted file mode 100644
index 1d6c5274..00000000
--- a/packaging/snap/snapcraft.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: adguard-home
-base: core18
-version: 'dev_version'
-summary: Network-wide ads & trackers blocking DNS server
-description: |
-  AdGuard Home is a network-wide software for blocking ads & tracking. After
-  you set it up, it'll cover ALL your home devices, and you don't need any
-  client-side software for that.
-
-  It operates as a DNS server that re-routes tracking domains to a "black hole,"
-  thus preventing your devices from connecting to those servers. It's based
-  on software we use for our public AdGuard DNS servers -- both share a lot
-  of common code.
-grade: stable
-confinement: strict
-
-parts:
-  adguard-home:
-    plugin: make
-    source: .
-    build-snaps: [ node/13/stable, go ]
-    build-packages: [ git, build-essential ]
-    override-build: |
-      make clean
-      make
-      cp AdGuardHome ${SNAPCRAFT_PART_INSTALL}/
-apps:
-  adguard-home:
-    command: AdGuardHome -w ${SNAP_DATA} --no-check-update
-    plugs: [ network-bind ]
-    daemon: simple
-    restart-condition: always
\ No newline at end of file
diff --git a/snapcraft.yaml b/snapcraft.yaml
deleted file mode 100644
index c5cda82b..00000000
--- a/snapcraft.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-# Note that this snapcraft.yaml file is used for automatic Edge channel builds ONLY!
-# We use packaging/snap/snapcraft.yaml for beta and release builds
-# Check out build_snap.sh for more details
-name: adguard-home
-base: core18
-version: 'edge'
-summary: Network-wide ads & trackers blocking DNS server
-description: |
-  AdGuard Home is a network-wide software for blocking ads & tracking. After
-  you set it up, it'll cover ALL your home devices, and you don't need any
-  client-side software for that.
-
-  It operates as a DNS server that re-routes tracking domains to a "black hole,"
-  thus preventing your devices from connecting to those servers. It's based
-  on software we use for our public AdGuard DNS servers -- both share a lot
-  of common code.
-grade: stable
-confinement: strict
-
-architectures:
-  - build-on: amd64
-  - build-on: armhf
-  - build-on: i386
-  - build-on: arm64
-
-parts:
-  adguard-home:
-    plugin: make
-    source: .
-    build-snaps: [ node/13/stable, go ]
-    build-packages: [ git, build-essential ]
-    override-build: |
-      make clean
-      make
-      cp AdGuardHome ${SNAPCRAFT_PART_INSTALL}/
-apps:
-  adguard-home:
-    command: AdGuardHome -w ${SNAP_DATA} --no-check-update
-    plugs: [ network-bind ]
-    daemon: simple
-    restart-condition: always
diff --git a/tools.go b/tools.go
new file mode 100644
index 00000000..350f00a6
--- /dev/null
+++ b/tools.go
@@ -0,0 +1,8 @@
+// +build tools
+
+package tools
+
+import (
+	// Import packr
+	_ "github.com/gobuffalo/packr/packr"
+)

From 632a47d56f292ee19b84f6630d9236332613ec08 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <ay.meshkov@gmail.com>
Date: Thu, 9 Jul 2020 19:54:53 +0300
Subject: [PATCH 02/12] 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<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$"
-    }
-  },
-  "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 @@
     <a href="https://twitter.com/AdGuard">Twitter</a> |
     <a href="https://t.me/adguard_en">Telegram</a>
     <br /><br />
-    <a href="https://travis-ci.com/AdguardTeam/AdGuardHome">
-      <img src="https://travis-ci.com/AdguardTeam/AdGuardHome.svg" alt="Build status" />
-    </a>
     <a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
       <img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage" />
     </a>
@@ -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`
 
 <a id="contributing"></a>
 ## 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() {

From 2bbffffcfc875558eddf590b3185b968ff969ede Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 14:57:53 +0300
Subject: [PATCH 03/12] Fix project name, update README

---
 .goreleaser.yml |  4 +++-
 Makefile        | 11 +++++------
 README.md       |  8 ++++++++
 3 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/.goreleaser.yml b/.goreleaser.yml
index f698bcf1..b2e11b09 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,3 +1,5 @@
+project_name: AdGuardHome
+
 env:
   - GO111MODULE=on
   - GOPROXY=https://goproxy.io
@@ -80,7 +82,7 @@ snapcrafts:
     license: GPL-3.0
     apps:
       adguard-home:
-        args: -w ${SNAP_DATA} --no-check-update
+        command: AdGuardHome -w $SNAP_DATA --no-check-update
         plugs: [ network-bind ]
         daemon: simple
 
diff --git a/Makefile b/Makefile
index 7de43f3a..6c0bb5fa 100644
--- a/Makefile
+++ b/Makefile
@@ -43,9 +43,7 @@ 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%=%)
+RELEASE_VERSION=$(TAG_NAME)
 SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
 
 # Set proper version
@@ -72,7 +70,8 @@ ifndef DOCKER_TAGS
 		DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):beta
 	endif
 	ifeq ($(CHANNEL),edge)
-		DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):edge
+		# Overwrite the "version" tag when we push to the edge channel
+		DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):edge
 	endif
 endif
 
@@ -171,12 +170,12 @@ define write_version_file
 
 	# Variables for CI
 	rm -f $(DIST_DIR)/version.txt
-	echo "version=v$(version)" > $(DIST_DIR)/version.txt
+	echo "version=$(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 "  \"version\": \"$(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
diff --git a/README.md b/README.md
index 602c1be0..ab8f306d 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,14 @@ Run `make snapshot` or `make release` to build all AdGuard distrs.
 * 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).
 
+Please note, that we're using [Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/) to build our official image.
+
+You may need to prepare before using these builds:
+
+* (Linux-only) Install Qemu: `docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes`
+* Prepare builder: `docker buildx create --name buildx-builder --driver docker-container --use`
+
+
 ### Resources that we update periodically
 
 * `scripts/translations`

From db0283098c30eab5c32492a67b2ebea3f72b2adb Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 16:58:52 +0300
Subject: [PATCH 04/12] Improve makefile clean target

---
 Makefile | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 6c0bb5fa..3eaf6d30 100644
--- a/Makefile
+++ b/Makefile
@@ -127,6 +127,9 @@ clean:
 	# make build output
 	rm -f AdGuardHome
 	rm -f AdGuardHome.exe
+	# tests output
+	rm -rf data
+	rm coverage.txt
 	# static build output
 	rm -rf build
 	# dist folder
@@ -134,7 +137,7 @@ clean:
 	# client deps
 	rm -rf client/node_modules
 	# packr-generated files
-	PATH=$(GOPATH)/bin:$(PATH) packr clean
+	command -v packr && PATH=$(GOPATH)/bin:$(PATH) packr clean
 
 docker-multi-arch:
 	DOCKER_CLI_EXPERIMENTAL=enabled \

From dc5c89795b4266e607a9da2e9b572fa89937906b Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 17:10:11 +0300
Subject: [PATCH 05/12] fix packr detect

---
 Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 3eaf6d30..817fbd34 100644
--- a/Makefile
+++ b/Makefile
@@ -129,7 +129,7 @@ clean:
 	rm -f AdGuardHome.exe
 	# tests output
 	rm -rf data
-	rm coverage.txt
+	rm -f coverage.txt
 	# static build output
 	rm -rf build
 	# dist folder
@@ -137,7 +137,7 @@ clean:
 	# client deps
 	rm -rf client/node_modules
 	# packr-generated files
-	command -v packr && PATH=$(GOPATH)/bin:$(PATH) packr clean
+	which packr && PATH=$(GOPATH)/bin:$(PATH) packr clean
 
 docker-multi-arch:
 	DOCKER_CLI_EXPERIMENTAL=enabled \

From 0be029913574df8af54b70e70607fecb0503e3c7 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 17:18:04 +0300
Subject: [PATCH 06/12] don't fail on packr

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 817fbd34..03482143 100644
--- a/Makefile
+++ b/Makefile
@@ -137,7 +137,7 @@ clean:
 	# client deps
 	rm -rf client/node_modules
 	# packr-generated files
-	which packr && PATH=$(GOPATH)/bin:$(PATH) packr clean
+	PATH=$(GOPATH)/bin:$(PATH) packr clean || true
 
 docker-multi-arch:
 	DOCKER_CLI_EXPERIMENTAL=enabled \

From ab401cabe2e461be9be3dc88aeaa01ae8d95c8e6 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 18:28:09 +0300
Subject: [PATCH 07/12] Don't set the version tag for edge channel

---
 Makefile | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 03482143..d91f98fe 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@ endif
 
 # Docker target parameters
 DOCKER_IMAGE_NAME ?= adguardhome-dev
+DOCKER_IMAGE_FULL_NAME = $(DOCKER_IMAGE_NAME):$(VERSION)
 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')
@@ -64,14 +65,15 @@ BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
 DOCKER_TAGS ?=
 ifndef DOCKER_TAGS
 	ifeq ($(CHANNEL),release)
-		DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):latest
+		DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):latest
 	endif
 	ifeq ($(CHANNEL),beta)
-		DOCKER_TAGS := $(DOCKER_TAGS) --tag $(DOCKER_IMAGE_NAME):beta
+		DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):beta
 	endif
 	ifeq ($(CHANNEL),edge)
-		# Overwrite the "version" tag when we push to the edge channel
-		DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):edge
+		# Don't set the version tag when pushing to "edge"
+		DOCKER_IMAGE_FULL_NAME := $(DOCKER_IMAGE_NAME):edge
+		# DOCKER_TAGS := --tag $(DOCKER_IMAGE_NAME):edge
 	endif
 endif
 
@@ -149,7 +151,7 @@ docker-multi-arch:
 	--build-arg BUILD_DATE=$(BUILD_DATE) \
 	$(DOCKER_TAGS) \
 	--output "$(DOCKER_OUTPUT)" \
-	-t "$(DOCKER_IMAGE_NAME):$(VERSION)" -f ./Dockerfile .
+	-t "$(DOCKER_IMAGE_FULL_NAME)" -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)

From fdf608904dc45ec323b77d71dcccdbedba7f7f51 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 22:20:30 +0300
Subject: [PATCH 08/12] Fix version check when req body is empty

---
 home/control_update.go | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/home/control_update.go b/home/control_update.go
index 80ade843..d734f4d1 100644
--- a/home/control_update.go
+++ b/home/control_update.go
@@ -45,10 +45,13 @@ func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
 	}
 
 	req := getVersionJSONRequest{}
-	err := json.NewDecoder(r.Body).Decode(&req)
-	if err != nil {
-		httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
-		return
+	var err error
+	if r.ContentLength != 0 {
+		err = json.NewDecoder(r.Body).Decode(&req)
+		if err != nil {
+			httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
+			return
+		}
 	}
 
 	now := time.Now()

From f6217e602b42439ae267ba01b104d640fa8a9f4d Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 22:20:46 +0300
Subject: [PATCH 09/12] Excluded some paths from golang linter

---
 .golangci.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.golangci.yml b/.golangci.yml
index 47425e3f..be43ec74 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -15,6 +15,9 @@ run:
     - dnsfilter/rule_to_regexp.go
     - util/pprof.go
     - ".*_test.go"
+    - client/.*
+    - build/.*
+    - dist/.*
 
 
 # all available settings of specific linters

From 1fe568b842002f456bed1d80502cbdd247bedd56 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 22:21:11 +0300
Subject: [PATCH 10/12] Updated links to beta&edge versions in README

---
 README.md | 43 +++++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/README.md b/README.md
index ab8f306d..9054e504 100644
--- a/README.md
+++ b/README.md
@@ -202,26 +202,41 @@ You are welcome to fork this repository, make your changes and submit a pull req
 <a id="test-unstable-versions"></a>
 ### Test unstable versions
 
+There are two update channels that you can use:
+
+* `beta` - beta version of AdGuard Home. More or less stable versions.
+* `edge` - the newest version of AdGuard Home. New updates are pushed to this channel daily and it is the closest to the master branch you can get.
+
+There are three options how you can install an unstable version:
+
+1. [Snap Store](https://snapcraft.io/adguard-home) -- look for "beta" and "edge" channels there.
+2. [Docker Hub](https://hub.docker.com/r/adguard/adguardhome) -- look for "beta" and "edge" tags there.
+3. Standalone builds. Look for the available builds below.
+
 There are three options how you can install an unstable version.
 
 1. You can either install a beta version of AdGuard Home which we update periodically.
 2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
 3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.
 
-* Beta builds
-    * [Raspberry Pi (32-bit ARMv6)](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm.tar.gz)
-    * [MacOS](https://static.adguard.com/adguardhome/beta/AdGuardHome_MacOS.zip)
-    * [Windows 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_amd64.zip)
-    * [Windows 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_386.zip)
-    * [Linux 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz)
-    * [Linux 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
-    * [FreeBSD 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz)
-    * [Linux 64-bit ARM](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz)
-    * [Linux 32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz)
-    * [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.tar.gz)
-    * [MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle.tar.gz)
-* [Docker Hub](https://hub.docker.com/r/adguard/adguardhome)
-* [Snap Store](https://snapcraft.io/adguard-home)
+* Beta channel builds
+    * Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
+    * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz)
+    * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz)
+    * Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip)
+    * MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip)
+    * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_386.tar.gz)
+    * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_freebsd_armv7.tar.gz)
+
+* Edge channel builds
+    * Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz)
+    * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz)
+    * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz)
+    * Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip)
+    * MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip)
+    * FreeBSD: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_386.tar.gz)
+    * FreeBSD ARM: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv5.tar.gz), [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv6.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_freebsd_armv7.tar.gz)
+
 
 <a id="reporting-issues"></a>
 ### Report issues

From 6811d6207a33b7215256e8a733d8ac197fbf9b1d Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 23:24:43 +0300
Subject: [PATCH 11/12] Removed snapshot target

---
 Makefile  | 24 ++++++++++++------------
 README.md |  6 +++++-
 2 files changed, 17 insertions(+), 13 deletions(-)

diff --git a/Makefile b/Makefile
index d91f98fe..f577ea7c 100644
--- a/Makefile
+++ b/Makefile
@@ -13,8 +13,7 @@
 #
 # 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.
+# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta).
 # * 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
@@ -25,7 +24,7 @@ PWD := $(shell pwd)
 TARGET=AdGuardHome
 BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
 
-# See release and snapshot targets
+# See release target
 DIST_DIR=dist
 
 # Update channel. Can be release, beta or edge. Uses edge by default.
@@ -40,6 +39,13 @@ endif
 endif
 endif
 
+# goreleaser command depends on the $CHANNEL
+GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot
+ifneq ($(CHANNEL),edge)
+	# If this is not an "edge" build, use normal release command
+	GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish
+endif
+
 # Version properties
 COMMIT=$(shell git rev-parse --short HEAD)
 TAG_NAME=$(shell git describe --abbrev=0)
@@ -82,7 +88,7 @@ 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
+.PHONY: all build client client-watch docker lint test dependencies clean release docker-multi-arch
 all: build
 
 build: dependencies client
@@ -156,15 +162,9 @@ docker-multi-arch:
 	@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
+	CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
 	$(call write_version_file,$(VERSION))
 	PATH=$(GOPATH)/bin:$(PATH) packr clean
 
@@ -190,8 +190,8 @@ define write_version_file
 	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
+	echo "  \"download_darwin_386\": \"$(BASE_URL)/AdGuardHome_darwin_386.zip\"," >> $(DIST_DIR)/version.json
 
 	# Linux
 	echo "  \"download_linux_amd64\": \"$(BASE_URL)/AdGuardHome_linux_amd64.tar.gz\"," >> $(DIST_DIR)/version.json
diff --git a/README.md b/README.md
index 9054e504..17421a5d 100644
--- a/README.md
+++ b/README.md
@@ -174,7 +174,11 @@ 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.
+Commands:
+
+* `make release` - builds a snapshot build (CHANNEL=edge)
+* `CHANNEL=beta make release` - builds beta version, tag is mandatory.
+* `CHANNEL=release make release` - builds release version, tag is mandatory.
 
 #### Docker image
 

From c2642cc58ece302182148c7126d3b9b1176166a8 Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Fri, 10 Jul 2020 23:54:02 +0300
Subject: [PATCH 12/12] fix GH actions snapshot build

---
 .github/workflows/build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0c2fdebb..5c75c8ba 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -128,7 +128,7 @@ jobs:
       -
         name: Run snapshot build
         run: |
-          make snapshot
+          make release
 
   docker:
     runs-on: ubuntu-latest