Compare commits

...

344 Commits
v0.6.6 ... main

Author SHA1 Message Date
bakito-renovate[bot]
2244fb6666 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.13.0 (#705)
* chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.13.0

* Update Go version from 1.25.3 to 1.25.4

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2025-12-01 05:23:03 +01:00
bakito-renovate[bot]
d76a633ceb chore(deps): update module github.com/anchore/syft/cmd/syft to v1.38.0 (#703)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-24 07:23:58 +01:00
bakito-renovate[bot]
91682b1867 chore(deps): update module go.uber.org/zap to v1.27.1 (#702)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-24 06:51:00 +01:00
bakito-renovate[bot]
a56663cf73 chore(deps): update module github.com/go-resty/resty/v2 to v2.17.0 (#704)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-24 06:50:46 +01:00
dependabot[bot]
22439e0de1 chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 (#701)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 06:45:59 +01:00
bakito-renovate[bot]
81ea4311bd chore(deps): update module k8s.io/apimachinery to v0.34.2 (#699)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-16 16:54:28 +01:00
bakito-renovate[bot]
2d9df306a8 chore(deps): update module golang.org/x/mod to v0.30.0 (#700)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-16 16:07:51 +01:00
bakito-renovate[bot]
b6c6c0d87a chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.2 (#698)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-16 16:04:27 +01:00
Marc Brugger
afee473692 feat: generate yaml config docs (#696) 2025-11-11 20:40:57 +01:00
bakito-renovate[bot]
dde2356599 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.1 (#694)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-10 06:55:01 +01:00
bakito-renovate[bot]
c4557f71e5 chore(deps): update module github.com/anchore/syft/cmd/syft to v1.37.0 (#695)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-10 06:54:50 +01:00
bakito-renovate[bot]
35159a4f64 chore(deps): update module github.com/oapi-codegen/oapi-codegen/v2 to v2.5.1 (#690)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-03 07:06:21 +01:00
bakito-renovate[bot]
053a9890e2 chore(deps): update dependency adguardteam/adguardhome to v0.107.69 (#689)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-03 06:45:57 +01:00
bakito-renovate[bot]
6c9489e80e chore(deps): update module github.com/onsi/ginkgo/v2 to v2.27.2 (#691)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-03 06:45:24 +01:00
bakito-renovate[bot]
cd82169016 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.6.0 (#692)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-11-03 06:45:01 +01:00
Marc Brugger
6f498aedc6 Add riscv64 architecture support to goreleaser (#688) 2025-11-01 00:32:21 +01:00
Marc Brugger
57f8938ccc test: extend caches for build (#686)
* test: extend caches for build

* Update caching strategy in Go workflow

* Remove extra whitespace in go.yml
2025-10-26 17:15:30 +01:00
bakito
25e7aeeecb test: setup cache 2025-10-25 21:54:06 +02:00
bakito-renovate[bot]
b77248d82d chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.7 (#684)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-24 20:30:42 +02:00
bakito
85f996c516 fix: correct release task 2025-10-24 19:09:40 +02:00
bakito-renovate[bot]
0d04d693aa chore(deps): update module github.com/onsi/ginkgo/v2 to v2.27.1 (#682)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-24 18:41:22 +02:00
bakito-renovate[bot]
e5f5321f45 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.6 (#680)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-24 18:40:56 +02:00
bakito-renovate[bot]
8b4e78b148 chore(deps): update module github.com/anchore/syft/cmd/syft to v1.36.0 (#681)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-24 18:40:41 +02:00
bakito-renovate[bot]
d306fba8e2 chore(deps): update dependency adguardteam/adguardhome to v0.107.68 (#679)
* fix: upgrade adguardhome schema to v0.107.68

* chore(deps): update dependency adguardteam/adguardhome to v0.107.68

---------

Co-authored-by: bakito <github@bakito.ch>
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-24 18:40:07 +02:00
bakito
5648639607 feat: generate sbom 2025-10-18 15:16:09 +02:00
bakito-renovate[bot]
35c1af2cea chore(deps): update module golang.org/x/mod to v0.29.0 (#675)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-13 07:58:04 +02:00
dependabot[bot]
2be15795e5 chore(deps): bump github.com/quic-go/quic-go from 0.54.0 to 0.54.1 (#674)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.0 to 0.54.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.54.0...v0.54.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.54.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 00:08:15 +02:00
bakito-renovate[bot]
2fe94ec964 chore(deps): update k8s.io/utils digest to bc988d5 (#669)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-06 22:55:34 +02:00
bakito-renovate[bot]
e0c00c3ccb chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.5 (#671)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-06 22:55:04 +02:00
bakito-renovate[bot]
a90113f470 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.26.0 (#672)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-10-06 22:43:30 +02:00
bakito-renovate[bot]
46ec505fd9 chore(deps): update dependency adguardteam/adguardhome to v0.107.67 (#670)
* chore(deps): update dependency adguardteam/adguardhome to v0.107.67

* Update wait-for-start.sh

* Update read-latest-replica-config.sh

* Update values.yaml

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2025-10-06 22:33:44 +02:00
bakito-renovate[bot]
5a47f8857d chore(deps): update module github.com/go-faker/faker/v4 to v4.7.0 (#668)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-29 07:00:24 +02:00
bakito-renovate[bot]
05485821c0 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.3 (#667)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-29 06:57:08 +02:00
bakito-renovate[bot]
3cce67b220 chore(deps): update module github.com/gin-gonic/gin to v1.11.0 (#665)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-22 02:49:46 +02:00
bakito-renovate[bot]
0429d86f40 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.5.0 (#666)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-22 02:49:30 +02:00
bakito-renovate[bot]
731d92e9d8 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.2 (#664)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-22 02:47:07 +02:00
bakito-renovate[bot]
7f99704566 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.12.1 (#662)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-16 20:25:06 +02:00
Marc Brugger
5acd3beff5 fix: correct division by 0 issue in web percentage (#661) 2025-09-15 21:00:21 +02:00
bakito
e10830434b chore: remove openapi patch for fixed upstream / move utils to cmd 2025-09-15 18:47:07 +02:00
bakito-renovate[bot]
c7a1d5dcef chore(deps): update dependency adguardteam/adguardhome to v0.107.66 (#660)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-15 17:56:45 +02:00
imgbot[bot]
7b4c342631 chore: [ImgBot] Optimize images (#659)
*Total -- 17.52kb -> 4.59kb (73.81%)

/media/adguardhome-sync.svg -- 8.76kb -> 2.29kb (73.81%)
/internal/sync/static/logo.svg -- 8.76kb -> 2.29kb (73.81%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-09-15 17:46:26 +02:00
Marc Brugger
700dbd2d63 feat: offline js and css (#658) 2025-09-15 17:37:06 +02:00
Marc Brugger
f5c9e822a7 fix: disable tls feature by default (#657) 2025-09-15 16:26:19 +02:00
bakito
fb5afb2505 fix: correct typo in index page 2025-09-15 08:10:43 +02:00
Marc Brugger
2882785b9b chore: move pkg to internal (#655) 2025-09-15 08:07:27 +02:00
Marc Brugger
7d9edf4fa0 feat: remove support for deprecated env vars (#654) 2025-09-15 07:59:29 +02:00
apo-mak
bc70795769 fix: Fix typo in RUN_ON_START description (#652) 2025-09-15 07:46:46 +02:00
bakito
ea92d376c7 chore: actions/checkout@v5 2025-09-15 07:21:03 +02:00
bakito-renovate[bot]
16e97cd9b2 chore(deps): update module github.com/go-faker/faker/v4 to v4.6.2 (#653)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-15 06:54:50 +02:00
Marc Brugger
20fe83745f feat: sync tls config (#651)
* feat: implement tls config sync

* feat: implement workaround for #633

* extend feature test

* add tests
2025-09-14 18:25:36 +02:00
Marc Brugger
961cf4dbb5 feat: implement workaround for #633 (#650) 2025-09-14 18:03:32 +02:00
bakito-renovate[bot]
81d1bbf27b chore(deps): update module k8s.io/apimachinery to v0.34.1 (#647)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-11 07:31:17 +02:00
Marc Brugger
0fe6bb3a0b chore: update github actions (#646) 2025-09-08 08:07:23 +02:00
bakito-renovate[bot]
847a38fe1c chore(deps): update module golang.org/x/mod to v0.28.0 (#645)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-08 07:02:36 +02:00
bakito-renovate[bot]
1db6a5a4e0 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.25.3 (#640)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:51:15 +02:00
bakito-renovate[bot]
6388eec95f chore(deps): update module github.com/spf13/cobra to v1.10.1 (#644)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:09:59 +02:00
bakito
31ac480737 update tools 2025-09-05 17:09:23 +02:00
bakito-renovate[bot]
6877b58b02 chore(deps): update module github.com/prometheus/client_golang to v1.23.2 (#641)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:07:36 +02:00
bakito-renovate[bot]
d212045058 chore(deps): update module github.com/bakito/semver to v1.1.7 (#639)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:07:24 +02:00
bakito-renovate[bot]
83f34fb377 chore(deps): update module k8s.io/apimachinery to v0.34.0 (#637)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 07:24:25 +02:00
bakito-renovate[bot]
f1a943491a chore(deps): update module github.com/kubernetes-sigs/controller-tools to v0.19.0 (#636)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 06:40:47 +02:00
bakito-renovate[bot]
75d9724db6 chore(deps): update onsi (#635)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 06:39:59 +02:00
bakito-renovate[bot]
531d63d04f chore(deps): update onsi (#632)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:42:02 +02:00
bakito-renovate[bot]
84999be6fc chore(deps): update module go.uber.org/mock to v0.6.0 (#631)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:49 +02:00
bakito-renovate[bot]
190ccdad8a chore(deps): update module github.com/uber-go/mock to v0.6.0 (#630)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:30 +02:00
bakito-renovate[bot]
49054242f8 chore(deps): update module github.com/segmentio/golines to v0.13.0 (#629)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:20 +02:00
bakito-renovate[bot]
930684ffc2 chore(deps): update k8s.io/utils digest to 0af2bda (#627)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:23:58 +02:00
bakito-renovate[bot]
3fac1d2ced chore(deps): update dependency adguardteam/adguardhome to v0.107.65 (#628)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:23:48 +02:00
bakito-renovate[bot]
c85579ce4a chore(deps): update golang docker tag to v1.25 (#625)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:46:46 +02:00
bakito-renovate[bot]
d0dd73220d chore(deps): update module github.com/bakito/semver to v1.1.5 (#623)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:31 +02:00
bakito-renovate[bot]
80f4436d98 chore(deps): update module k8s.io/apimachinery to v0.33.4 (#624)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:19 +02:00
bakito-renovate[bot]
fecc60cfca chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (#626)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:04 +02:00
bakito-renovate[bot]
b3d052db80 chore(deps): update module golang.org/x/mod to v0.27.0 (#622)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-11 06:43:10 +02:00
bakito-renovate[bot]
9cd88a0b47 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.1 (#618)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:41:18 +02:00
bakito-renovate[bot]
56eb522ea5 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.11.2 (#619)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:41:08 +02:00
bakito-renovate[bot]
e6feaddc15 chore(deps): update module github.com/prometheus/client_golang to v1.23.0 (#620)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:40:56 +02:00
bakito-renovate[bot]
70ae93f73a chore(deps): update dependency adguardteam/adguardhome to v0.107.64 (#617)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:40:46 +02:00
bakito-renovate[bot]
c3d6e696a1 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.11.1 (#616)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-28 07:46:26 +02:00
bakito-renovate[bot]
89f2a1eb82 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.0 (#614)
* chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.0

* fix lint

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-07-24 21:11:18 +02:00
bakito-renovate[bot]
307b050579 chore(deps): update module github.com/onsi/gomega to v1.38.0 (#615)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-24 20:47:07 +02:00
bakito-renovate[bot]
caddf2a029 chore(deps): update module github.com/oapi-codegen/runtime to v1.1.2 (#610)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:34 +02:00
bakito-renovate[bot]
20ff0192d5 chore(deps): update module k8s.io/apimachinery to v0.33.3 (#611)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:18 +02:00
bakito-renovate[bot]
7d8cfbaeed chore(deps): update module github.com/oapi-codegen/oapi-codegen/v2 to v2.5.0 (#612)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:04 +02:00
Marc Brugger
b07101c97a fix: Correct version check and refactor healthz check (#609) 2025-07-16 17:06:17 +02:00
Marc Brugger
447bfb24a9 test: extend versions check #607 (#608) 2025-07-16 07:55:00 +02:00
Marc Brugger
4fff8f4302 test: run goreleaser with --parallelism 2 (#606) 2025-07-14 20:48:51 +02:00
Michael Stegeman
04bd12d7dd feat: Add OpenBSD support to releaser. (#605)
* Add OpenBSD support to releaser.

* Skip upx on OpenBSD.
2025-07-14 20:01:18 +02:00
bakito-renovate[bot]
4e33a88163 chore(deps): update module golang.org/x/mod to v0.26.0 (#604)
* chore(deps): update module golang.org/x/mod to v0.26.0

* go 1.24.5

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-07-13 14:30:32 +02:00
bakito
4a38df1b59 chore: update goreleaser 2025-07-13 09:18:00 +02:00
Marc Brugger
9ec5c0412e add new logo (#603) 2025-07-13 08:23:12 +02:00
ElevenNotes
01676e12f3 feat: add unauthorized health check based on status (if all success = http OK) (#601)
* feat: add unauthorized health check based on status (if all success = http OK)

* feat: ignore v or other non-numerical version affixes and suffixes

* fix: syntax

* fix: add head not just get
2025-07-10 22:33:46 +02:00
Marc Brugger
78f7d08398 test: add rewrites for e2e test (#600) 2025-07-05 09:25:56 +02:00
bakito-renovate[bot]
11d4d14f4d chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.2.1 (#598)
* chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.2.1

* ignore revive var-naming

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-06-30 20:36:54 +02:00
bakito-renovate[bot]
ff91f4873a chore(deps): update dependency adguardteam/adguardhome to v0.107.63 (#597)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-27 16:43:31 +02:00
bakito-renovate[bot]
778bf7b516 chore(deps): update module k8s.io/apimachinery to v0.33.2 (#596)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-23 06:53:46 +02:00
bakito
cce24c1223 docs: add icon to readme 2025-06-19 09:28:38 +02:00
Ted Jangius
b4fabf687b Add logo (#595) 2025-06-18 21:57:16 +02:00
Joseph Williams
74ae6a9096 Docs: Clarify initial setup instructions for origin and replicas (#593)
* Disambiguate 'Prerequisites' section in README.md

* Correct  part of speech error in 'Prerequisite' section in README.md
2025-06-12 23:48:16 +02:00
bakito-renovate[bot]
92129ab069 chore(deps): update k8s.io/utils digest to 4c0f3b2 (#590)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:59:24 +02:00
bakito-renovate[bot]
d7f50086c3 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.10.2 (#591)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:59:11 +02:00
bakito-renovate[bot]
50f883337a chore(deps): update module golang.org/x/mod to v0.25.0 (#592)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:58:52 +02:00
bakito-renovate[bot]
5a35f7c9d1 chore(deps): update dependency adguardteam/adguardhome to v0.107.62 (#589)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-01 13:06:59 +02:00
bakito-renovate[bot]
1b1a712a65 chore(deps): update module github.com/santhosh-tekuri/jsonschema/v6 to v6.0.2 (#588)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-24 11:02:28 +02:00
bakito-renovate[bot]
724a20e47e chore(deps): update module github.com/gin-gonic/gin to v1.10.1 (#587)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-24 11:02:17 +02:00
Marc Brugger
64c922a1d8 feat: support request headers (#586) 2025-05-18 16:59:29 +02:00
bakito-renovate[bot]
905c783ca9 chore(deps): update module k8s.io/apimachinery to v0.33.1 (#585)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-18 10:10:30 +02:00
bakito-renovate[bot]
4eac62e6ce chore(deps): update module sigs.k8s.io/controller-tools/cmd/controller-gen to v0.18.0 (#583)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-12 07:18:01 +02:00
bakito
418409ac47 chore: re-generate mock and makefie deps 2025-05-05 20:16:27 +02:00
bakito-renovate[bot]
4895bfdc55 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.6 (#582)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-05 07:26:23 +02:00
bakito-renovate[bot]
976902f861 chore(deps): update github.com/google/pprof digest to c008609 (#580)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-03 09:05:28 +02:00
bakito-renovate[bot]
b06a1cfec0 chore(deps): update k8s.io/utils digest to 0f33e8f (#581)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-03 09:05:13 +02:00
bakito-renovate[bot]
449fce9d6a chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.9.0 (#578)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-02 21:16:37 +02:00
bakito-renovate[bot]
6b45caadfb chore(deps): update uber to v0.5.2 (#577)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-30 00:04:50 +02:00
bakito-renovate[bot]
06b49cb810 chore(deps): update module github.com/go-faker/faker/v4 to v4.6.1 (#576)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-27 08:28:35 +02:00
bakito
ae58133a0d chore: regenerate model 2025-04-26 15:23:37 +02:00
Marc Brugger
2b0bfb126e test: correct env var for test (#574) 2025-04-26 09:25:33 +02:00
Marc Brugger
97841e3f32 Correct env variable handling to not use unrelated variables (#571) 2025-04-26 09:01:30 +02:00
bakito-renovate[bot]
2fc2baf6f8 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.5 (#568)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-25 07:54:58 +02:00
bakito-renovate[bot]
06355a71da chore(deps): update k8s to v0.33.0 (#567)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-24 18:58:59 +02:00
Marc Brugger
9782c430af feat: implement sync metrics (#562) 2025-04-23 23:59:27 +02:00
bakito-renovate[bot]
cafdf14008 chore(deps): update indirect go dependencies (#563)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-23 07:25:59 +02:00
bakito-renovate[bot]
e7569de5c6 chore(deps): update dependency adguardteam/adguardhome to v0.107.61 (#566)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-23 07:20:21 +02:00
Sy
2b69fe15ed docs: Add Home Assistant paragraph to README (#564)
Adding guidance to help Home Assistant AdGuard Home Add-on users enable syncing.
2025-04-20 17:41:17 +02:00
Marc Brugger
bb1f2d02c5 fix: ignore empty config file in config validation (#561) 2025-04-16 12:51:27 +02:00
bakito-renovate[bot]
3bd093ceb9 chore(deps): update indirect go dependencies (#548)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 19:33:48 +02:00
bakito-renovate[bot]
05c0eab4a8 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.2 (#558)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 19:33:38 +02:00
bakito-renovate[bot]
57745f2953 chore(deps): update module github.com/prometheus/client_golang to v1.22.0 (#553)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:19:04 +02:00
bakito-renovate[bot]
9f5ac14aa1 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.1 (#554)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:48 +02:00
bakito-renovate[bot]
8f2aa58fe8 chore(deps): update module mvdan.cc/gofumpt to v0.8.0 (#555)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:31 +02:00
bakito-renovate[bot]
01e23d3b69 chore(deps): update dependency adguardteam/adguardhome to v0.107.60 (#557)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:18 +02:00
Marc Brugger
c2da171c85 Update bug_report.yaml 2025-04-14 21:03:57 +02:00
bakito-renovate[bot]
b44dd16ad8 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.23.4 (#549)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-09 04:00:36 +00:00
bakito-renovate[bot]
ba00016be8 chore(deps): update uber to v0.5.1 (#552)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-08 09:21:20 +00:00
Marc Brugger
e301f483c6 docs: add env variables to readme (#547) 2025-04-05 21:44:46 +02:00
bakito-renovate[bot]
ebf25a8ddf chore(deps): update indirect go dependencies (#546)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-05 21:39:59 +02:00
bakito-renovate[bot]
56d6644194 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.8.2 (#544)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-05 11:22:15 +01:00
bakito-renovate[bot]
51401dac81 chore(deps): update module github.com/onsi/gomega to v1.37.0 (#545)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-04 04:00:44 +00:00
bakito-renovate[bot]
18b07898ea chore(deps): update indirect go dependencies (#543)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-03-30 13:26:05 +02:00
Marc Brugger
223739f4a7 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.0.2 (#539)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-25 22:48:39 +01:00
Marc Brugger
02ff6a11f0 feat: implement stricter lint rules git golanci-lint v2 (#538)
* feat: implement stricter lint rules git golanci-lint v2

* fix lint issues

* fix lint issues
2025-03-25 21:30:22 +01:00
Marc Brugger
2bff464f65 Correct metrics indent 2025-03-23 15:24:30 +01:00
Marc Brugger
8fe0fe27ea add unraid disclaimer (#535) 2025-03-23 15:07:31 +01:00
Marc Brugger
5c00caa296 chore(deps): update dependency adguardteam/adguardhome to v0.107.59 (#527)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:35 +01:00
Marc Brugger
344337a3d5 chore(deps): update k8s.io/utils digest to 1f6e0b7 (#530)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:25 +01:00
Marc Brugger
e1287564c3 chore(deps): update indirect go dependencies (#533)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:11 +01:00
Marc Brugger
8f61de83bf chore(deps): update onsi (#528)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 09:08:24 +01:00
bakito
f3810cf241 test: add test layout 2025-03-18 20:37:28 +01:00
Marc Brugger
3d385f409e chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.8 (#526)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-18 06:38:12 +01:00
Marc Brugger
19bdd47b3c chore: update depenencies (#525) 2025-03-16 11:29:37 +01:00
Marc Brugger
32c728ccf8 fix: handle small passwords in mask function #522 (#523) 2025-03-13 19:10:37 +01:00
dependabot[bot]
327d67c9ef build(deps): bump golang.org/x/net from 0.35.0 to 0.36.0 (#519)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 07:07:32 +01:00
Marc Brugger
edd7f82351 chore(deps): update k8s to v0.32.3 (#520)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-13 07:07:21 +01:00
Marc Brugger
012d43b950 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.8.0 (#521)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-13 07:07:06 +01:00
Marc Brugger
e2298f6f1e chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.7 (#518)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-12 22:45:48 +01:00
Marc Brugger
18350f94a6 test: add metrics test (#517) 2025-03-09 17:38:08 +01:00
bakito
1a9d5df5cb test: use files instead of const for test expects 2025-03-09 17:09:30 +01:00
bakito
8ef1eb8c2a feat: print runtime info in config only mode 2025-03-09 10:53:38 +01:00
Marc Brugger
7c2018acbc test: add print config tests (#508) 2025-03-06 07:28:25 +01:00
Marc Brugger
2079f6a3eb chore(deps): update module github.com/onsi/ginkgo/v2 to v2.23.0 (#515)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-06 07:22:18 +01:00
Marc Brugger
d9419446bf chore(deps): update module golang.org/x/mod to v0.24.0 (#516)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-06 07:22:03 +01:00
Marc Brugger
c0cbccb63c chore(deps): update module github.com/prometheus/client_golang to v1.21.1 (#514)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-05 07:11:51 +01:00
Marc Brugger
5444dda08a chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.6 (#512)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-03 07:11:01 +01:00
Marc Brugger
224617aae6 test: scan released windows files with VirusTotal (#510) 2025-02-28 20:36:35 +01:00
bakito
c941c8a100 test: add virustotal scan for windows files 2025-02-27 21:17:51 +01:00
Stephan
07a25cd094 docs: Add unraid section to README (#509)
Added comment for running in Unraid
2025-02-26 07:54:43 +01:00
bakito
54d98c10fe extend print config comments #507 2025-02-25 21:34:37 +01:00
bakito
4a897c40b5 Print PRINT_CONFIG_ONLY in markdown so it can be used in github issues #507 2025-02-25 21:21:53 +01:00
bakito
9af45d3cab Print unmodified config file in PRINT_CONFIG_ONLY mode #507 2025-02-25 20:52:19 +01:00
bakito
9c60b399a8 Print environment variables in addition of the config in PRINT_CONFIG_ONLY mode #507 2025-02-25 20:26:40 +01:00
bakito
01dbf8e50a regenerate model for v0.107.57 2025-02-21 19:55:24 +01:00
Marc Brugger
58d0302c74 chore(deps): update dependency adguardteam/adguardhome to v0.107.57 (#506)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-21 07:24:16 +01:00
Marc Brugger
90ea6a13de chore(deps): update module github.com/prometheus/client_golang to v1.21.0 (#505)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-20 07:16:22 +01:00
bakito
4f80a7979f test: format code on running tests 2025-02-19 08:07:38 +01:00
Marc Brugger
3812101c25 chore(deps): update module github.com/spf13/cobra to v1.9.1 (#503)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-17 08:08:50 +01:00
Marc Brugger
8c79c8b32d test: extened faker config (#502) 2025-02-15 22:39:53 +01:00
Marc Brugger
0afc437252 chore(deps): update module github.com/santhosh-tekuri/jsonschema/v5 to v6 (#501)
* chore(deps): update module github.com/santhosh-tekuri/jsonschema/v5 to v6

* Update validate.go

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 22:13:01 +01:00
Marc Brugger
6ae53fea6e chore(deps): update module github.com/spf13/cobra to v1.9.0 (#500)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 21:33:37 +01:00
Marc Brugger
d925ccf45a feat: add yaml config file schema validation (#499) 2025-02-15 21:27:52 +01:00
Marc Brugger
24243a4b4d chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.5 (#496)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 10:45:27 +01:00
Marc Brugger
ef9ebc29b7 chore(deps): update k8s to v0.32.2 (#498)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 10:37:19 +01:00
Marc Brugger
bd4e0f2b28 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.7.0 (#494)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-12 08:11:48 +01:00
Marc Brugger
e952c9f85a chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.2 (#495)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-12 08:11:39 +01:00
Marc Brugger
df67e9a0bd chore(deps): update module golang.org/x/mod to v0.23.0 (#492)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-05 21:25:51 +01:00
bakito
7e47ed0879 regenerate model for v0.107.56 2025-02-01 11:23:19 +01:00
Marc Brugger
1952ec1527 chore(deps): update dependency adguardteam/adguardhome to v0.107.56 (#490)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-01 09:52:03 +01:00
Marc Brugger
9ca5205d6a chore(deps): update module github.com/go-resty/resty/v2 to v2.16.5 (#491)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-28 22:41:52 +01:00
Marc Brugger
8a8da9d162 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.6.1 (#489)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-23 22:56:08 +01:00
Marc Brugger
6440c71492 chore(deps): update module github.com/go-resty/resty/v2 to v2.16.4 (#488)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 22:10:49 +01:00
Marc Brugger
0b66b2debb chore(deps): update module github.com/go-resty/resty/v2 to v2.16.3 (#484)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 21:27:27 +01:00
Marc Brugger
351c142b91 chore(deps): update k8s to v0.32.1 (#486)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 21:27:13 +01:00
Marc Brugger
8ce6aed66f chore(deps): update module github.com/onsi/ginkgo/v2 to v2.22.2 (#480)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:49 +01:00
Marc Brugger
778d18d816 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.5.1 (#482)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:38 +01:00
Marc Brugger
2ea436bbbc chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.63.4 (#481)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:14 +01:00
Marc Brugger
b5a820b6f4 cancel in progress actions (#483) 2025-01-07 18:29:51 +01:00
Marc Brugger
37f5043493 chore(deps): update module github.com/onsi/gomega to v1.36.2 (#476)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-30 14:18:26 +01:00
Marc Brugger
7638fb75a3 Update .goreleaser.yml (#478) 2024-12-29 11:54:24 +01:00
bakito
fdad6d3b7b update golang.org/x/net 2024-12-28 10:58:40 +01:00
Marc Brugger
074e300974 Format code with golines (#475) 2024-12-23 08:19:45 +01:00
Marc Brugger
523e068195 run on start if api disabled and cron enabled (#474) 2024-12-23 08:16:19 +01:00
Marc Brugger
9a7c617311 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.22.1 (#471)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-23 08:15:29 +01:00
Marc Brugger
c214899b75 chore(deps): update module github.com/caarlos0/env/v11 to v11.3.1 (#472)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-23 08:15:13 +01:00
Marc Brugger
e31a8c8064 Correct webpage styling (#469) 2024-12-18 08:33:36 +01:00
bakito
31cc27df1b check for nil in stats 2024-12-18 08:15:44 +01:00
Marc Brugger
d60051b5cb remove golang.org/x/exp dependency (#467) 2024-12-17 20:43:18 +01:00
Marc Brugger
6770ae2a99 chore(deps): update module github.com/caarlos0/env/v11 to v11.3.0 (#465)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-17 06:40:43 +01:00
Marc Brugger
84738809ef chore(deps): update golang.org/x/exp digest to 4a55095 (#463)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-16 07:30:16 +01:00
Marc Brugger
43a08f7eef chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.5.0 (#464)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-16 07:30:06 +01:00
Marc Brugger
89aeec5f97 Draw also stats of single instances in dashboard diagram (#462)
* allow multiple graphs

* draw single instances
2024-12-15 12:06:09 +01:00
Marc Brugger
00ee5415a5 chore(deps): update k8s.io/utils digest to 24370be (#460)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-14 23:56:56 +01:00
Marc Brugger
b5bd79b61e Update README.md add API_METRICS_ENABLED 2024-12-14 22:28:14 +01:00
Marc Brugger
7080c236eb Show aggregated stats in web UI (#459) 2024-12-14 19:28:11 +01:00
Marc Brugger
bb073eb51e chore(deps): update dependency adguardteam/adguardhome to v0.107.55 (#455)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-12 07:18:25 +01:00
dependabot[bot]
7372d49aca build(deps): bump golang.org/x/crypto from 0.30.0 to 0.31.0 (#457)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 07:17:34 +01:00
Marc Brugger
1d771d4a0a chore(deps): update k8s to v0.32.0 (#456)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-12 06:45:35 +01:00
Marc Brugger
dcffdbd98e chore(deps): update golang.org/x/exp digest to 1829a12 (#453)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:32:02 +01:00
Marc Brugger
abcc2e4125 chore(deps): update module github.com/onsi/gomega to v1.36.1 (#452)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:31:51 +01:00
Marc Brugger
e453381382 chore(deps): update k8s to v0.31.4 (#454)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:31:39 +01:00
Marc Brugger
9a9913998a chore(deps): update golang.org/x/exp digest to 43b7b7c (#451)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-06 11:37:24 +01:00
Marc Brugger
a3cdb66fe6 chore(deps): update module github.com/onsi/gomega to v1.36.0 (#444)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-26 06:45:08 +01:00
Marc Brugger
39aa84e2fd chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.62.2 (#449)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-26 06:20:41 +01:00
Marc Brugger
1ec2270eb5 Delete .github/dependabot.yml 2024-11-25 07:10:51 +01:00
dependabot[bot]
5d1ee9b538 build(deps): bump github.com/onsi/ginkgo/v2 in the onsi group (#447)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.21.0 to 2.22.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.21.0...v2.22.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 07:06:55 +01:00
Marc Brugger
03d3cf57e9 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.8 (#438)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:52:10 +01:00
dependabot[bot]
2a5465ddb0 build(deps): bump dcarbone/install-jq-action from 2 to 3 (#441)
Bumps [dcarbone/install-jq-action](https://github.com/dcarbone/install-jq-action) from 2 to 3.
- [Release notes](https://github.com/dcarbone/install-jq-action/releases)
- [Commits](https://github.com/dcarbone/install-jq-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: dcarbone/install-jq-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 06:52:02 +01:00
Marc Brugger
84efc786d4 chore(deps): update dcarbone/install-jq-action action to v3 (#442)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:51:45 +01:00
Marc Brugger
12e5fb6f7f chore(deps): update k8s to v0.31.3 (#445)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:51:17 +01:00
dependabot[bot]
5c66900fdb build(deps): bump github.com/go-resty/resty/v2 from 2.16.0 to 2.16.2 (#448)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.16.0 to 2.16.2.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.16.0...v2.16.2)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 06:50:51 +01:00
Jeremy Diaz
2710ead089 Updated README.md (#440)
Co-authored-by: diaznet <jd@diaznet.fr>
2024-11-16 12:56:29 +01:00
Marc Brugger
839aa420e0 chore(deps): update module github.com/go-resty/resty/v2 to v2.16.0 (#436)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-11 07:04:24 +01:00
Marc Brugger
4ee42d4092 chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.62.0 (#437)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-11 07:04:13 +01:00
Marc Brugger
b2950f0718 chore(deps): update golang.org/x/exp digest to 2d47ceb (#434)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-09 10:35:54 +01:00
Marc Brugger
7b1793476d chore(deps): update dependency adguardteam/adguardhome to v0.107.54 (#432)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-08 18:54:34 +01:00
Marc Brugger
f8750ef231 chore(deps): update module golang.org/x/mod to v0.22.0 (#433)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-08 07:03:48 +01:00
Marc Brugger
cc9283530f chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.4 (#431)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-05 06:21:07 +01:00
Marc Brugger
5a61e56766 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.3 (#430)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-04 21:27:27 +01:00
Marc Brugger
8f71d514ee chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.1 (#429)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-02 21:34:53 +01:00
Marc Brugger
1b122b228f chore(deps): update module github.com/onsi/gomega to v1.35.1 (#428)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-01 08:26:02 +01:00
Marc Brugger
e88c0cdd38 Update README.md (#427) 2024-10-30 22:04:44 +01:00
Marc Brugger
3ceb188b81 chore(deps): update onsi (#425)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-30 07:37:29 +01:00
Marc Brugger
0bc52ecb3c chore(deps): update k8s to v0.31.2 (#424)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-24 07:21:34 +02:00
Marc Brugger
36d6c37df3 chore(deps): update module go.uber.org/mock to v0.5.0 (#422)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-18 09:38:01 +02:00
Marc Brugger
8fd793fdab chore(deps): update module go.uber.org/mock/mockgen to v0.5.0 (#423)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-18 09:37:52 +02:00
Marc Brugger
be1909aedb chore(deps): update module github.com/prometheus/client_golang to v1.20.5 (#421)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-16 07:29:55 +02:00
Marc Brugger
e83b4d84ec chore(deps): update golang.org/x/exp digest to f66d83c (#420)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-09 23:43:54 +02:00
Marc Brugger
b39b3f38fe print ignored error count (#419)
* print ignored error count
2024-10-05 16:28:40 +02:00
bakito
d50765925b delete Taskfile.yml 2024-10-05 11:03:04 +02:00
Marc Brugger
e0f8971155 Sync DNS settings before filters (#418) 2024-10-05 10:59:12 +02:00
Marc Brugger
f1d53f5610 chore(deps): update golang.org/x/exp digest to 225e2ab (#417)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-05 09:01:17 +02:00
Marc Brugger
4b4e6b1c72 ignore more errors (#416) 2024-10-03 22:07:01 +02:00
Marc Brugger
095af716c9 chore(deps): update dependency adguardteam/adguardhome to v0.107.53 (#415)
* chore(deps): update dependency adguardteam/adguardhome to v0.107.53

* update schema

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-03 18:45:25 +02:00
bakito
e4e9f050df track AdGuardHome with renovate 2024-10-03 18:30:28 +02:00
bakito
638b9b9428 upgrade toolbox 2024-09-28 15:07:17 +02:00
Marc Brugger
0d4d18c595 chore(deps): update module github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen to v2.4.1 (#414)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-28 15:04:13 +02:00
bakito
33350c168d go mod tidy 2024-09-28 09:05:51 +02:00
bakito
1bbb882225 cleanup renovate config 2024-09-28 08:59:41 +02:00
bakito
6e86e8e5c5 correct renovate config regex 2024-09-28 08:57:57 +02:00
bakito
7cb4c8793a update renovate config 2024-09-28 08:46:39 +02:00
Marc Brugger
4d2c636d9b Upgrade to Go 1.23 (#413)
* Update Dockerfile
* Update go.mod
* Update .goreleaser.yml
* Update AdGuardHome.yaml
2024-09-27 21:15:21 +02:00
bakito
9895d6de43 update makefile dependencies 2024-09-27 18:47:32 +02:00
Marc Brugger
f62b6f94c5 chore(deps): update module github.com/go-resty/resty/v2 to v2.15.3 (#412)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-26 23:16:19 +02:00
Marc Brugger
e00d9a6c59 #403 add feature to disable theme sync (#411) 2024-09-22 21:24:20 +02:00
Marc Brugger
d2e573bbed chore(deps): update module github.com/go-resty/resty/v2 to v2.15.2 (#409)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-22 07:36:51 +02:00
Marc Brugger
8c0b6ee70d chore(deps): update module github.com/go-resty/resty/v2 to v2.15.1 (#408)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-20 06:45:23 +02:00
Marc Brugger
b9c7b1f559 chore(deps): update module github.com/prometheus/client_golang to v1.20.4 (#407)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-18 11:28:04 +02:00
Marc Brugger
7b5b876b0c chore(deps): update module github.com/go-resty/resty/v2 to v2.15.0 (#406)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-15 13:56:06 +02:00
Marc Brugger
848fb50132 chore(deps): update module k8s.io/apimachinery to v0.31.1 (#405)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-13 06:30:04 +02:00
Marc Brugger
6206402cae chore(deps): update golang.org/x/exp digest to 701f63a (#404)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-10 07:18:49 +02:00
Marc Brugger
b24dc91ef2 chore(deps): update module golang.org/x/mod to v0.21.0 (#401)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-06 00:18:13 +02:00
Marc Brugger
d2d58f9058 chore(deps): update module github.com/prometheus/client_golang to v1.20.3 (#402)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-06 00:18:01 +02:00
Marc Brugger
e6fb75f715 chore(deps): update golang.org/x/exp digest to e7e105d (#400)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-05 07:37:23 +02:00
bakito
3b31049636 correct cron description and add video section to Readme #398 2024-08-29 19:30:38 +02:00
Marc Brugger
5db091d800 chore(deps): update onsi (#399)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-29 06:47:25 +02:00
Marc Brugger
8278ecb6a2 chore(deps): update module github.com/prometheus/client_golang to v1.20.2 (#397)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-23 21:51:21 +02:00
Marc Brugger
084eeba24a chore(deps): update golang.org/x/exp digest to 9b4947d (#396)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-23 07:00:03 +02:00
Marc Brugger
0134840300 chore(deps): update golang.org/x/exp digest to 778ce7b (#395)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-22 20:23:40 +02:00
Marc Brugger
05335aaf3f chore(deps): update module github.com/onsi/ginkgo/v2 to v2.20.1 (#394)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-22 07:09:14 +02:00
Marc Brugger
fb38741036 chore(deps): update module github.com/prometheus/client_golang to v1.20.1 (#393)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-21 07:07:47 +02:00
Marc Brugger
5a07feaffd chore(deps): update module github.com/prometheus/client_golang to v1.20.0 (#392)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-14 18:26:21 +02:00
Marc Brugger
e555c287bf chore(deps): update module k8s.io/apimachinery to v0.31.0 (#390)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-13 19:06:53 +02:00
Marc Brugger
ad52f32a3b chore(deps): update module github.com/caarlos0/env/v11 to v11.2.2 (#380)
* chore(deps): update module github.com/caarlos0/env/v11 to v11.2.2

* fix replica handling

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-09 10:10:32 +02:00
bakito
665b27cfbe cleanup dependencies 2024-08-09 09:24:23 +02:00
Marc Brugger
871d558ffd chore(deps): update golang.org/x/exp digest to 0cdaa3a (#388)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-09 08:46:05 +02:00
Marc Brugger
881e00e7e3 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.20.0 (#386)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-08 08:02:08 +02:00
Marc Brugger
29963b9d75 chore(deps): update module golang.org/x/mod to v0.20.0 (#385)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-05 23:20:26 +02:00
Marc Brugger
bea8ec2d98 chore(deps): update module github.com/go-resty/resty/v2 to v2.14.0 (#384)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-05 23:20:01 +02:00
Marc Brugger
851f5fad58 chore(deps): update module github.com/onsi/gomega to v1.34.1 (#379)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-30 07:59:58 +02:00
Marc Brugger
1125837420 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.19.1 (#378)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-27 08:09:13 +02:00
Marc Brugger
34f9a13da3 chore(deps): update module github.com/onsi/gomega to v1.34.0 (#376)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-25 20:14:34 +02:00
Marc Brugger
1432ab4b52 Build more archs (#375)
* Build more archs
2024-07-24 08:59:36 +02:00
Marc Brugger
8f3a8e3572 Build image for linux/arm/v8 #372 (#374) 2024-07-23 20:25:55 +02:00
Marc Brugger
87d5903cfc extend bug issue template with os info (#373) 2024-07-23 06:38:36 +02:00
Marc Brugger
a50639af23 chore(deps): update module github.com/caarlos0/env/v10 to v11 (#371)
* chore(deps): update module github.com/caarlos0/env/v10 to v11

* update code

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-22 00:02:14 +02:00
Marc Brugger
c4b1a70dac chore(deps): update module k8s.io/apimachinery to v0.30.3 (#369)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:27:02 +02:00
Marc Brugger
94677c7a39 chore(deps): update golang.org/x/exp digest to 8a7402a (#368)
* chore(deps): update golang.org/x/exp digest to 8a7402a

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:22:31 +02:00
Marc Brugger
cd2295ef05 Add renovate.json (#367)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:14:45 +02:00
bakito
944bb42074 update schema base version 2024-07-08 21:30:10 +02:00
bakito
1350364804 allow e2e test bin files in .gitognore 2024-07-08 21:25:34 +02:00
Marc Brugger
3625395d32 print e2e logs (#365)
* keep origin pre and post logs

* print origin pre and post logs
2024-07-08 21:23:08 +02:00
dependabot[bot]
c32ed90a2e Bump golang.org/x/mod from 0.18.0 to 0.19.0 (#363)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/mod/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 21:05:43 +02:00
Marc Brugger
9af8c00bca fix e2e test for newes agh version (#364) 2024-07-08 20:58:34 +02:00
bakito
0af476ac8e update tools 2024-07-03 07:14:23 +02:00
dependabot[bot]
3b58e8ce3c Bump docker/build-push-action from 5 to 6 (#362)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 07:01:37 +02:00
Wei Feng
1fffb80066 docs: add "Run as Linux Service via Systemd" to README (#359)
* docs: add "Run as Linux Service via Systemd" to README

* fix: update config file anchor

* chore: add a section on testing details

* fix: web ui url
2024-06-17 07:10:07 +02:00
dependabot[bot]
5f7e6e69e3 Bump github.com/spf13/cobra from 1.8.0 to 1.8.1 (#358)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 07:02:30 +02:00
dependabot[bot]
14e84b23d1 Bump k8s.io/apimachinery from 0.30.1 to 0.30.2 in the k8s group (#357)
Bumps the k8s group with 1 update: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery).


Updates `k8s.io/apimachinery` from 0.30.1 to 0.30.2
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.30.1...v0.30.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 07:02:23 +02:00
bakito
562576b8d4 correct docker compose example #356 2024-06-13 07:38:06 +02:00
bakito
532962527f correct docker compose example #356 2024-06-13 07:16:28 +02:00
Marc Brugger
99d1914542 correct deprecated lint flag 2024-06-10 07:55:47 +02:00
dependabot[bot]
6fa534dac1 Replace goreleaser action with make
* Use make

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2024-06-10 06:50:33 +02:00
dependabot[bot]
3b6da33535 Bump golang.org/x/mod from 0.17.0 to 0.18.0 (#353)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/mod/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 06:34:27 +02:00
dependabot[bot]
ded4a22a67 Bump github.com/onsi/ginkgo/v2 from 2.17.3 to 2.19.0 in the onsi group (#350)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.3 to 2.19.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.3...v2.19.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 07:06:05 +02:00
bakito
ab0589916f only return 200 for clear log requests 2024-05-25 08:21:49 +02:00
Marc Brugger
3226690d70 add clear log button (#349)
* add clear log button

* use button group for instances
2024-05-25 08:12:07 +02:00
bakito
4d235491f2 update tools 2024-05-25 08:11:24 +02:00
dependabot[bot]
96b7890404 Bump k8s.io/apimachinery from 0.30.0 to 0.30.1 in the k8s group (#347)
Bumps the k8s group with 1 update: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery).


Updates `k8s.io/apimachinery` from 0.30.0 to 0.30.1
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.30.0...v0.30.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 07:48:20 +02:00
dependabot[bot]
760cc01217 Bump github.com/gin-gonic/gin from 1.9.1 to 1.10.0 (#344)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.1...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 07:14:10 +02:00
dependabot[bot]
c394a05fc1 Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1 (#343)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:28 +02:00
dependabot[bot]
11ab4483e0 Bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.17.3 in the onsi group (#342)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.2 to 2.17.3
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.17.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:11 +02:00
dependabot[bot]
65747a7f28 Bump github.com/go-resty/resty/v2 from 2.12.0 to 2.13.1 (#345)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.12.0 to 2.13.1.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.12.0...v2.13.1)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:03 +02:00
dependabot[bot]
ca9ddc4222 Bump golangci/golangci-lint-action from 5 to 6 (#346)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:57:22 +02:00
dependabot[bot]
6a2237b513 Bump github.com/onsi/gomega from 1.33.0 to 1.33.1 in the onsi group (#341)
Bumps the onsi group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/gomega` from 1.33.0 to 1.33.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.0...v1.33.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 06:42:31 +02:00
Marc Brugger
4afccfad8d handle empty client blockes service schedule in equals check (#339) 2024-05-03 20:44:50 +02:00
Marc Brugger
9a32a1d638 Update README.md 2024-05-03 01:14:13 +02:00
bakito
e091914a06 fix deepcomygen 2024-05-02 23:11:50 +02:00
bakito
7b584668ac skip sanitize 2024-05-02 23:11:28 +02:00
dependabot[bot]
30019327da Bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 in the onsi group (#335)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.1 to 2.17.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.17.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 07:04:28 +02:00
dependabot[bot]
fc8eeeaf0a Bump golangci/golangci-lint-action from 4 to 5 (#336)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 07:03:34 +02:00
bakito
11201881c0 remove tools.go 2024-04-22 18:15:58 +02:00
dependabot[bot]
498dcb8bcb Bump github.com/onsi/gomega from 1.32.0 to 1.33.0 in the onsi group (#334)
Bumps the onsi group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/gomega` from 1.32.0 to 1.33.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 07:59:35 +02:00
dependabot[bot]
5564239bea Bump golang.org/x/net from 0.22.0 to 0.23.0 (#332)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 16:51:27 +02:00
bakito
5319c5f53f remove goreleaser from tools 2024-04-08 18:26:35 +02:00
dependabot[bot]
49b9e1ce1f Bump golang.org/x/mod from 0.16.0 to 0.17.0 (#331)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/mod/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 07:48:00 +02:00
Marc Brugger
82a61aef09 support api tls mode (#329)
Add support api tls mode
2024-04-06 11:46:12 +02:00
Marc Brugger
3c58a8f091 Replace deprecated API endpoints (#326)
* extend query log config
* replace deprecated services
* replace more deprecated services
* implement equals for stats config
2024-04-02 20:31:47 +02:00
dependabot[bot]
4a62b80e75 Bump github.com/golangci/golangci-lint from 1.57.1 to 1.57.2 (#325)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.57.1 to 1.57.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.57.1...v1.57.2)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 06:46:21 +02:00
dependabot[bot]
52c1687d83 Bump the onsi group with 2 updates (#322)
Bumps the onsi group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.16.0 to 2.17.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.16.0...v2.17.1)

Updates `github.com/onsi/gomega` from 1.31.1 to 1.32.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.1...v1.32.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 07:03:08 +01:00
dependabot[bot]
238a0f46e0 Bump github.com/golangci/golangci-lint from 1.56.2 to 1.57.1 (#323)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.56.2 to 1.57.1.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.56.2...v1.57.1)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 07:02:59 +01:00
bakito
bbf5d2d178 skip upx for darwin 2024-03-24 18:58:19 +01:00
Marc Brugger
1c4ea24da1 Sanitize dns config (#321)
Sanitize dns config for misconfigurations
2024-03-24 18:51:07 +01:00
dependabot[bot]
f277460b9d Bump github.com/docker/docker (#318)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 21:57:44 +01:00
dependabot[bot]
15c1643d34 Bump the k8s group with 2 updates (#316)
Bumps the k8s group with 2 updates: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) and [k8s.io/code-generator](https://github.com/kubernetes/code-generator).


Updates `k8s.io/apimachinery` from 0.29.2 to 0.29.3
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.29.2...v0.29.3)

Updates `k8s.io/code-generator` from 0.29.2 to 0.29.3
- [Commits](https://github.com/kubernetes/code-generator/compare/v0.29.2...v0.29.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
- dependency-name: k8s.io/code-generator
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 07:10:10 +01:00
dependabot[bot]
e7f0a05e1a Bump github.com/go-resty/resty/v2 from 2.9.1 to 2.12.0 (#317)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.9.1 to 2.12.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.9.1...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 07:09:59 +01:00
dependabot[bot]
8506b5d5a5 Bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#315)
Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 06:59:50 +01:00
dependabot[bot]
0d629127a9 Bump github.com/prometheus/client_golang from 1.17.0 to 1.19.0 (#314)
* Bump github.com/prometheus/client_golang from 1.17.0 to 1.19.0

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.19.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* go-jose.v2 v2.6.3

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2024-03-12 20:03:12 +01:00
124 changed files with 5076 additions and 4153 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
dist
bin

View File

@@ -17,7 +17,9 @@ body:
id: adguardhome-sync-version
attributes:
label: AdguardHome-Sync Version
description: What version of adguardhome-sync was running when you discovered this issue?
description: |
- What version of adguardhome-sync was running when you discovered this issue?
- Are you running the docker or binary version?
validations:
required: true
- type: textarea
@@ -27,6 +29,15 @@ body:
description: What version of adguardhome was running when you discovered this issue?
validations:
required: true
- type: textarea
id: os-information
attributes:
label: OS Information
description: |
- What Operating System are you running? `cat /etc/os-release`
- What is the architecture of your CPU? `uname -m`
validations:
required: true
- type: textarea
id: config
attributes:
@@ -47,7 +58,7 @@ body:
- type: textarea
id: logs
attributes:
label: Relevant log output
label: Relevant DEBUG log output
description: |
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.

View File

@@ -1,28 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
groups:
k8s:
patterns:
- "k8s.io/*"
update-types:
- "minor"
- "patch"
onsi:
patterns:
- "github.com/onsi/*"
update-types:
- "minor"
- "patch"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -20,6 +20,10 @@ on:
schedule:
- cron: '32 19 * * 6'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
@@ -38,11 +42,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"

View File

@@ -1,13 +1,20 @@
name: docker-image
name: docker-images
on:
workflow_dispatch: # allows manual triggering
schedule:
- cron: '0 0 * * *'
#pull_request:
# branches:
# - main
release:
types:
- published
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
images:
runs-on: ubuntu-latest
@@ -38,22 +45,22 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Modify Dockerfile
run: |
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
- name: Build and push ${{github.event.release.tag_name }}
- name: Build images ${{github.event.release.tag_name }}
id: docker_build_release
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name != '' }}
with:
context: .
pull: true
push: true
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
provenance: false
build-args: |
VERSION=${{ github.event.release.tag_name }}
@@ -63,16 +70,16 @@ jobs:
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
if: ${{ github.event.release.tag_name == '' }}
- name: Build and push main
- name: Build images
id: docker_build_main
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
with:
context: .
pull: true
push: true
push: ${{ github.ref == 'refs/heads/main' }}
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
provenance: false
build-args: |
VERSION=main
@@ -80,4 +87,3 @@ jobs:
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@@ -8,6 +8,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
e2e:
runs-on: ubuntu-latest
@@ -15,13 +19,15 @@ jobs:
matrix:
build:
- mode: env
protocol: https
- mode: file
protocol: http
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup jq
uses: dcarbone/install-jq-action@v2
uses: dcarbone/install-jq-action@v3
- name: Install kind with registry
uses: bakito/kind-with-registry-action@main
@@ -31,15 +37,19 @@ jobs:
- name: Install Helm Chart
run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }}
- name: Wait for pod to start
run: ./testdata/e2e/bin/wait-for-start.sh ${{ matrix.build.protocol }}
- name: Show origin pre Logs
run: ./testdata/e2e/bin/show-origin-logs.sh pre
- name: Wait for sync to finish
run: ./testdata/e2e/bin/wait-for-sync.sh
- name: Show origin Logs
run: ./testdata/e2e/bin/show-origin-logs.sh
run: ./testdata/e2e/bin/wait-for-sync.sh ${{ matrix.build.protocol }}
- name: Show origin post Logs
run: ./testdata/e2e/bin/show-origin-logs.sh post
- name: Show Replica Logs
run: ./testdata/e2e/bin/show-replica-logs.sh
- name: Show Sync Logs
run: ./testdata/e2e/bin/show-sync-logs.sh
- name: Show Sync Metrics
run: ./testdata/e2e/bin/show-sync-metrics.sh
run: ./testdata/e2e/bin/show-sync-metrics.sh ${{ matrix.build.protocol }}
- name: Read latest replica config
run: ./testdata/e2e/bin/read-latest-replica-config.sh

View File

@@ -6,41 +6,63 @@ on:
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
- name: Cache toolbox binaries
uses: actions/cache@v4
with:
skip-pkg-cache: true
path: ${{ github.workspace }}/bin
key: ${{ runner.os }}-${{ github.job }}-tools-${{ hashFiles('.toolbox.mk') }}
- name: Lint
run: make lint
test:
name: test
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
- name: Cache toolbox binaries
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/bin
key: ${{ runner.os }}-${{ github.job }}-tools-${{ hashFiles('.toolbox.mk') }}
- name: Model
run: make model
- name: Test
run: make test-ci
- name: Send coverage
if: runner.os == 'Linux'
uses: shogo82148/actions-goveralls@v1
continue-on-error: true
with:
path-to-profile: coverage.out
@@ -50,15 +72,18 @@ jobs:
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
- name: Cache toolbox binaries
uses: actions/cache@v4
with:
version: latest
args: --skip-publish --snapshot --rm-dist
path: ${{ github.workspace }}/bin
key: ${{ runner.os }}-${{ github.job }}-tools-${{ hashFiles('.toolbox.mk') }}
- name: Run GoReleaser
run: make test-release

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'

16
.github/workflows/virustotal.yaml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Scan GitHub Release with VirusTotal
on:
release:
types: [released]
jobs:
scan_release:
runs-on: ubuntu-latest
steps:
- name: Analyze Build Assets
uses: bakito/virustotal-action@main
with:
release_name: ${{github.event.release.tag_name}}
vt_api_key: ${{secrets.VT_API_KEY}}

2
.gitignore vendored
View File

@@ -9,7 +9,7 @@ main
.adguardhome-sync.yaml
tmp
bin
!testdata/e2e/bin
config*.yaml
*.log
wiki
Taskfile.yml

215
.golangci.yaml Normal file
View File

@@ -0,0 +1,215 @@
version: '2'
linters:
enable:
- asciicheck
- bidichk
- bodyclose
- canonicalheader
- containedctx
- copyloopvar
- decorder
- dogsled
- dupword
- durationcheck
- err113
- errname
- errorlint
- exptostd
- fatcontext
- forcetypeassert
- gocheckcompilerdirectives
- gochecksumtype
- gocritic
- godot
- gomodguard
- goprintffuncname
- gosmopolitan
- grouper
- iface
- importas
- inamedparam
- interfacebloat
- intrange
- loggercheck
- makezero
- mirror
- misspell
- nilerr
- nilnesserr
- noctx
- nolintlint
- nosprintfhostport
- perfsprint
- predeclared
- promlinter
- protogetter
- reassign
- revive
- rowserrcheck
- sloglint
- spancheck
- sqlclosecheck
- staticcheck
- tagalign
- testableexamples
- testifylint
- thelper
- unconvert
- unparam
- usestdlibvars
- usetesting
- wastedassign
- whitespace
- zerologlint
disable:
- asasalint
- contextcheck
- cyclop
- depguard
- dupl
- errchkjson
- exhaustive
- exhaustruct
- forbidigo
- funlen
- ginkgolinter
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocyclo
- godox
- goheader
- gomoddirectives
- gosec
- ireturn
- lll
- maintidx
- musttag
- nakedret
- nestif
- nilnil
- nlreturn
- nonamedreturns
- paralleltest
- prealloc
- recvcheck
- tagliatelle
- testpackage
- tparallel
- varnamelen
- wrapcheck
- wsl
settings:
gocritic:
enable-all: true
disabled-checks:
- emptyFallthrough
- hugeParam
- rangeValCopy
- unnamedResult
- whyNoLint
govet:
disable:
- fieldalignment
- shadow
enable-all: true
misspell:
locale: US
revive:
enable-all-rules: true
rules:
- name: add-constant
disabled: true
- name: cognitive-complexity
disabled: true
- name: cyclomatic
disabled: true
- name: deep-exit
disabled: true
- name: dot-imports
severity: warning
disabled: false
exclude: [""]
arguments:
- allowedPackages: ["github.com/onsi/ginkgo/v2", "github.com/onsi/gomega"]
- name: empty-block
disabled: true
- name: exported
disabled: true
- name: filename-format
arguments:
- ^[a-z][-0-9_a-z]*(?:\.gen)?\.go$
- name: flag-parameter
disabled: true
- name: function-length
disabled: true
- name: function-result-limit
disabled: true
- name: import-shadowing
disabled: true
- name: line-length-limit
disabled: true
- name: max-control-nesting
disabled: true
- name: max-public-structs
disabled: true
- name: nested-structs
disabled: true
- name: package-comments
disabled: true
- name: unused-parameter
disabled: true
- name: unused-receiver
disabled: true
- name: var-naming
disabled: true
- name: enforce-switch-style
disabled: true
- name: blank-imports
disabled: true
staticcheck:
checks:
- 'all'
- '-ST1000'
exclusions:
generated: lax
presets:
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- err113
text: do not define dynamic errors, use wrapped static errors instead
- linters:
- forbidigo
path: ^internal/cmds/
- linters:
- forcetypeassert
path: _test\.go$
- linters:
- forbidigo
path: assets/scripts/generate-commit.go
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
- golines
settings:
gci:
sections:
- standard
- default
- prefix(github.com/bakito/adguardhome-sync)
gofumpt:
module-path: github.com/bakito/adguardhome-sync
extra-rules: true
goimports:
local-prefixes:
- github.com/bakito/adguardhome-sync
golines:
max-len: 128
tab-len: 4

View File

@@ -1,38 +0,0 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- bodyclose
- dogsled
- durationcheck
- errcheck
- errorlint
- gci
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- staticcheck
- unconvert
- unparam
- unused
linters-settings:
gosec:
# Exclude generated files
exclude-generated: true
excludes:
- G601 # not applicable in go 1.22 anymore
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true

View File

@@ -1,3 +1,4 @@
version: 2
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
builds:
@@ -9,11 +10,13 @@ builds:
- linux
- windows
- darwin
- openbsd
goarch:
- 386
- amd64
- arm
- arm64
- riscv64
goarm:
- 5
- 6
@@ -23,23 +26,34 @@ builds:
goarch: arm
- goos: darwin
goarch: arm64
- goos: darwin
goarch: riscv64
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
- goos: windows
goarch: riscv64
- goos: openbsd
goarch: 386
- goos: openbsd
goarch: arm
hooks:
post:
# don't upx windows binaries as they make trouble with virus scanners
- bash -c 'if [[ "{{ .Path }}" != *.exe ]]; then upx {{ .Path }}; fi'
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]] && [[ "{{ .Path }}" != *openbsd* ]] && [[ "{{ .Path }}" != *riscv* ]]; then upx {{ .Path }}; fi'
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^chore'
release:
prerelease: auto
sboms:
- artifacts: archive

102
.toolbox.mk Normal file
View File

@@ -0,0 +1,102 @@
## toolbox - start
## Generated with https://github.com/bakito/toolbox
## Current working directory
TB_LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
## Location to install dependencies to
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
$(TB_LOCALBIN):
if [ ! -e $(TB_LOCALBIN) ]; then mkdir -p $(TB_LOCALBIN); fi
# Helper functions
STRIP_V = $(patsubst v%,%,$(1))
## Tool Binaries
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
TB_SEMVER ?= $(TB_LOCALBIN)/semver
TB_SYFT ?= $(TB_LOCALBIN)/syft
## Tool Versions
# renovate: packageName=github.com/kubernetes-sigs/controller-tools
TB_CONTROLLER_GEN_VERSION ?= v0.19.0
# renovate: packageName=github.com/golangci/golangci-lint/v2
TB_GOLANGCI_LINT_VERSION ?= v2.6.2
TB_GOLANGCI_LINT_VERSION_NUM ?= $(call STRIP_V,$(TB_GOLANGCI_LINT_VERSION))
# renovate: packageName=github.com/goreleaser/goreleaser/v2
TB_GORELEASER_VERSION ?= v2.13.0
TB_GORELEASER_VERSION_NUM ?= $(call STRIP_V,$(TB_GORELEASER_VERSION))
# renovate: packageName=github.com/uber-go/mock
TB_MOCKGEN_VERSION ?= v0.6.0
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2
TB_OAPI_CODEGEN_VERSION ?= v2.5.1
# renovate: packageName=github.com/bakito/semver
TB_SEMVER_VERSION ?= v1.1.7
TB_SEMVER_VERSION_NUM ?= $(call STRIP_V,$(TB_SEMVER_VERSION))
# renovate: packageName=github.com/anchore/syft/cmd/syft
TB_SYFT_VERSION ?= v1.38.0
TB_SYFT_VERSION_NUM ?= $(call STRIP_V,$(TB_SYFT_VERSION))
## Tool Installer
.PHONY: tb.controller-gen
tb.controller-gen: ## Download controller-gen locally if necessary.
@test -s $(TB_CONTROLLER_GEN) || \
GOBIN=$(TB_LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(TB_CONTROLLER_GEN_VERSION)
.PHONY: tb.ginkgo
tb.ginkgo: ## Download ginkgo locally if necessary.
@test -s $(TB_GINKGO) || \
GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: tb.golangci-lint
tb.golangci-lint: ## Download golangci-lint locally if necessary.
@test -s $(TB_GOLANGCI_LINT) && $(TB_GOLANGCI_LINT) --version | grep -q $(TB_GOLANGCI_LINT_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
.PHONY: tb.goreleaser
tb.goreleaser: ## Download goreleaser locally if necessary.
@test -s $(TB_GORELEASER) && $(TB_GORELEASER) --version | grep -q $(TB_GORELEASER_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
.PHONY: tb.mockgen
tb.mockgen: ## Download mockgen locally if necessary.
@test -s $(TB_MOCKGEN) || \
GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
.PHONY: tb.oapi-codegen
tb.oapi-codegen: ## Download oapi-codegen locally if necessary.
@test -s $(TB_OAPI_CODEGEN) || \
GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
.PHONY: tb.semver
tb.semver: ## Download semver locally if necessary.
@test -s $(TB_SEMVER) && $(TB_SEMVER) -version | grep -q $(TB_SEMVER_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
.PHONY: tb.syft
tb.syft: ## Download syft locally if necessary.
@test -s $(TB_SYFT) && $(TB_SYFT) --version | grep -q $(TB_SYFT_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/anchore/syft/cmd/syft@$(TB_SYFT_VERSION)
## Reset Tools
.PHONY: tb.reset
tb.reset:
@rm -f \
$(TB_CONTROLLER_GEN) \
$(TB_GINKGO) \
$(TB_GOLANGCI_LINT) \
$(TB_GORELEASER) \
$(TB_MOCKGEN) \
$(TB_OAPI_CODEGEN) \
$(TB_SEMVER) \
$(TB_SYFT)
## Update Tools
.PHONY: tb.update
tb.update: tb.reset
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
sigs.k8s.io/controller-tools/cmd/controller-gen@github.com/kubernetes-sigs/controller-tools \
github.com/golangci/golangci-lint/v2/cmd/golangci-lint?--version \
github.com/goreleaser/goreleaser/v2?--version \
go.uber.org/mock/mockgen@github.com/uber-go/mock \
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
github.com/bakito/semver?-version \
github.com/anchore/syft/cmd/syft?--version
## toolbox - end

View File

@@ -1,10 +1,8 @@
FROM golang:1.22-bullseye as builder
FROM golang:1.25-alpine AS builder
WORKDIR /go/src/app
RUN apt-get update && \
apt-get install -y upx ca-certificates tzdata && \
apt-get upgrade -y # upgrade to get latest ca-certs
RUN apk update && apk add upx ca-certificates tzdata
ARG VERSION=main
ARG BUILD="N/A"
@@ -15,8 +13,10 @@ ENV GO111MODULE=on \
COPY . /go/src/app/
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync . \
&& upx -q adguardhome-sync
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync .
RUN go version && upx -q adguardhome-sync
# application image
FROM scratch

118
Makefile
View File

@@ -1,99 +1,50 @@
# Include toolbox tasks
include ./.toolbox.mk
# Run go lint against code
lint: golangci-lint
$(GOLANGCI_LINT) run --fix
lint: tb.golangci-lint
$(TB_GOLANGCI_LINT) run --fix
# Run go mod tidy
tidy:
go mod tidy
generate: deepcopy-gen
generate: model mocks deepcopy-gen
deepcopy-gen: tb.controller-gen
@mkdir -p ./tmp
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
$(DEEPCOPY_GEN) -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types
$(TB_CONTROLLER_GEN) paths=./internal/types object
.PHONY: docs
docs:
go run cmd/docs/main.go
# Run tests
test: generate lint test-ci
fuzz:
go test -fuzz=FuzzMask -v ./internal/types/ -fuzztime=60s
# Run ci tests
test-ci: mocks tidy ginkgo
$(GINKGO) --cover --coverprofile coverage.out.tmp ./...
test-ci: mocks tidy tb.ginkgo
$(TB_GINKGO) --cover --coverprofile coverage.out.tmp ./...
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
go tool cover -func=coverage.out
mocks: mockgen
$(MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
$(MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
mocks: tb.mockgen
$(TB_MOCKGEN) -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
$(TB_MOCKGEN) -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
release: semver goreleaser
@version=$$($(LOCALBIN)/semver); \
release: tb.semver tb.goreleaser tb.syft
@version=$$($(TB_SEMVER)); \
git tag -s $$version -m"Release $$version"
$(GORELEASER) --clean
PATH=$(TB_LOCALBIN):$${PATH} $(TB_GORELEASER) --clean --parallelism 2
test-release: goreleaser
$(GORELEASER) --skip=publish --snapshot --clean
## toolbox - start
## Current working directory
LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
## Location to install dependencies to
LOCALBIN ?= $(LOCALDIR)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
GINKGO ?= $(LOCALBIN)/ginkgo
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
GORELEASER ?= $(LOCALBIN)/goreleaser
MOCKGEN ?= $(LOCALBIN)/mockgen
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
SEMVER ?= $(LOCALBIN)/semver
## Tool Installer
.PHONY: deepcopy-gen
deepcopy-gen: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
$(DEEPCOPY_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen
.PHONY: ginkgo
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
$(GINKGO): $(LOCALBIN)
test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint
.PHONY: goreleaser
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
$(GORELEASER): $(LOCALBIN)
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser
.PHONY: mockgen
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
$(MOCKGEN): $(LOCALBIN)
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install go.uber.org/mock/mockgen
.PHONY: oapi-codegen
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
$(OAPI_CODEGEN): $(LOCALBIN)
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen
.PHONY: semver
semver: $(SEMVER) ## Download semver locally if necessary.
$(SEMVER): $(LOCALBIN)
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver
## Update Tools
.PHONY: update-toolbox-tools
update-toolbox-tools:
@rm -f \
$(LOCALBIN)/deepcopy-gen \
$(LOCALBIN)/ginkgo \
$(LOCALBIN)/golangci-lint \
$(LOCALBIN)/goreleaser \
$(LOCALBIN)/mockgen \
$(LOCALBIN)/oapi-codegen \
$(LOCALBIN)/semver
toolbox makefile -f $(LOCALDIR)/Makefile
## toolbox - end
test-release: tb.goreleaser tb.syft
PATH=$(TB_LOCALBIN):$${PATH} $(TB_GORELEASER) --skip=publish --snapshot --clean --parallelism 2
start-replica:
docker rm -f adguardhome-replica
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
@@ -101,6 +52,7 @@ copy-replica-config:
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
start-replica2:
docker rm -f adguardhome-replica2
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
@@ -122,12 +74,18 @@ kind-create:
kind-test:
@./testdata/e2e/bin/install-chart.sh
model: oapi-codegen
# renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.69
model: tb.oapi-codegen
@mkdir -p tmp
go run openapi/main.go v0.107.44
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
$(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > internal/client/model/model_generated.go
model-diff:
go run openapi/main.go v0.107.44
go run openapi/main.go
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
go run cmd/openapi/main.go
diff tmp/schema.yaml tmp/schema-master.yaml
zellij:
zellij -l ./testdata/test-layout.kdl

335
README.md
View File

@@ -3,7 +3,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/bakito/adguardhome-sync)](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
[![Coverage Status](https://coveralls.io/repos/github/bakito/adguardhome-sync/badge.svg?branch=main&service=github)](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
# AdGuardHome sync
# <img src="./media/adguardhome-sync.svg" alt="AdGuardHome sync" width="50"/> AdGuardHome sync
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
@@ -22,6 +22,7 @@ and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations)
- Clients
- DNS Config
- DHCP Config
- Theme
By default, all features are enabled. Single features can be disabled in the config.
@@ -42,7 +43,144 @@ go install github.com/bakito/adguardhome-sync@latest
## Prerequisites
Both the origin instance must be initially setup via the AdguardHome installation wizard.
Both the origin instance and replica(s) must be initially set up with AdguardHome via the AdguardHome installation
wizard.
## Config via environment variables
For Replicas replace `#` with the index number for the replica. E.g.: `REPLICA#_URL` -> `REPLICA1_URL`
<!-- env-doc-start -->
| Name | Type | Description |
| :--- | ---- |:----------- |
| CRON (string) | string | Cron expression for the sync interval |
| RUN_ON_START (bool) | bool | Run the sync on startup |
| PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
| CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
| ORIGIN_URL (string) | string | URL of adguardhome instance |
| ORIGIN_WEB_URL (string) | string | Web URL of adguardhome instance |
| ORIGIN_API_PATH (string) | string | API Path |
| ORIGIN_USERNAME (string) | string | Adguardhome username |
| ORIGIN_PASSWORD (string) | string | Adguardhome password |
| ORIGIN_COOKIE (string) | string | Adguardhome cookie |
| ORIGIN_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
| ORIGIN_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
| ORIGIN_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
| ORIGIN_INTERFACE_NAME (string) | string | Network interface name |
| ORIGIN_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
| REPLICA#_URL (string) | string | URL of adguardhome instance |
| REPLICA#_WEB_URL (string) | string | Web URL of adguardhome instance |
| REPLICA#_API_PATH (string) | string | API Path |
| REPLICA#_USERNAME (string) | string | Adguardhome username |
| REPLICA#_PASSWORD (string) | string | Adguardhome password |
| REPLICA#_COOKIE (string) | string | Adguardhome cookie |
| REPLICA#_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
| REPLICA#_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
| REPLICA#_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
| REPLICA#_INTERFACE_NAME (string) | string | Network interface name |
| REPLICA#_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
| API_PORT (int) | int | API port (API is disabled if port is set to 0) |
| API_USERNAME (string) | string | API username |
| API_PASSWORD (string) | string | API password |
| API_DARK_MODE (bool) | bool | API dark mode |
| API_METRICS_ENABLED (bool) | bool | Enable metrics |
| API_METRICS_SCRAPE_INTERVAL (int64) | int64 | Interval for metrics scraping |
| API_METRICS_QUERY_LOG_LIMIT (int) | int | Metrics log query limit |
| API_TLS_CERT_DIR (string) | string | API TLS certificate directory |
| API_TLS_CERT_NAME (string) | string | API TLS certificate file name |
| API_TLS_KEY_NAME (string) | string | API TLS key file name |
| FEATURES_DNS_ACCESS_LISTS (bool) | bool | Sync DNS access lists |
| FEATURES_DNS_SERVER_CONFIG (bool) | bool | Sync DNS server config |
| FEATURES_DNS_REWRITES (bool) | bool | Sync DNS rewrites |
| FEATURES_DHCP_SERVER_CONFIG (bool) | bool | Sync DHCP server config |
| FEATURES_DHCP_STATIC_LEASES (bool) | bool | Sync DHCP static leases |
| FEATURES_GENERAL_SETTINGS (bool) | bool | Sync general settings |
| FEATURES_QUERY_LOG_CONFIG (bool) | bool | Sync query log config |
| FEATURES_STATS_CONFIG (bool) | bool | Sync stats config |
| FEATURES_CLIENT_SETTINGS (bool) | bool | Sync client settings |
| FEATURES_SERVICES (bool) | bool | Sync services |
| FEATURES_FILTERS (bool) | bool | Sync filters |
| FEATURES_THEME (bool) | bool | Sync the web UI theme |
| FEATURES_TLS_CONFIG (bool) | bool | Sync the TLS config |
<!-- env-doc-end -->
### YAML Configuration file
location: $HOME/.adguardhome-sync.yaml
<!-- yaml-doc-start -->
```yaml
cron: # (string) Cron expression for the sync interval
runOnStart: # (bool) Run the sync on startup
printConfigOnly: # (bool) Print current config only and stop the application
continueOnError: # (bool) Continue sync on errors
origin: # (struct) Origin instance
url: # (string) URL of adguardhome instance
webURL: # (string) Web URL of adguardhome instance
apiPath: # (string) API Path
username: # (string) Adguardhome username
password: # (string) Adguardhome password
cookie: # (string) Adguardhome cookie
requestHeaders: # (map) Request Headers 'key1:value1,key2:value2'
insecureSkipVerify: # (bool) Skip TLS verification
autoSetup: # (bool) Automatically setup the instance if it is not initialized
interfaceName: # (string) Network interface name
dhcpServerEnabled: # (bool) Enable DHCP server
replica: # (struct) Single or replica instance (don't use in combination with replicas')
url: # (string) URL of adguardhome instance
webURL: # (string) Web URL of adguardhome instance
apiPath: # (string) API Path
username: # (string) Adguardhome username
password: # (string) Adguardhome password
cookie: # (string) Adguardhome cookie
requestHeaders: # (map) Request Headers 'key1:value1,key2:value2'
insecureSkipVerify: # (bool) Skip TLS verification
autoSetup: # (bool) Automatically setup the instance if it is not initialized
interfaceName: # (string) Network interface name
dhcpServerEnabled: # (bool) Enable DHCP server
replicas: # (struct) List or replica instances (don't use in combination with replicas')
- url: # (string) URL of adguardhome instance
webURL: # (string) Web URL of adguardhome instance
apiPath: # (string) API Path
username: # (string) Adguardhome username
password: # (string) Adguardhome password
cookie: # (string) Adguardhome cookie
requestHeaders: # (map) Request Headers 'key1:value1,key2:value2'
insecureSkipVerify: # (bool) Skip TLS verification
autoSetup: # (bool) Automatically setup the instance if it is not initialized
interfaceName: # (string) Network interface name
dhcpServerEnabled: # (bool) Enable DHCP server
api: # (struct)
port: # (int) API port (API is disabled if port is set to 0)
username: # (string) API username
password: # (string) API password
darkMode: # (bool) API dark mode
metrics: # (struct)
enabled: # (bool) Enable metrics
scrapeInterval: # (int64) Interval for metrics scraping
queryLogLimit: # (int) Metrics log query limit
tls: # (struct)
certDir: # (string) API TLS certificate directory
certName: # (string) API TLS certificate file name
keyName: # (string) API TLS key file name
features: # (struct)
dns: # (struct)
accessLists: # (bool) Sync DNS access lists
serverConfig: # (bool) Sync DNS server config
rewrites: # (bool) Sync DNS rewrites
dhcp: # (struct)
serverConfig: # (bool) Sync DHCP server config
staticLeases: # (bool) Sync DHCP static leases
generalSettings: # (bool) Sync general settings
queryLogConfig: # (bool) Sync query log config
statsConfig: # (bool) Sync stats config
clientSettings: # (bool) Sync client settings
services: # (bool) Sync services
filters: # (bool) Sync filters
theme: # (bool) Sync the web UI theme
tlsConfig: # (bool) Sync the TLS config
```
<!-- yaml-doc-end -->
## Username / Password vs. Cookie
@@ -71,9 +209,54 @@ export REPLICA1_PASSWORD=password
adguardhome-sync run
# run as daemon
adguardhome-sync run --cron "*/10 * * * *"
adguardhome-sync run --cron "0 */2 * * *"
```
### Run as Linux Service via Systemd
> Verified on Ubuntu Linux 24.04
Assume you have downloaded the the `adguardhome-sync` binary to `/opt/adguardhome-sync`.
Create systemd service file `/opt/adguardhome-sync/adguardhome-sync.service`:
```
[Unit]
Description = AdGuardHome Sync
After = network.target
[Service]
ExecStart = /opt/adguardhome-sync/adguardhome-sync --config /opt/adguardhome-sync/adguardhome-sync.yaml run
[Install]
WantedBy = multi-user.target
```
Create a configuration file `/opt/adguardhome-sync/adguardhome-sync.yaml`, please follow [Config file](#config-file-1)
section below for details.
Install and enable service:
```bash
sudo cp /opt/adguardhome-sync/adguardhome-sync.service /etc/systemd/system/
sudo systemctl enable adguardhome-sync.service
sudo systemctl start adguardhome-sync.service
```
Then you can check the status:
```bash
sudo systemctl status adguardhome-sync.service
```
If web UI has been enabled in configuration (default port is 8080), can also check the status via
`http://<server-IP>:8080`
## Run Windows
```bash
@@ -95,15 +278,14 @@ set REPLICA1_USERNAME=username
set REPLICA1_PASSWORD=password
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
set FEATURES_DHCP=false
set FEATURES_DHCP_SERVERCONFIG=false
set FEATURES_DHCP_STATICLEASES=false
set FEATURES_DHCP_SERVER_CONFIG=false
set FEATURES_DHCP_STATIC_LEASES=false
# run once
adguardhome-sync run
# run as daemon
adguardhome-sync run --cron "*/10 * * * *"
adguardhome-sync run --cron "0 */2 * * *"
```
## docker cli
@@ -136,130 +318,36 @@ services:
restart: unless-stopped
```
### env
## Unraid
```yaml
---
version: "2.1"
services:
adguardhome-sync:
image: ghcr.io/bakito/adguardhome-sync
container_name: adguardhome-sync
command: run
environment:
LOG_LEVEL: "info"
ORIGIN_URL: "https://192.168.1.2:3000"
# ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
⚠️ Disclaimer: There exists an unraid template for this application. This project does not manage this template.
Also, as unraid is not known to me, I cannot give any support on unraind templates.
ORIGIN_USERNAME: "username"
ORIGIN_PASSWORD: "password"
REPLICA1_URL: "http://192.168.1.3"
REPLICA1_USERNAME: "username"
REPLICA1_PASSWORD: "password"
REPLICA2_URL: "http://192.168.1.4"
REPLICA2_USERNAME: "username"
REPLICA2_PASSWORD: "password"
REPLICA2_API_PATH: "/some/path/control"
# REPLICA2_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
# REPLICA2_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA2_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
# REPLICA2_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
CRON: "*/10 * * * *" # run every 10 minutes
RUNONSTART: true
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
Note when running the Docker container in Unraid please remove unneeded env variables.
If replica2 isn't used, this can cause sync errors.
# Configure the sync API server, disabled if api port is 0
API_PORT: 8080
# API_DARK_MODE: true
# API_USERNAME: admin
# API_PASSWORD: secret
## Home Assistant AdGuard Home Add-on users
# Configure sync features; by default all features are enabled.
# FEATURES_GENERAL_SETTINGS: true
# FEATURES_QUERY_LOG_CONFIG: true
# FEATURES_STATS_CONFIG: true
# FEATURES_CLIENT_SETTINGS: true
# FEATURES_SERVICES: true
# FEATURES_FILTERS: true
# FEATURES_DHCP_SERVER_CONFIG: true
# FEATURES_DHCP_STATIC_LEASES: true
# FEATURES_DNS_SERVER_CONFIG: true
# FEATURES_DNS_ACCESS_LISTS: true
# FEATURES_DNS_REWRITES: true
ports:
- 8080:8080
restart: unless-stopped
```
To enable syncing with a Home Assistant instance using
the [AdGuard Home Add-on](https://github.com/hassio-addons/addon-adguard-home), you will need to enable the disabled
ports, under the Network heading
### Config file
![show-disabled-ports](https://github.com/user-attachments/assets/1df5f352-37a2-4508-82ec-7f270087d0b4)
location: $HOME/.adguardhome-sync.yaml
And then set the port of your choice for the Web interface
```yaml
# cron expression to run in daemon mode. (default; "" = runs only once)
cron: "*/10 * * * *"
![web-interface-port](https://github.com/user-attachments/assets/286ed030-4831-4f49-8b29-53e8802129c3)
# runs the synchronisation on startup
runOnStart: true
Don't forget to save and restart the add-on.
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
continueOnError: false
Depending on your setup, you may also need to disable SSL for the add-on.
origin:
# url of the origin instance
url: https://192.168.1.2:3000
# apiPath: define an api path if other than "/control"
# insecureSkipVerify: true # disable tls check
username: username
password: password
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
The username:password required for the Home Assistant replica is the one you use to login to your instance, however it's
recommended to setup a new local only user with minimal permissions.
# replicas instances
replicas:
# url of the replica instance
- url: http://192.168.1.3
username: username
password: password
# cookie: Replica1-Cookie-Name=CCCOOOKKKIIIEEE
- url: http://192.168.1.4
username: username
password: password
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
# Configure the sync API server, disabled if api port is 0
api:
# Port, default 8080
port: 8080
# if username and password are defined, basic auth is applied to the sync API
username: username
password: password
# enable api dark mode
darkMode: true
# enable metrics on path '/metrics' (api port must be != 0)
# metrics:
# enabled: true
# scrapeInterval: 30s
# queryLogLimit: 10000
# Configure sync features; by default all features are enabled.
features:
generalSettings: true
queryLogConfig: true
statsConfig: true
clientSettings: true
services: true
filters: true
dhcp:
serverConfig: true
staticLeases: true
dns:
serverConfig: true
accessLists: true
rewrites: true
```
All credit for this method goes to [Brunty](https://github.com/brunty) who has a far
more [detailed write up](https://brunty.me/post/replicate-adguard-home-settings-into-home-assistant-adguard-home-addon/)
about this on his blog.
## Log Level
@@ -275,4 +363,9 @@ The following log levels are supported (default: info)
## Log Format
Default log format is `console`.
It can be changed to `json`by setting the environment variable: `LOG_FORMAT=json`
It can be changed to `json` by setting the environment variable: `LOG_FORMAT=json`.
## Video Tutorials
- [Como replicar la configuración de tu servidor DNS Adguard automáticamente - Tu servidor Part #12](https://www.youtube.com/watch?v=1LPeu_JG064) (
Spanish) by [Jonatan Castro](https://github.com/jcastro)

View File

@@ -1,135 +0,0 @@
version: '3'
env:
AGH_MODEL_VERSION: v0.107.43
GOBIN: '{{.USER_WORKING_DIR}}/bin'
tasks:
install-go-tool:
label: "Install {{ .TOOL_NAME }}"
cmds:
- go install {{ .TOOL_MODULE }}
status:
- test -f {{.GOBIN}}/{{.TOOL_NAME}}
deepcopy-gen:
desc: Install deepcopy-gen
cmd:
task: install-go-tool
vars:
TOOL_NAME: deepcopy-gen
TOOL_MODULE: k8s.io/code-generator/cmd/deepcopy-gen
ginkgo:
cmd:
task: install-go-tool
vars:
TOOL_NAME: ginkgo
TOOL_MODULE: github.com/onsi/ginkgo/v2/ginkgo
goreleaser:
cmd:
task: install-go-tool
vars:
TOOL_NAME: goreleaser
TOOL_MODULE: github.com/goreleaser/goreleaser
golangci-lint:
cmd:
task: install-go-tool
vars:
TOOL_NAME: golangci-lint
TOOL_MODULE: github.com/golangci/golangci-lint/cmd/golangci-lint
mockgen:
cmd:
task: install-go-tool
vars:
TOOL_NAME: mockgen
TOOL_MODULE: go.uber.org/mock/mockgen
oapi-codegen:
cmd:
task: install-go-tool
vars:
TOOL_NAME: oapi-codegen
TOOL_MODULE: github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen
semver:
cmd:
task: install-go-tool
vars:
TOOL_NAME: semver
TOOL_MODULE: github.com/bakito/semver
lint:
deps:
- golangci-lint
cmds:
- '{{.GOBIN}}/golangci-lint run --fix'
tidy:
desc: Run go mod tidy
cmd: go mod tidy
generate:
deps:
- deepcopy-gen
cmds:
- mkdir -p ./tmp
- touch ./tmp/deepcopy-gen-boilerplate.go.txt
- '{{.GOBIN}}/deepcopy-gen -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types'
mocks:
deps:
- mockgen
cmds:
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client'
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags'
test:
cmds:
- task: generate
- task: lint
- task: test-ci
test-ci:
deps:
- ginkgo
- tidy
- mocks
cmds:
- '{{.GOBIN}}/ginkgo --cover --coverprofile coverage.out.tmp ./...'
- cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
- go tool cover -func=coverage.out
release:
deps:
- semver
- goreleaser
cmds:
- git tag -s $$version -m"Release $({{.GOBIN}}/semver)
- '{{.GOBIN}}/goreleaser --clean'
test-release:
deps:
- goreleaser
- semver
cmds:
- '{{.GOBIN}}/goreleaser --skip=publish --snapshot --clean'
model:
deps:
- oapi-codegen
cmds:
- mkdir -p tmp
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
- '{{.GOBIN}}/oapi-codegen -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go'
model-diff:
deps:
- oapi-codegen
cmds:
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
- go run openapi/main.go
- diff tmp/schema.yaml tmp/schema-master.yaml

174
cmd/docs/main.go Normal file
View File

@@ -0,0 +1,174 @@
// Print the available environment variables
package main
import (
"fmt"
"io"
"log/slog"
"os"
"reflect"
"strings"
"github.com/bakito/adguardhome-sync/internal/types"
)
const (
envStartMarker = "<!-- env-doc-start -->"
envEndMarker = "<!-- env-doc-end -->"
yamlStartMarker = "<!-- yaml-doc-start -->"
yamlEndMarker = "<!-- yaml-doc-end -->"
)
func main() {
slog.Info("Reading README.md")
content, err := os.ReadFile("README.md")
if err != nil {
slog.Error("Error reading README.md", "error", err)
os.Exit(1)
}
fileContent := string(content)
slog.Info("Generating environment variables")
fileContent = generateEnvDocumentation(fileContent)
slog.Info("Generating yaml configuration")
fileContent = generateYAMLDocumentation(fileContent)
slog.Info("Writing README.md")
err = os.WriteFile("README.md", []byte(fileContent), 0o644)
if err != nil {
slog.Error("Error writing README.md", "error", err)
os.Exit(1)
}
}
func generateEnvDocumentation(fileContent string) string {
var buf strings.Builder
_, _ = buf.WriteString("| Name | Type | Description |\n")
_, _ = buf.WriteString("| :--- | ---- |:----------- |\n")
writeEnvDocumentation(&buf, reflect.TypeOf(types.Config{}), "")
return updateDocumentationSection(fileContent, envStartMarker, envEndMarker, buf.String())
}
func generateYAMLDocumentation(fileContent string) string {
var buf strings.Builder
_, _ = buf.WriteString("```yaml\n")
writeYAMLDocumentation(&buf, reflect.TypeOf(types.Config{}), "", "")
_, _ = buf.WriteString("```\n")
return updateDocumentationSection(fileContent, yamlStartMarker, yamlEndMarker, buf.String())
}
func updateDocumentationSection(fileContent, startMarker, endMarker, newContent string) string {
startIdx := strings.Index(fileContent, startMarker)
endIdx := strings.Index(fileContent, endMarker)
if startIdx == -1 || endIdx == -1 {
slog.Error(fmt.Sprintf("Could not find markers %s and %s in README.md", startMarker, endMarker))
os.Exit(1)
}
return fileContent[:startIdx+len(startMarker)] + "\n" + newContent + fileContent[endIdx:]
}
func writeEnvDocumentation(w io.Writer, t reflect.Type, prefix string) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
for _, field := range reflect.VisibleFields(t) {
if field.PkgPath != "" {
continue
}
envTag := field.Tag.Get("env")
if envTag == "" {
switch field.Name {
case "Origin":
envTag = "ORIGIN"
case "Replica":
envTag = "REPLICA#"
}
}
combinedTag := buildCombinedTag(prefix, envTag)
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if ft.Kind() == reflect.Struct && ft.Name() != "Time" {
writeEnvDocumentation(w, ft, strings.TrimSuffix(combinedTag, "_"))
} else if envTag != "" {
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
docs := field.Tag.Get("documentation")
_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
}
}
}
func writeYAMLDocumentation(w io.Writer, t reflect.Type, firstPrefix, otherPrefix string) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
var i int
for _, field := range reflect.VisibleFields(t) {
if field.PkgPath != "" {
continue
}
yamlTag := field.Tag.Get("yaml")
if yamlTag == "-" {
continue
}
yamlTag = strings.TrimSuffix(yamlTag, ",omitempty")
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
pf := otherPrefix
if i == 0 {
pf = firstPrefix
}
newFirstPrefix := pf + " "
newOtherPrefix := otherPrefix + " "
if yamlTag == "replicas" && ft.Kind() == reflect.Slice {
ft = ft.Elem()
newFirstPrefix += "- "
newOtherPrefix += " "
}
if yamlTag != "" {
docs := field.Tag.Get("documentation")
_, _ = fmt.Fprintf(w, "%s%s: # (%s) %s\n", pf, yamlTag, ft.Kind().String(), docs)
i++
}
if ft.Kind() == reflect.Struct && ft.Name() != "Time" {
writeYAMLDocumentation(w, ft, newFirstPrefix, newOtherPrefix)
}
}
}
func buildCombinedTag(prefix, envTag string) string {
if prefix != "" && envTag != "" {
return prefix + "_" + envTag
} else if prefix != "" {
return prefix
}
return envTag
}

View File

@@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"io"
"log"
@@ -20,19 +21,33 @@ func main() {
}
log.Printf("Patching schema version %s\n", version)
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version))
ctx := context.Background() // Or use context.WithTimeout
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version),
http.NoBody,
)
if err != nil {
log.Fatal(err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer func() { _ = resp.Body.Close() }()
data, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
log.Println(err)
return
}
schema := make(map[string]interface{})
schema := make(map[string]any)
err = yaml.Unmarshal(data, &schema)
if err != nil {
log.Fatalln(err)
log.Println(err)
return
}
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
@@ -45,19 +60,47 @@ func main() {
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
delete(dnsInfo, "allOf")
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]interface{}),
//nolint:forcetypeassert
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]any),
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
log.Fatalln(err)
log.Println(err)
return
}
}
}
correctEntries(schema)
addFakeTags(schema)
b, err := yaml.Marshal(&schema)
if err != nil {
log.Fatalln(err)
log.Println(err)
return
}
log.Printf("Writing schema file tmp/%s", fileName)
err = os.WriteFile("tmp/"+fileName, b, 0o600)
if err != nil {
log.Println(err)
return
}
}
func correctEntries(schema map[string]any) {
}
func addFakeTags(schema map[string]any) {
fake := map[string]any{"faker": `slice_len=24`}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "blocked_filtering", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "dns_queries", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_parental", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_safebrowsing", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
}

View File

@@ -4,9 +4,10 @@ import (
"fmt"
"os"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/version"
"github.com/spf13/cobra"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/version"
)
var (
@@ -14,7 +15,7 @@ var (
logger = log.GetLogger("root")
)
// rootCmd represents the base command when called without any subcommands
// rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "adguardhome-sync",
Short: "Synchronize config from one AdGuardHome instance to another",
@@ -25,7 +26,7 @@ var rootCmd = &cobra.Command{
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
_, _ = fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -1,17 +1,17 @@
package cmd
import (
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/config"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/sync"
)
// runCmd represents the run command
// runCmd represents the run command.
var doCmd = &cobra.Command{
Use: "run",
Short: "Start a synchronisation from origin to replica",
Short: "Start a synchronization from origin to replica",
Long: `Synchronizes the configuration form an origin instance to a replica`,
RunE: func(cmd *cobra.Command, args []string) error {
logger = log.GetLogger("run")
@@ -26,18 +26,16 @@ var doCmd = &cobra.Command{
return err
}
if cfg.PrintConfigOnly {
config, err := yaml.Marshal(cfg)
if err != nil {
if cfg.PrintConfigOnly() {
if err := cfg.Print(); err != nil {
logger.Error(err)
return err
}
logger.Infof("Printing adguardhome-sync config (THE APPLICATION WILL NOT START IN THIS MODE): \n%s",
string(config))
return nil
}
return sync.Sync(cfg)
return sync.Sync(cfg.Get())
},
}
@@ -47,20 +45,21 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
"Can be used to debug the config E.g: when having authentication issues.")
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronization task "+
"will not fail on single errors, but will log the errors and continue.")
doCmd.PersistentFlags().Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username")
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode")
doCmd.PersistentFlags().
Int(config.FlagAPIPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
doCmd.PersistentFlags().String(config.FlagAPIUsername, "", "Sync API username")
doCmd.PersistentFlags().String(config.FlagAPIPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagAPIDarkMode, "", "API UI in dark mode")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsServerConfig, true, "Enable DNS server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsAccessLists, true, "Enable DNS server access lists feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSServerConfig, true, "Enable DNS server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSAccessLists, true, "Enable DNS server access lists feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSRewrites, true, "Enable DNS rewrites feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
@@ -68,22 +67,27 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureTLSConfig, false, "Enable TLS config sync feature")
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
doCmd.PersistentFlags().String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().
String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
doCmd.PersistentFlags().String(config.FlagOriginAPIPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
doCmd.PersistentFlags().String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path")
doCmd.PersistentFlags().
String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
doCmd.PersistentFlags().String(config.FlagReplicaAPIPath, "/control", "Replica instance API path")
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
doCmd.PersistentFlags().Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
doCmd.PersistentFlags().
Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().
String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
}

473
go.mod
View File

@@ -1,439 +1,82 @@
module github.com/bakito/adguardhome-sync
go 1.22
go 1.25.4
require (
github.com/bakito/semver v1.1.3
github.com/caarlos0/env/v10 v10.0.0
github.com/deepmap/oapi-codegen/v2 v2.1.0
github.com/gin-gonic/gin v1.9.1
github.com/go-resty/resty/v2 v2.9.1
github.com/golangci/golangci-lint v1.56.2
github.com/caarlos0/env/v11 v11.3.1
github.com/gin-gonic/gin v1.11.0
github.com/go-faker/faker/v4 v4.7.0
github.com/go-resty/resty/v2 v2.17.0
github.com/google/uuid v1.6.0
github.com/goreleaser/goreleaser v1.24.0
github.com/jinzhu/copier v0.4.0
github.com/oapi-codegen/runtime v1.1.1
github.com/onsi/ginkgo/v2 v2.16.0
github.com/onsi/gomega v1.31.1
github.com/prometheus/client_golang v1.17.0
github.com/oapi-codegen/runtime v1.1.2
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/prometheus/client_golang v1.23.2
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0
go.uber.org/mock v0.4.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
golang.org/x/mod v0.16.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/cobra v1.10.1
go.uber.org/mock v0.6.0
go.uber.org/zap v1.27.1
golang.org/x/mod v0.30.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.29.2
k8s.io/code-generator v0.29.2
k8s.io/apimachinery v0.34.2
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
cloud.google.com/go v0.110.10 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/kms v1.15.5 // indirect
cloud.google.com/go/storage v1.35.1 // indirect
code.gitea.io/sdk/gitea v0.17.1 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/4meepo/tagalign v1.3.3 // indirect
github.com/Abirdcfly/dupword v0.0.13 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Antonboom/errname v0.1.12 // indirect
github.com/Antonboom/nilnil v0.1.7 // indirect
github.com/Antonboom/testifylint v1.1.2 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.2 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/atc0005/go-teams-notify/v2 v2.9.0 // indirect
github.com/aws/aws-sdk-go v1.50.10 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.27.5 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.2.1 // indirect
github.com/breml/bidichk v0.2.7 // indirect
github.com/breml/errchkjson v0.3.6 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/butuzov/ireturn v0.3.0 // indirect
github.com/butuzov/mirror v1.1.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/caarlos0/ctrlc v1.2.0 // indirect
github.com/caarlos0/env/v9 v9.0.0 // indirect
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/caarlos0/go-shellwords v1.0.12 // indirect
github.com/caarlos0/go-version v0.1.1 // indirect
github.com/caarlos0/log v0.4.4 // indirect
github.com/catenacyber/perfsprint v0.6.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/daixiang0/gci v0.12.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect
github.com/dghubble/oauth1 v0.7.2 // indirect
github.com/dghubble/sling v1.4.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/getkin/kin-openapi v0.122.0 // indirect
github.com/ghostiam/protogetter v0.3.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-critic/go-critic v0.11.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.11.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.2.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.4.1 // indirect
github.com/golangci/revgrep v0.5.2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.19.0 // indirect
github.com/google/go-github/v57 v57.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/ko v0.15.1 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/google/rpmpack v0.5.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/goreleaser/chglog v0.5.0 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/nfpm/v2 v2.35.3 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.12.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.7.0 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jjti/go-spancheck v0.5.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/compress v1.17.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-mastodon v0.0.6 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mgechev/revive v1.3.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/moricho/tparallel v0.3.1 // indirect
github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-cobra v1.2.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.15.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.4.8 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.0 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/securego/gosec/v2 v2.19.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sigstore/cosign/v2 v2.2.1 // indirect
github.com/sigstore/rekor v1.3.3 // indirect
github.com/sigstore/sigstore v1.7.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/slack-go/slack v0.12.3 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.17.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tetafro/godot v1.4.16 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.1.0 // indirect
github.com/uudashr/gocognit v1.1.2 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/go-gitlab v0.97.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.1 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go-simpler.org/musttag v0.8.0 // indirect
go-simpler.org/sloglint v0.4.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
gocloud.dev v0.36.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.152.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.4.6 // indirect
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
mvdan.cc/gofumpt v0.6.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kind v0.20.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

1784
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,18 @@ func (cl *client) doGet(req *resty.Request, url string) error {
rl.Debug("do get")
resp, err := req.Get(url)
if err != nil {
if resp != nil && resp.StatusCode() == http.StatusFound {
loc := resp.Header().Get("Location")
if loc == "/install.html" || loc == "/control/install.html" {
return ErrSetupNeeded
l := rl
if resp != nil {
if resp.StatusCode() == http.StatusFound {
loc := resp.Header().Get("Location")
if loc == "/install.html" || loc == "/control/install.html" {
return ErrSetupNeeded
}
}
l = l.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err)
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
l.Debug("error in do get")
return detailedError(resp, err)
}

View File

@@ -11,19 +11,33 @@ import (
"strconv"
"strings"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/internal/utils"
)
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
type Error struct {
message string
errorCode int
}
func (e *Error) Error() string {
return e.message
}
func (e *Error) Code() int {
return e.errorCode
}
var (
l = log.GetLogger("client")
// ErrSetupNeeded custom error
// ErrSetupNeeded custom error.
ErrSetupNeeded = errors.New("setup needed")
)
@@ -33,16 +47,19 @@ func detailedError(resp *resty.Response, err error) error {
e += fmt.Sprintf("(%s)", string(resp.Body()))
}
if err != nil {
e += fmt.Sprintf(": %s", err.Error())
e += ": " + err.Error()
}
return &Error{
message: e,
errorCode: resp.StatusCode(),
}
return errors.New(e)
}
// New create a new client
// New create a new client.
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL)
apiURL = config.URL + "/control"
} else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
}
@@ -51,7 +68,7 @@ func New(config types.AdGuardInstance) (Client, error) {
return nil, err
}
u.Path = path.Clean(u.Path)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true).SetHeaders(config.RequestHeaders)
// #nosec G402 has to be explicitly enabled
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
@@ -84,7 +101,9 @@ func New(config types.AdGuardInstance) (Client, error) {
}, nil
}
// Client AdguardHome API client interface
// Client AdguardHome API client interface.
//
//nolint:interfacebloat
type Client interface {
Host() string
Status() (*model.ServerStatus, error)
@@ -109,27 +128,27 @@ type Client interface {
SetSafeSearchConfig(settings *model.SafeSearchConfig) error
ProfileInfo() (*model.ProfileInfo, error)
SetProfileInfo(settings *model.ProfileInfo) error
BlockedServices() (*model.BlockedServicesArray, error)
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
SetBlockedServices(services *model.BlockedServicesArray) error
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
Clients() (*model.Clients, error)
AddClient(client *model.Client) error
UpdateClient(client *model.Client) error
DeleteClient(client *model.Client) error
QueryLogConfig() (*model.QueryLogConfig, error)
SetQueryLogConfig(*model.QueryLogConfig) error
StatsConfig() (*model.StatsConfig, error)
SetStatsConfig(sc *model.StatsConfig) error
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error
StatsConfig() (*model.GetStatsConfigResponse, error)
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
Setup() error
AccessList() (*model.AccessList, error)
SetAccessList(*model.AccessList) error
SetAccessList(accessList *model.AccessList) error
DNSConfig() (*model.DNSConfig, error)
SetDNSConfig(*model.DNSConfig) error
SetDNSConfig(config *model.DNSConfig) error
DhcpConfig() (*model.DhcpStatus, error)
SetDhcpConfig(*model.DhcpStatus) error
SetDhcpConfig(status *model.DhcpStatus) error
AddDHCPStaticLease(lease model.DhcpStaticLease) error
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
TLSConfig() (*model.TlsConfig, error)
SetTLSConfig(tls *model.TlsConfig) error
}
type client struct {
@@ -168,7 +187,10 @@ func (cl *client) Stats() (*model.Stats, error) {
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
ql := &model.QueryLog{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(ql), fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit))
err := cl.doGet(
cl.client.R().EnableTrace().SetResult(ql),
fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit),
)
return ql, err
}
@@ -223,7 +245,7 @@ func (cl *client) toggleStatus(mode string) (bool, error) {
}
func (cl *client) toggleBool(mode string, enable bool) error {
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode))
cl.log.With("enable", enable).Info("Toggle " + mode)
var target string
if enable {
target = "enable"
@@ -262,7 +284,10 @@ func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}), "/filtering/refresh")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}),
"/filtering/refresh",
)
}
func (cl *client) ToggleProtection(enable bool) error {
@@ -287,17 +312,6 @@ func (cl *client) ToggleFiltering(enabled bool, interval int) error {
}), "/filtering/config")
}
func (cl *client) BlockedServices() (*model.BlockedServicesArray, error) {
svcs := &model.BlockedServicesArray{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(svcs), "/blocked_services/list")
return svcs, err
}
func (cl *client) SetBlockedServices(services *model.BlockedServicesArray) error {
cl.log.With("services", model.ArrayString(services)).Info("Set blocked services")
return cl.doPost(cl.client.R().EnableTrace().SetBody(services), "/blocked_services/set")
}
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
sched := &model.BlockedServicesSchedule{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
@@ -322,7 +336,10 @@ func (cl *client) AddClient(client *model.Client) error {
func (cl *client) UpdateClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Update client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}),
"/clients/update",
)
}
func (cl *client) DeleteClient(client *model.Client) error {
@@ -330,26 +347,27 @@ func (cl *client) DeleteClient(client *model.Client) error {
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
}
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
qlc := &model.QueryLogConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
qlc := &model.QueryLogConfigWithIgnored{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog/config")
return qlc, err
}
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfig) error {
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).Info("Set query log config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(qlc), "/querylog_config")
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfigWithIgnored) error {
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).
Info("Set query log config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
}
func (cl *client) StatsConfig() (*model.StatsConfig, error) {
stats := &model.StatsConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
func (cl *client) StatsConfig() (*model.GetStatsConfigResponse, error) {
stats := &model.GetStatsConfigResponse{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats/config")
return stats, err
}
func (cl *client) SetStatsConfig(sc *model.StatsConfig) error {
cl.log.With("interval", *sc.Interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(sc), "/stats_config")
func (cl *client) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
cl.log.With("interval", sc.Interval).Info("Set stats config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(sc), "/stats/config/update")
}
func (cl *client) Setup() error {
@@ -450,3 +468,14 @@ func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
}
func (cl *client) TLSConfig() (*model.TlsConfig, error) {
tlsc := &model.TlsConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(tlsc), "/tls/status")
return tlsc, err
}
func (cl *client) SetTLSConfig(tlsc *model.TlsConfig) error {
cl.log.With("enabled", tlsc.Enabled).Info("Set TLS config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(tlsc), "/tls/configure")
}

View File

@@ -8,13 +8,14 @@ import (
"os"
"path/filepath"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var (
@@ -137,7 +138,14 @@ var _ = Describe("Client", func() {
Context("Setup", func() {
It("should add setup the instance", func() {
ts, cl = ClientPost("/install/configure", fmt.Sprintf(`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`, username, password))
ts, cl = ClientPost(
"/install/configure",
fmt.Sprintf(
`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":%q,"password":%q}`,
username,
password,
),
)
err := cl.Setup()
Ω(err).ShouldNot(HaveOccurred())
})
@@ -239,20 +247,6 @@ var _ = Describe("Client", func() {
})
})
Context("BlockedServices", func() {
It("should read BlockedServices", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
s, err := cl.BlockedServices()
Ω(err).ShouldNot(HaveOccurred())
Ω(*s).Should(HaveLen(2))
})
It("should set BlockedServices", func() {
ts, cl = ClientPost("/blocked_services/set", `["bar","foo"]`)
err := cl.SetBlockedServices(&model.BlockedServicesArray{"foo", "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("BlockedServicesSchedule", func() {
It("should read BlockedServicesSchedule", func() {
ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
@@ -308,7 +302,7 @@ var _ = Describe("Client", func() {
Context("QueryLogConfig", func() {
It("should read QueryLogConfig", func() {
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
ts, cl = ClientGet("querylog_config.json", "/querylog/config")
qlc, err := cl.QueryLogConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(qlc.Enabled).ShouldNot(BeNil())
@@ -317,26 +311,36 @@ var _ = Describe("Client", func() {
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
})
It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"anonymize_client_ip":true,"enabled":true,"interval":123}`)
ts, cl = ClientPut(
"/querylog/config/update",
`{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`,
)
var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: utils.Ptr(true), Interval: &interval, Enabled: utils.Ptr(true)})
err := cl.SetQueryLogConfig(&model.QueryLogConfigWithIgnored{
QueryLogConfig: model.QueryLogConfig{
AnonymizeClientIp: utils.Ptr(true),
Interval: &interval,
Enabled: utils.Ptr(true),
},
Ignored: []string{"foo.bar"},
})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("StatsConfig", func() {
It("should read StatsConfig", func() {
ts, cl = ClientGet("stats_info.json", "/stats_info")
ts, cl = ClientGet("stats_info.json", "/stats/config")
sc, err := cl.StatsConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(sc.Interval).ShouldNot(BeNil())
Ω(*sc.Interval).Should(Equal(model.StatsConfigInterval(1)))
Ω(sc.Interval).Should(Equal(float32(1)))
})
It("should set StatsConfig", func() {
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
ts, cl = ClientPost("/stats/config/update", `{"enabled":false,"ignored":null,"interval":123}`)
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -361,8 +365,8 @@ var _ = Describe("Client", func() {
Context("doPost", func() {
It("should return an error on status code != 200", func() {
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized"))
})
@@ -370,10 +374,10 @@ var _ = Describe("Client", func() {
})
})
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
func ClientGet(file, path string) (*httptest.Server, client.Client) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
b, err := os.ReadFile(filepath.Join("../../testdata", file))
b, err := os.ReadFile(filepath.Join("..", "..", "testdata", file))
Ω(err).ShouldNot(HaveOccurred())
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)

View File

@@ -11,19 +11,20 @@ import (
"net/url"
"path"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
)
var l = log.GetLogger("client")
// New create a new api client
// New create a new api client.
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL)
apiURL = config.URL + "/control"
} else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
}
@@ -96,7 +97,7 @@ func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterCo
return write(ctx, config, a.client.FilteringConfig)
}
func write[B interface{}](
func write[B any](
ctx context.Context,
body B,
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
@@ -112,7 +113,7 @@ func write[B interface{}](
return nil
}
func read[I interface{}](
func read[I any](
ctx context.Context,
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
parse func(rsp *http.Response) (*I, error),

View File

@@ -3,7 +3,7 @@ package client
import (
"context"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
type Client interface {

View File

@@ -3,8 +3,9 @@ package client
import (
"net/http"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/go-resty/resty/v2"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
var _ model.HttpRequestDoer = &adapter{}

View File

@@ -5,11 +5,14 @@ import (
"sort"
"strings"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/jinzhu/copier"
"go.uber.org/zap"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/internal/utils"
)
// Clone the config
// Clone the config.
func (c *DhcpStatus) Clone() *DhcpStatus {
clone := &DhcpStatus{}
_ = copier.Copy(clone, c)
@@ -25,16 +28,16 @@ func (c *DhcpStatus) cleanV4V6() {
}
}
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance.
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
c.cleanV4V6()
o.cleanV4V6()
return c.Equals(o)
}
// Equals dhcp server config equal check
// Equals dhcp server config equal check.
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JsonEquals(c, o)
return utils.JSONEquals(c, o)
}
func (c *DhcpStatus) HasConfig() bool {
@@ -54,8 +57,8 @@ func (j DhcpConfigV6) isValid() bool {
type DhcpStaticLeases []DhcpStaticLease
// MergeDhcpStaticLeases the leases
func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (DhcpStaticLeases, DhcpStaticLeases) {
// MergeDhcpStaticLeases the leases.
func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
var thisLeases []DhcpStaticLease
var otherLeases []DhcpStaticLease
@@ -67,8 +70,6 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
}
current := make(map[string]DhcpStaticLease)
var adds DhcpStaticLeases
var removes DhcpStaticLeases
for _, le := range thisLeases {
current[le.Mac] = le
}
@@ -88,21 +89,21 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
return adds, removes
}
// Equals dns config equal check
// Equals dns config equal check.
func (c *DNSConfig) Equals(o *DNSConfig) bool {
cc := c.Clone()
oo := o.Clone()
cc.Sort()
oo.Sort()
return utils.JsonEquals(cc, oo)
return utils.JSONEquals(cc, oo)
}
func (c *DNSConfig) Clone() *DNSConfig {
return utils.Clone(c, &DNSConfig{})
}
// Sort sort dns config
// Sort dns config.
func (c *DNSConfig) Sort() {
if c.UpstreamDns != nil {
sort.Strings(*c.UpstreamDns)
@@ -117,14 +118,14 @@ func (c *DNSConfig) Sort() {
}
}
// Equals access list equal check
// Equals access list equal check.
func (al *AccessList) Equals(o *AccessList) bool {
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
}
func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
if a == nil && b == nil {
return true
}
@@ -150,7 +151,7 @@ func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
return true
}
// Sort clients
// Sort clients.
func (cl *Client) Sort() {
if cl.Ids != nil {
sort.Strings(*cl.Ids)
@@ -166,15 +167,42 @@ func (cl *Client) Sort() {
}
}
// Equals Clients equal check
// PrepareDiff so we skip it in diff.
func (cl *Client) PrepareDiff() *string {
var tz *string
bss := cl.BlockedServicesSchedule
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
tz = cl.BlockedServicesSchedule.TimeZone
cl.BlockedServicesSchedule.TimeZone = nil
}
return tz
}
// AfterDiff reset after diff.
func (cl *Client) AfterDiff(tz *string) {
if cl.BlockedServicesSchedule != nil {
cl.BlockedServicesSchedule.TimeZone = tz
}
}
// Equals Clients equal check.
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
return utils.JsonEquals(cl, o)
bssCl := cl.PrepareDiff()
bssO := o.PrepareDiff()
defer func() {
cl.AfterDiff(bssCl)
o.AfterDiff(bssO)
}()
return utils.JSONEquals(cl, o)
}
// Add ac client
// Add ac client.
func (clients *Clients) Add(cl Client) {
if clients.Clients == nil {
clients.Clients = &ClientsArray{cl}
@@ -184,8 +212,8 @@ func (clients *Clients) Add(cl Client) {
}
}
// Merge merge Clients
func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client) {
// Merge merge Clients.
func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
current := make(map[string]*Client)
if clients.Clients != nil {
cc := *clients.Clients
@@ -202,10 +230,6 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
}
}
var adds []*Client
var removes []*Client
var updates []*Client
for _, cl := range expected {
if oc, ok := current[*cl.Name]; ok {
if !cl.Equals(oc) {
@@ -224,7 +248,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
return adds, updates, removes
}
// Key RewriteEntry key
// Key RewriteEntry key.
func (re *RewriteEntry) Key() string {
var d string
var a string
@@ -237,16 +261,13 @@ func (re *RewriteEntry) Key() string {
return fmt.Sprintf("%s#%s", d, a)
}
// RewriteEntries list of RewriteEntry
// RewriteEntries list of RewriteEntry.
type RewriteEntries []RewriteEntry
// Merge RewriteEntries
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
// Merge RewriteEntries.
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
current := make(map[string]RewriteEntry)
var adds RewriteEntries
var removes RewriteEntries
var duplicates RewriteEntries
processed := make(map[string]bool)
for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok {
@@ -279,16 +300,13 @@ func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, Rewrite
return adds, removes, duplicates
}
func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter) {
func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
if this == nil && other == nil {
return nil, nil, nil
}
current := make(map[string]*Filter)
var adds []Filter
var updates []Filter
var removes []Filter
if this != nil {
for _, fi := range *this {
current[fi.Url] = &fi
@@ -315,24 +333,29 @@ func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter
return adds, updates, removes
}
// Equals Filter equal check
// Equals Filter equal check.
func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
}
// Equals QueryLogConfig equal check
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool {
return ptrEquals(qlc.Enabled, o.Enabled) &&
ptrEquals(qlc.AnonymizeClientIp, o.AnonymizeClientIp) &&
qlc.Interval.Equals(o.Interval)
type QueryLogConfigWithIgnored struct {
QueryLogConfig
// Ignored List of host names, which should not be written to log
Ignored []string `json:"ignored,omitempty"`
}
// Equals QueryLogConfigInterval equal check
// Equals QueryLogConfig equal check.
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
return utils.JSONEquals(qlc, o)
}
// Equals QueryLogConfigInterval equal check.
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
return ptrEquals(qlc, o)
}
func ptrEquals[T comparable](a *T, b *T) bool {
func ptrEquals[T comparable](a, b *T) bool {
if a == nil && b == nil {
return true
}
@@ -348,7 +371,7 @@ func ptrEquals[T comparable](a *T, b *T) bool {
return aa == bb
}
// EnableConfig API struct
// EnableConfig API struct.
type EnableConfig struct {
Enabled bool `json:"enabled"`
}
@@ -363,30 +386,29 @@ func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
ptrEquals(ssc.Youtube, o.Youtube)
}
func (pi *ProfileInfo) Equals(o *ProfileInfo) bool {
return pi.Language == o.Language &&
pi.Theme == o.Theme
func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool {
return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme)
}
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo) *ProfileInfo {
if pi.Equals(o) {
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo {
if pi.Equals(o, withTheme) {
return nil
}
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" {
merged.Language = o.Language
}
if o.Theme != "" {
if withTheme && o.Theme != "" {
merged.Theme = o.Theme
}
if merged.Name == "" || merged.Language == "" || merged.Theme == "" || merged.Equals(pi) {
if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) {
return nil
}
return merged
}
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JsonEquals(bss, o)
return utils.JSONEquals(bss, o)
}
func (bss *BlockedServicesSchedule) ServicesString() string {
@@ -401,3 +423,74 @@ func ArrayString(a *[]string) string {
sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
}
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
// disable UsePrivatePtrResolvers if not configured
// https://github.com/AdguardTeam/AdGuardHome/issues/6820
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
l.Warn(
"disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin",
)
c.UsePrivatePtrResolvers = utils.Ptr(false)
}
}
// Equals GetStatsConfigResponse equal check.
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
return utils.JSONEquals(sc, o)
}
func NewStats() *Stats {
return &Stats{
NumBlockedFiltering: ptr.To(0),
NumReplacedParental: ptr.To(0),
NumReplacedSafesearch: ptr.To(0),
NumReplacedSafebrowsing: ptr.To(0),
NumDnsQueries: ptr.To(0),
BlockedFiltering: ptr.To(make([]int, 24)),
DnsQueries: ptr.To(make([]int, 24)),
ReplacedParental: ptr.To(make([]int, 24)),
ReplacedSafebrowsing: ptr.To(make([]int, 24)),
}
}
func (s *Stats) Add(other *Stats) {
s.NumBlockedFiltering = addInt(s.NumBlockedFiltering, other.NumBlockedFiltering)
s.NumReplacedSafebrowsing = addInt(s.NumReplacedSafebrowsing, other.NumReplacedSafebrowsing)
s.NumDnsQueries = addInt(s.NumDnsQueries, other.NumDnsQueries)
s.NumReplacedSafesearch = addInt(s.NumReplacedSafesearch, other.NumReplacedSafesearch)
s.NumReplacedParental = addInt(s.NumReplacedParental, other.NumReplacedParental)
s.BlockedFiltering = sumUp(s.BlockedFiltering, other.BlockedFiltering)
s.DnsQueries = sumUp(s.DnsQueries, other.DnsQueries)
s.ReplacedParental = sumUp(s.ReplacedParental, other.ReplacedParental)
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
}
func addInt(t, add *int) *int {
if add != nil {
return ptr.To(*t + *add)
}
return t
}
func sumUp(t, o *[]int) *[]int {
if o != nil {
tt := *t
oo := *o
var sum []int
for i := range tt {
if len(oo) >= i {
sum = append(sum, tt[i]+oo[i])
}
}
return &sum
}
return t
}
func (c *TlsConfig) Equals(config *TlsConfig) bool {
return utils.JSONEquals(c, config)
}

View File

@@ -1,6 +1,6 @@
// Package model provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
package model
import (
@@ -91,6 +91,15 @@ const (
QueryLogConfigIntervalN90 QueryLogConfigInterval = 90
)
// Defines values for QueryLogItemClientProto.
const (
Dnscrypt QueryLogItemClientProto = "dnscrypt"
Doh QueryLogItemClientProto = "doh"
Doq QueryLogItemClientProto = "doq"
Dot QueryLogItemClientProto = "dot"
Empty QueryLogItemClientProto = ""
)
// Defines values for QueryLogItemReason.
const (
QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList"
@@ -186,6 +195,9 @@ type AddressesInfo struct {
// BlockedService defines model for BlockedService.
type BlockedService struct {
// GroupId The ID of the group, that the service belongs to.
GroupId *string `json:"group_id,omitempty"`
// IconSvg The SVG icon as a Base64-encoded string to make it easier to embed it into a data URL.
IconSvg string `json:"icon_svg"`
@@ -202,6 +214,7 @@ type BlockedService struct {
// BlockedServicesAll defines model for BlockedServicesAll.
type BlockedServicesAll struct {
BlockedServices []BlockedService `json:"blocked_services"`
Groups interface{} `json:"groups"`
}
// BlockedServicesArray defines model for BlockedServicesArray.
@@ -294,7 +307,7 @@ type Client struct {
// SafeSearch Safe search settings.
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Upstreams *[]string `json:"upstreams,omitempty"`
@@ -360,7 +373,7 @@ type ClientFindSubEntry struct {
// SafeSearch Safe search settings.
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
Upstreams *[]string `json:"upstreams,omitempty"`
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
@@ -397,6 +410,17 @@ type ClientsFindEntry map[string]ClientFindSubEntry
// ClientsFindResponse Client search results.
type ClientsFindResponse = []ClientsFindEntry
// ClientsSearchRequest Client search request
type ClientsSearchRequest struct {
Clients *[]ClientsSearchRequestItem `json:"clients,omitempty"`
}
// ClientsSearchRequestItem defines model for ClientsSearchRequestItem.
type ClientsSearchRequestItem struct {
// Id Client IP address, CIDR, MAC address, or ClientID
Id *string `json:"id,omitempty"`
}
// DNSConfig DNS server configuration
type DNSConfig struct {
// BlockedResponseTtl TTL for blocked responses.
@@ -406,16 +430,23 @@ type DNSConfig struct {
BlockingMode *DNSConfigBlockingMode `json:"blocking_mode,omitempty"`
// BootstrapDns Bootstrap servers, port is optional after colon. Empty value will reset it to default values.
BootstrapDns *[]string `json:"bootstrap_dns,omitempty"`
CacheOptimistic *bool `json:"cache_optimistic,omitempty"`
CacheSize *int `json:"cache_size,omitempty"`
CacheTtlMax *int `json:"cache_ttl_max,omitempty"`
CacheTtlMin *int `json:"cache_ttl_min,omitempty"`
DisableIpv6 *bool `json:"disable_ipv6,omitempty"`
DnssecEnabled *bool `json:"dnssec_enabled,omitempty"`
EdnsCsCustomIp *string `json:"edns_cs_custom_ip,omitempty"`
EdnsCsEnabled *bool `json:"edns_cs_enabled,omitempty"`
EdnsCsUseCustom *bool `json:"edns_cs_use_custom,omitempty"`
BootstrapDns *[]string `json:"bootstrap_dns,omitempty"`
// CacheEnabled Enables or disables the DNS response cache.
//
// If `cache_enabled` is `true`, the companion field `cache_size` must
// be present and greater than 0, or the `dns.cache_size` setting in
// the configuration file must already be greater than 0.
CacheEnabled *bool `json:"cache_enabled,omitempty"`
CacheOptimistic *bool `json:"cache_optimistic,omitempty"`
CacheSize *int `json:"cache_size,omitempty"`
CacheTtlMax *int `json:"cache_ttl_max,omitempty"`
CacheTtlMin *int `json:"cache_ttl_min,omitempty"`
DisableIpv6 *bool `json:"disable_ipv6,omitempty"`
DnssecEnabled *bool `json:"dnssec_enabled,omitempty"`
EdnsCsCustomIp *string `json:"edns_cs_custom_ip,omitempty"`
EdnsCsEnabled *bool `json:"edns_cs_enabled,omitempty"`
EdnsCsUseCustom *bool `json:"edns_cs_use_custom,omitempty"`
// FallbackDns List of fallback DNS servers used when upstream DNS servers are not responding. Empty value will clear the list.
FallbackDns *[]string `json:"fallback_dns,omitempty"`
@@ -443,8 +474,11 @@ type DNSConfig struct {
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
// UpstreamMode Upstream modes enumeration.
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
// UpstreamTimeout The number of seconds to wait for a response from the upstream server
UpstreamTimeout *int `json:"upstream_timeout,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
}
// DNSConfigBlockingMode defines model for DNSConfig.BlockingMode.
@@ -595,7 +629,7 @@ type FilterCheckHostResponse struct {
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
// Deprecated: use `rules[*].filter_list_id` instead.
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
FilterId *int `json:"filter_id,omitempty"`
// IpAddrs Set if reason=Rewrite
@@ -606,7 +640,7 @@ type FilterCheckHostResponse struct {
// Rule Filtering rule applied to the request (if any).
// Deprecated: use `rules[*].text` instead.
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
Rule *string `json:"rule,omitempty"`
// Rules Applied rules.
@@ -725,11 +759,18 @@ type Login struct {
// NetInterface Network interface info
type NetInterface struct {
// Flags Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast".
Flags string `json:"flags"`
HardwareAddress string `json:"hardware_address"`
IpAddresses *[]string `json:"ip_addresses,omitempty"`
Mtu int `json:"mtu"`
Name string `json:"name"`
Flags string `json:"flags"`
// GatewayIp The IP address of the gateway.
GatewayIp string `json:"gateway_ip"`
HardwareAddress string `json:"hardware_address"`
// Ipv4Addresses The addresses of the interface of v4 family.
Ipv4Addresses []string `json:"ipv4_addresses"`
// Ipv6Addresses The addresses of the interface of v6 family.
Ipv6Addresses []string `json:"ipv6_addresses"`
Name string `json:"name"`
}
// NetInterfaces Network interfaces dictionary, keys are interface names.
@@ -791,8 +832,8 @@ type QueryLogItem struct {
ClientId *string `json:"client_id,omitempty"`
// ClientInfo Client information for a query log item.
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
ClientProto *interface{} `json:"client_proto,omitempty"`
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
ClientProto *QueryLogItemClientProto `json:"client_proto,omitempty"`
// Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any.
Ecs *string `json:"ecs,omitempty"`
@@ -800,7 +841,7 @@ type QueryLogItem struct {
// FilterId In case if there's a rule applied to this DNS request, this is ID of the filter list that the rule belongs to.
// Deprecated: use `rules[*].filter_list_id` instead.
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
FilterId *int `json:"filterId,omitempty"`
// OriginalAnswer Answer from upstream server (optional)
@@ -814,7 +855,7 @@ type QueryLogItem struct {
// Rule Filtering rule applied to the request (if any).
// Deprecated: use `rules[*].text` instead.
// Deprecated:
// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
Rule *string `json:"rule,omitempty"`
// Rules Applied rules.
@@ -833,6 +874,9 @@ type QueryLogItem struct {
Upstream *string `json:"upstream,omitempty"`
}
// QueryLogItemClientProto defines model for QueryLogItem.ClientProto.
type QueryLogItemClientProto string
// QueryLogItemReason Request filtering status.
type QueryLogItemReason string
@@ -886,11 +930,20 @@ type RewriteEntry struct {
// Domain Domain name
Domain *string `json:"domain,omitempty"`
// Enabled Optional. If omitted on add, defaults to `true`. On update, omitted preserves previous value.
Enabled *bool `json:"enabled,omitempty"`
}
// RewriteList Rewrite rules array
type RewriteList = []RewriteEntry
// RewriteSettings DNS rewrite settings
type RewriteSettings struct {
// Enabled indicates whether rewrites are applied
Enabled bool `json:"enabled"`
}
// RewriteUpdate Rewrite rule update object
type RewriteUpdate struct {
// Target Rewrite rule
@@ -904,6 +957,7 @@ type RewriteUpdate struct {
type SafeSearchConfig struct {
Bing *bool `json:"bing,omitempty"`
Duckduckgo *bool `json:"duckduckgo,omitempty"`
Ecosia *bool `json:"ecosia,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Google *bool `json:"google,omitempty"`
Pixabay *bool `json:"pixabay,omitempty"`
@@ -951,6 +1005,12 @@ type ServerStatus struct {
Version string `json:"version"`
}
// ServiceGroup defines model for ServiceGroup.
type ServiceGroup struct {
// Id The ID of this group.
Id string `json:"id"`
}
// SetProtectionRequest Protection state configuration
type SetProtectionRequest struct {
// Duration Duration of a pause, in milliseconds. Enabled should be false.
@@ -967,8 +1027,8 @@ type SetRulesRequest struct {
type Stats struct {
// AvgProcessingTime Average time in seconds on processing a DNS request
AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"`
BlockedFiltering *[]int `json:"blocked_filtering,omitempty"`
DnsQueries *[]int `json:"dns_queries,omitempty"`
BlockedFiltering *[]int `faker:"slice_len=24" json:"blocked_filtering,omitempty"`
DnsQueries *[]int `faker:"slice_len=24" json:"dns_queries,omitempty"`
// NumBlockedFiltering Number of requests blocked by filtering rules
NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"`
@@ -984,8 +1044,8 @@ type Stats struct {
// NumReplacedSafesearch Number of requests blocked by safesearch module
NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"`
ReplacedParental *[]int `json:"replaced_parental,omitempty"`
ReplacedSafebrowsing *[]int `json:"replaced_safebrowsing,omitempty"`
ReplacedParental *[]int `faker:"slice_len=24" json:"replaced_parental,omitempty"`
ReplacedSafebrowsing *[]int `faker:"slice_len=24" json:"replaced_safebrowsing,omitempty"`
// TimeUnits Time units
TimeUnits *StatsTimeUnits `json:"time_units,omitempty"`
@@ -1131,6 +1191,9 @@ type DhcpStaticLeaseBody = DhcpStaticLease
// RewriteEntryBody Rewrite rule
type RewriteEntryBody = RewriteEntry
// RewriteSettingsBody DNS rewrite settings
type RewriteSettingsBody = RewriteSettings
// RewriteUpdateBody Rewrite rule update object
type RewriteUpdateBody = RewriteUpdate
@@ -1165,7 +1228,13 @@ type ClientsFindParams struct {
// FilteringCheckHostParams defines parameters for FilteringCheckHost.
type FilteringCheckHostParams struct {
// Name Filter by host name
Name *string `form:"name,omitempty" json:"name,omitempty"`
Name string `form:"name" json:"name"`
// Client Optional ClientID or client IP address
Client *string `form:"client,omitempty" json:"client,omitempty"`
// Qtype Optional DNS type
Qtype *string `form:"qtype,omitempty" json:"qtype,omitempty"`
}
// QueryLogParams defines parameters for QueryLog.
@@ -1204,6 +1273,9 @@ type ClientsAddJSONRequestBody = Client
// ClientsDeleteJSONRequestBody defines body for ClientsDelete for application/json ContentType.
type ClientsDeleteJSONRequestBody = ClientDelete
// ClientsSearchJSONRequestBody defines body for ClientsSearch for application/json ContentType.
type ClientsSearchJSONRequestBody = ClientsSearchRequest
// ClientsUpdateJSONRequestBody defines body for ClientsUpdate for application/json ContentType.
type ClientsUpdateJSONRequestBody = ClientUpdate
@@ -1273,6 +1345,9 @@ type RewriteAddJSONRequestBody = RewriteEntry
// RewriteDeleteJSONRequestBody defines body for RewriteDelete for application/json ContentType.
type RewriteDeleteJSONRequestBody = RewriteEntry
// RewriteSettingsUpdateJSONRequestBody defines body for RewriteSettingsUpdate for application/json ContentType.
type RewriteSettingsUpdateJSONRequestBody = RewriteSettings
// RewriteUpdateJSONRequestBody defines body for RewriteUpdate for application/json ContentType.
type RewriteUpdateJSONRequestBody = RewriteUpdate
@@ -1493,6 +1568,11 @@ type ClientInterface interface {
// ClientsFind request
ClientsFind(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsSearchWithBody request with any body
ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsUpdateWithBody request with any body
ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -1665,6 +1745,14 @@ type ClientInterface interface {
// RewriteList request
RewriteList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
// RewriteSettingsGet request
RewriteSettingsGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
// RewriteSettingsUpdateWithBody request with any body
RewriteSettingsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
RewriteSettingsUpdate(ctx context.Context, body RewriteSettingsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// RewriteUpdateWithBody request with any body
RewriteUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -1985,6 +2073,30 @@ func (c *AdguardHomeClient) ClientsFind(ctx context.Context, params *ClientsFind
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsUpdateRequestWithBody(c.Server, contentType, body)
if err != nil {
@@ -2765,6 +2877,42 @@ func (c *AdguardHomeClient) RewriteList(ctx context.Context, reqEditors ...Reque
return c.Client.Do(req)
}
func (c *AdguardHomeClient) RewriteSettingsGet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRewriteSettingsGetRequest(c.Server)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) RewriteSettingsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRewriteSettingsUpdateRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) RewriteSettingsUpdate(ctx context.Context, body RewriteSettingsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRewriteSettingsUpdateRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) RewriteUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewRewriteUpdateRequestWithBody(c.Server, contentType, body)
if err != nil {
@@ -3673,6 +3821,46 @@ func NewClientsFindRequest(server string, params *ClientsFindParams) (*http.Requ
return req, nil
}
// NewClientsSearchRequest calls the generic ClientsSearch builder with application/json body
func NewClientsSearchRequest(server string, body ClientsSearchJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewClientsSearchRequestWithBody(server, "application/json", bodyReader)
}
// NewClientsSearchRequestWithBody generates requests for ClientsSearch with any type of body
func NewClientsSearchRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/clients/search")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewClientsUpdateRequest calls the generic ClientsUpdate builder with application/json body
func NewClientsUpdateRequest(server string, body ClientsUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
@@ -4150,9 +4338,37 @@ func NewFilteringCheckHostRequest(server string, params *FilteringCheckHostParam
if params != nil {
queryValues := queryURL.Query()
if params.Name != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, params.Name); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "name", runtime.ParamLocationQuery, *params.Name); err != nil {
if params.Client != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "client", runtime.ParamLocationQuery, *params.Client); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
} else {
for k, v := range parsed {
for _, v2 := range v {
queryValues.Add(k, v2)
}
}
}
}
if params.Qtype != nil {
if queryFrag, err := runtime.StyleParamWithLocation("form", true, "qtype", runtime.ParamLocationQuery, *params.Qtype); err != nil {
return nil, err
} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
return nil, err
@@ -5214,6 +5430,73 @@ func NewRewriteListRequest(server string) (*http.Request, error) {
return req, nil
}
// NewRewriteSettingsGetRequest generates requests for RewriteSettingsGet
func NewRewriteSettingsGetRequest(server string) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/rewrite/settings")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
// NewRewriteSettingsUpdateRequest calls the generic RewriteSettingsUpdate builder with application/json body
func NewRewriteSettingsUpdateRequest(server string, body RewriteSettingsUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewRewriteSettingsUpdateRequestWithBody(server, "application/json", bodyReader)
}
// NewRewriteSettingsUpdateRequestWithBody generates requests for RewriteSettingsUpdate with any type of body
func NewRewriteSettingsUpdateRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/rewrite/settings/update")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("PUT", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewRewriteUpdateRequest calls the generic RewriteUpdate builder with application/json body
func NewRewriteUpdateRequest(server string, body RewriteUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
@@ -5983,6 +6266,11 @@ type ClientWithResponsesInterface interface {
// ClientsFindWithResponse request
ClientsFindWithResponse(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*ClientsFindResp, error)
// ClientsSearchWithBodyWithResponse request with any body
ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
// ClientsUpdateWithBodyWithResponse request with any body
ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error)
@@ -6155,6 +6443,14 @@ type ClientWithResponsesInterface interface {
// RewriteListWithResponse request
RewriteListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RewriteListResp, error)
// RewriteSettingsGetWithResponse request
RewriteSettingsGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RewriteSettingsGetResp, error)
// RewriteSettingsUpdateWithBodyWithResponse request with any body
RewriteSettingsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RewriteSettingsUpdateResp, error)
RewriteSettingsUpdateWithResponse(ctx context.Context, body RewriteSettingsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*RewriteSettingsUpdateResp, error)
// RewriteUpdateWithBodyWithResponse request with any body
RewriteUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RewriteUpdateResp, error)
@@ -6559,6 +6855,28 @@ func (r ClientsFindResp) StatusCode() int {
return 0
}
type ClientsSearchResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ClientsFindResponse
}
// Status returns HTTPResponse.Status
func (r ClientsSearchResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r ClientsSearchResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type ClientsUpdateResp struct {
Body []byte
HTTPResponse *http.Response
@@ -7469,6 +7787,49 @@ func (r RewriteListResp) StatusCode() int {
return 0
}
type RewriteSettingsGetResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *RewriteSettings
}
// Status returns HTTPResponse.Status
func (r RewriteSettingsGetResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r RewriteSettingsGetResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type RewriteSettingsUpdateResp struct {
Body []byte
HTTPResponse *http.Response
}
// Status returns HTTPResponse.Status
func (r RewriteSettingsUpdateResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r RewriteSettingsUpdateResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type RewriteUpdateResp struct {
Body []byte
HTTPResponse *http.Response
@@ -8098,6 +8459,23 @@ func (c *ClientWithResponses) ClientsFindWithResponse(ctx context.Context, param
return ParseClientsFindResp(rsp)
}
// ClientsSearchWithBodyWithResponse request with arbitrary body returning *ClientsSearchResp
func (c *ClientWithResponses) ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearchWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
func (c *ClientWithResponses) ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearch(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
// ClientsUpdateWithBodyWithResponse request with arbitrary body returning *ClientsUpdateResp
func (c *ClientWithResponses) ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error) {
rsp, err := c.ClientsUpdateWithBody(ctx, contentType, body, reqEditors...)
@@ -8660,6 +9038,32 @@ func (c *ClientWithResponses) RewriteListWithResponse(ctx context.Context, reqEd
return ParseRewriteListResp(rsp)
}
// RewriteSettingsGetWithResponse request returning *RewriteSettingsGetResp
func (c *ClientWithResponses) RewriteSettingsGetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*RewriteSettingsGetResp, error) {
rsp, err := c.RewriteSettingsGet(ctx, reqEditors...)
if err != nil {
return nil, err
}
return ParseRewriteSettingsGetResp(rsp)
}
// RewriteSettingsUpdateWithBodyWithResponse request with arbitrary body returning *RewriteSettingsUpdateResp
func (c *ClientWithResponses) RewriteSettingsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RewriteSettingsUpdateResp, error) {
rsp, err := c.RewriteSettingsUpdateWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRewriteSettingsUpdateResp(rsp)
}
func (c *ClientWithResponses) RewriteSettingsUpdateWithResponse(ctx context.Context, body RewriteSettingsUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*RewriteSettingsUpdateResp, error) {
rsp, err := c.RewriteSettingsUpdate(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseRewriteSettingsUpdateResp(rsp)
}
// RewriteUpdateWithBodyWithResponse request with arbitrary body returning *RewriteUpdateResp
func (c *ClientWithResponses) RewriteUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RewriteUpdateResp, error) {
rsp, err := c.RewriteUpdateWithBody(ctx, contentType, body, reqEditors...)
@@ -9243,6 +9647,32 @@ func ParseClientsFindResp(rsp *http.Response) (*ClientsFindResp, error) {
return response, nil
}
// ParseClientsSearchResp parses an HTTP response from a ClientsSearchWithResponse call
func ParseClientsSearchResp(rsp *http.Response) (*ClientsSearchResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &ClientsSearchResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest ClientsFindResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
}
return response, nil
}
// ParseClientsUpdateResp parses an HTTP response from a ClientsUpdateWithResponse call
func ParseClientsUpdateResp(rsp *http.Response) (*ClientsUpdateResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
@@ -10159,6 +10589,48 @@ func ParseRewriteListResp(rsp *http.Response) (*RewriteListResp, error) {
return response, nil
}
// ParseRewriteSettingsGetResp parses an HTTP response from a RewriteSettingsGetWithResponse call
func ParseRewriteSettingsGetResp(rsp *http.Response) (*RewriteSettingsGetResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &RewriteSettingsGetResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest RewriteSettings
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
}
return response, nil
}
// ParseRewriteSettingsUpdateResp parses an HTTP response from a RewriteSettingsUpdateWithResponse call
func ParseRewriteSettingsUpdateResp(rsp *http.Response) (*RewriteSettingsUpdateResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &RewriteSettingsUpdateResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
return response, nil
}
// ParseRewriteUpdateResp parses an HTTP response from a RewriteUpdateWithResponse call
func ParseRewriteUpdateResp(rsp *http.Response) (*RewriteUpdateResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)

View File

@@ -1,9 +1,12 @@
package model
import (
"github.com/bakito/adguardhome-sync/pkg/utils"
. "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var _ = Describe("Types", func() {
@@ -71,4 +74,31 @@ var _ = Describe("Types", func() {
Entry(`When SubnetMask is ""`, DhcpConfigV6{RangeStart: utils.Ptr("")}),
)
})
Context("DNSConfig", func() {
var (
cfg *DNSConfig
l *zap.SugaredLogger
)
BeforeEach(func() {
cfg = &DNSConfig{
UsePrivatePtrResolvers: utils.Ptr(true),
}
l = log.GetLogger("test")
})
Context("Sanitize", func() {
It("should disable UsePrivatePtrResolvers resolvers is nil ", func() {
cfg.LocalPtrUpstreams = nil
cfg.Sanitize(l)
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
})
It("should disable UsePrivatePtrResolvers resolvers is empty ", func() {
cfg.LocalPtrUpstreams = utils.Ptr([]string{})
cfg.Sanitize(l)
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
})
})
})
})

View File

@@ -4,12 +4,13 @@ import (
"encoding/json"
"os"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var _ = Describe("Types", func() {
@@ -115,12 +116,12 @@ var _ = Describe("Types", func() {
Context("QueryLogConfig", func() {
Context("Equal", func() {
var (
a *model.QueryLogConfig
b *model.QueryLogConfig
a *model.QueryLogConfigWithIgnored
b *model.QueryLogConfigWithIgnored
)
BeforeEach(func() {
a = &model.QueryLogConfig{}
b = &model.QueryLogConfig{}
a = &model.QueryLogConfigWithIgnored{}
b = &model.QueryLogConfigWithIgnored{}
})
It("should be equal", func() {
a.Enabled = utils.Ptr(true)
@@ -242,7 +243,10 @@ var _ = Describe("Types", func() {
})
It("should return 3 one replicas if urls are different", func() {
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1", APIPath: apiPath}, {URL: url, APIPath: apiPath + "1"}}
cfg.Replicas = []types.AdGuardInstance{
{URL: url + "1", APIPath: apiPath},
{URL: url, APIPath: apiPath + "1"},
}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(3))
})
@@ -303,6 +307,29 @@ var _ = Describe("Types", func() {
})
})
})
Context("Client", func() {
Context("Equals", func() {
var (
cl1 *model.Client
cl2 *model.Client
)
BeforeEach(func() {
cl1 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("UTC")},
}
cl2 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("Local")},
}
})
It("should equal if only timezone differs on empty blocked service schedule", func() {
Ω(cl1.Equals(cl2)).Should(BeTrue())
})
})
})
Context("BlockedServices", func() {
Context("Equals", func() {
It("should be equal", func() {

View File

@@ -0,0 +1,181 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"Instance": {
"additionalProperties": false,
"properties": {
"apiPath": {
"type": "string"
},
"autoSetup": {
"type": "boolean"
},
"cookie": {
"type": "string"
},
"dhcpServerEnabled": {
"type": "boolean"
},
"insecureSkipVerify": {
"type": "boolean"
},
"interfaceName": {
"type": "string"
},
"password": {
"type": "string"
},
"url": {
"format": "uri",
"type": "string"
},
"username": {
"type": "string"
},
"webURL": {
"format": "uri",
"type": "string"
},
"requestHeaders": {
"type": "object"
}
},
"type": "object"
}
},
"description": "validates only for valid schema. No required fields, as the can be defined via env ars afterwards.",
"properties": {
"api": {
"additionalProperties": false,
"properties": {
"darkMode": {
"type": "boolean"
},
"metrics": {
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"queryLogLimit": {
"type": "integer"
},
"scrapeInterval": {
"type": "string"
}
},
"type": "object"
},
"password": {
"type": "string"
},
"port": {
"type": "number"
},
"tls": {
"additionalProperties": false,
"properties": {
"certDir": {
"type": "string"
},
"certName": {
"type": "string"
},
"keyName": {
"type": "string"
}
},
"type": "object"
},
"username": {
"type": "string"
}
},
"type": "object"
},
"continueOnError": {
"type": "boolean"
},
"cron": {
"type": "string"
},
"features": {
"additionalProperties": false,
"properties": {
"clientSettings": {
"type": "boolean"
},
"dhcp": {
"additionalProperties": false,
"properties": {
"serverConfig": {
"type": "boolean"
},
"staticLeases": {
"type": "boolean"
}
},
"type": "object"
},
"dns": {
"additionalProperties": false,
"properties": {
"accessLists": {
"type": "boolean"
},
"rewrites": {
"type": "boolean"
},
"serverConfig": {
"type": "boolean"
}
},
"type": "object"
},
"filters": {
"type": "boolean"
},
"generalSettings": {
"type": "boolean"
},
"queryLogConfig": {
"type": "boolean"
},
"services": {
"type": "boolean"
},
"statsConfig": {
"type": "boolean"
},
"theme": {
"type": "boolean"
},
"tlsConfig": {
"type": "boolean"
}
},
"type": "object"
},
"origin": {
"$ref": "#/definitions/Instance"
},
"printConfigOnly": {
"type": "boolean"
},
"replica": {
"$ref": "#/definitions/Instance"
},
"replicas": {
"items": {
"$ref": "#/definitions/Instance"
},
"type": "array"
},
"runOnStart": {
"type": "boolean"
}
},
"title": "adguardhome-sync Configuration",
"type": "object"
}

View File

@@ -4,9 +4,10 @@ import (
"errors"
"regexp"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/caarlos0/env/v10"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
)
var (
@@ -14,16 +15,39 @@ var (
logger = log.GetLogger("config")
)
func Get(configFile string, flags Flags) (*types.Config, error) {
type AppConfig struct {
cfg *types.Config
filePath string
content string
}
func (ac *AppConfig) PrintConfigOnly() bool {
return ac.cfg.PrintConfigOnly
}
func (ac *AppConfig) Get() *types.Config {
return ac.cfg
}
func (ac *AppConfig) Init() error {
return ac.cfg.Init()
}
func Get(configFile string, flags Flags) (*AppConfig, error) {
path, err := configFilePath(configFile)
if err != nil {
return nil, err
}
if err := validateSchema(path); err != nil {
return nil, err
}
cfg := initialConfig()
// read yaml config
if err := readFile(cfg, path); err != nil {
var content string
if content, err = readFile(cfg, path); err != nil {
return nil, err
}
@@ -39,22 +63,34 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
cfg.Replica.DHCPServerEnabled = nil
// ignore origin and replicas form env parsing as they are handled separately
replicas := cfg.Replicas
cfg.Replicas = nil
replica := cfg.Replica
cfg.Replica = nil
origin := cfg.Origin
cfg.Origin = nil
// overwrite from env vars
if err := env.Parse(cfg); err != nil {
return nil, err
}
if err := env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil {
if err := env.ParseWithOptions(origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
return nil, err
}
if err := env.ParseWithOptions(replica, env.Options{Prefix: "REPLICA_"}); err != nil {
return nil, err
}
// restore origin and replica
cfg.Origin = origin
cfg.Replica = replica
cfg.Replicas = replicas
// if not set from env, use previous value
if cfg.Replica.DHCPServerEnabled == nil {
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
}
if err := env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
return nil, err
}
if cfg.Replica != nil &&
cfg.Replica.URL == "" &&
cfg.Replica.Username == "" {
@@ -66,8 +102,6 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
"Do not use single replica and numbered (list) replica config combined")
}
handleDeprecatedEnvVars(cfg)
if cfg.Replica != nil {
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
cfg.Replica = nil
@@ -75,13 +109,13 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
return cfg, err
return &AppConfig{cfg: cfg, filePath: path, content: content}, err
}
func initialConfig() *types.Config {
return &types.Config{
RunOnStart: true,
Origin: types.AdGuardInstance{
Origin: &types.AdGuardInstance{
APIPath: "/control",
},
Replica: &types.AdGuardInstance{

View File

@@ -5,9 +5,11 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
)
func TestCmd(t *testing.T) {
format.TruncatedDiff = false
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}

View File

@@ -3,11 +3,12 @@ package config_test
import (
"os"
"github.com/bakito/adguardhome-sync/pkg/config"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
"github.com/bakito/adguardhome-sync/internal/config"
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
)
var _ = Describe("Config", func() {
@@ -16,7 +17,7 @@ var _ = Describe("Config", func() {
flags *flagsmock.MockFlags
mockCtrl *gm.Controller
changedEnvVars []string
setEnv = func(name string, value string) {
setEnv = func(name, value string) {
_ = os.Setenv(name, value)
changedEnvVars = append(changedEnvVars, name)
}
@@ -43,13 +44,25 @@ var _ = Describe("Config", func() {
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
})
})
Context("Env Var Clash", func() {
It("should not use USERNAME env variable if it is defined (#570)", func() {
incorrect := "ThisIsNotTheCorrectUsername"
setEnv("USERNAME", incorrect)
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
c, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(c.Get().Origin.Username).ShouldNot(Equal(incorrect))
Ω(c.Get().Replicas[0].Username).ShouldNot(Equal(incorrect))
})
})
Context("Origin Url", func() {
It("should have the origin URL from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
})
It("should have the origin URL from the config flags", func() {
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
@@ -58,7 +71,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
})
It("should have the origin URL from the config env var", func() {
setEnv("ORIGIN_URL", "https://origin-env:443")
@@ -68,7 +81,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
})
})
Context("Replica insecure skip verify", func() {
@@ -77,7 +90,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config flags", func() {
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
@@ -86,7 +99,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
@@ -96,7 +109,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
})
@@ -106,7 +119,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
@@ -114,7 +127,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
})
})
Context("API Port", func() {
@@ -122,26 +135,26 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9090))
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9990))
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9999))
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
@@ -151,8 +164,8 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
@@ -162,8 +175,8 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
Context("API Port", func() {
@@ -171,66 +184,75 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9090))
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9990))
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9999))
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
//////
Context("Feature DNS Server Config", func() {
It("should have the feature dns server config from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
It("should have the feature dns server config from the config flags", func() {
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
})
It("should have the feature dns server config from the config env var", func() {
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
It("should have the feature dns server config from the config DEPRECATED env var", func() {
setEnv("FEATURES_DNS_SERVERCONFIG", "false")
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
})
Context("Headers", func() {
It("have headers from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(2))
Ω(cfg.Get().Replicas[0].RequestHeaders["FOO"]).Should(Equal("bar"))
Ω(cfg.Get().Replicas[0].RequestHeaders["Client-ID"]).Should(Equal("xxxx"))
})
It("have headers from the config file will be replaced when defined as ENV", func() {
setEnv("REPLICA1_REQUEST_HEADERS", "AAA:bbb")
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(1))
Ω(cfg.Get().Replicas[0].RequestHeaders["AAA"]).Should(Equal("bbb"))
})
})
})

57
internal/config/env.go Normal file
View File

@@ -0,0 +1,57 @@
package config
import (
"fmt"
"os"
"strconv"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/internal/types"
)
// Manually collect replicas from env.
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
id, _ := strconv.Atoi(sm[1])
if id <= 0 {
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
}
if id > len(initialReplicas) {
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
} else {
re := initialReplicas[id-1]
re.URL = sm[2]
replicas = append(replicas, re)
}
}
}
if len(replicas) == 0 {
replicas = initialReplicas
}
for i := range replicas {
reID := i + 1
// keep the previously set value
replicaDhcpServer := replicas[i].DHCPServerEnabled
replicas[i].DHCPServerEnabled = nil
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
return nil, err
}
if replicas[i].DHCPServerEnabled == nil {
replicas[i].DHCPServerEnabled = replicaDhcpServer
}
if replicas[i].APIPath == "" {
replicas[i].APIPath = "/control"
}
}
return replicas, nil
}

View File

@@ -4,21 +4,24 @@ import (
"os"
"path/filepath"
"github.com/bakito/adguardhome-sync/pkg/types"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/types"
)
func readFile(cfg *types.Config, path string) error {
func readFile(cfg *types.Config, path string) (string, error) {
var content string
if _, err := os.Stat(path); err == nil {
b, err := os.ReadFile(path)
if err != nil {
return err
return "", err
}
content = string(b)
if err := yaml.Unmarshal(b, cfg); err != nil {
return err
return "", err
}
}
return nil
return content, nil
}
func configFilePath(configFile string) (string, error) {

View File

@@ -10,9 +10,6 @@ import (
)
var _ = Describe("Config", func() {
var ()
BeforeEach(func() {
})
Context("configFilePath", func() {
It("should return the same value", func() {
path := uuid.NewString()
@@ -22,11 +19,12 @@ var _ = Describe("Config", func() {
Ω(result).Should(Equal(path))
})
It("should the file in HOME dir", func() {
home := os.Getenv("HOME")
home, err := os.UserHomeDir()
Ω(err).ShouldNot(HaveOccurred())
result, err := configFilePath("")
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal(filepath.Join(home, "/.adguardhome-sync.yaml")))
Ω(result).Should(Equal(filepath.Join(home, ".adguardhome-sync.yaml")))
})
})
})

View File

@@ -6,26 +6,27 @@ const (
FlagPrintConfigOnly = "printConfigOnly"
FlagContinueOnError = "continueOnError"
FlagApiPort = "api-port"
FlagApiUsername = "api-username"
FlagApiPassword = "api-password"
FlagApiDarkMode = "api-dark-mode"
FlagAPIPort = "api-port"
FlagAPIUsername = "api-username"
FlagAPIPassword = "api-password"
FlagAPIDarkMode = "api-dark-mode"
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
FlagFeatureDnsServerConfig = "feature-dns-server-config"
FlagFeatureDnsAccessLists = "feature-dns-access-lists"
FlagFeatureDnsRewrites = "feature-dns-rewrites"
FlagFeatureDNSServerConfig = "feature-dns-server-config"
FlagFeatureDNSAccessLists = "feature-dns-access-lists"
FlagFeatureDNSRewrites = "feature-dns-rewrites"
FlagFeatureGeneral = "feature-general-settings"
FlagFeatureQueryLog = "feature-query-log-config"
FlagFeatureStats = "feature-stats-config"
FlagFeatureClient = "feature-client-settings"
FlagFeatureServices = "feature-services"
FlagFeatureFilters = "feature-filters"
FlagFeatureTLSConfig = "feature-tls-config"
FlagOriginURL = "origin-url"
FlagOriginWebURL = "origin-web-url"
FlagOriginApiPath = "origin-api-path"
FlagOriginAPIPath = "origin-api-path"
FlagOriginUsername = "origin-username"
FlagOriginPassword = "origin-password"
@@ -34,7 +35,7 @@ const (
FlagReplicaURL = "replica-url"
FlagReplicaWebURL = "replica-web-url"
FlagReplicaApiPath = "replica-api-path"
FlagReplicaAPIPath = "replica-api-path"
FlagReplicaUsername = "replica-username"
FlagReplicaPassword = "replica-password"
FlagReplicaCookie = "replica-cookie"

View File

@@ -1,7 +1,7 @@
package config
import (
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/types"
)
func readFlags(cfg *types.Config, flags Flags) error {
@@ -18,7 +18,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
return err
}
if err := fr.readApiFlags(); err != nil {
if err := fr.readAPIFlags(); err != nil {
return err
}
@@ -30,11 +30,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
return err
}
if err := fr.readReplicaFlags(); err != nil {
return err
}
return nil
return fr.readReplicaFlags()
}
type flagReader struct {
@@ -53,7 +49,7 @@ func (fr *flagReader) readReplicaFlags() error {
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) {
if err := fr.setStringFlag(FlagReplicaAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Replica.APIPath = value
}); err != nil {
return err
@@ -83,12 +79,9 @@ func (fr *flagReader) readReplicaFlags() error {
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
return fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
fr.cfg.Replica.InterfaceName = value
}); err != nil {
return err
}
return nil
})
}
func (fr *flagReader) readOriginFlags() error {
@@ -102,7 +95,7 @@ func (fr *flagReader) readOriginFlags() error {
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) {
if err := fr.setStringFlag(FlagOriginAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Origin.APIPath = value
}); err != nil {
return err
@@ -122,12 +115,9 @@ func (fr *flagReader) readOriginFlags() error {
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
return fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
fr.cfg.Origin.InsecureSkipVerify = value
}); err != nil {
return err
}
return nil
})
}
func (fr *flagReader) readFeatureFlags() error {
@@ -142,17 +132,17 @@ func (fr *flagReader) readFeatureFlags() error {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagFeatureDNSServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.ServerConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagFeatureDNSAccessLists, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.AccessLists = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagFeatureDNSRewrites, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.Rewrites = value
}); err != nil {
return err
@@ -188,56 +178,51 @@ func (fr *flagReader) readFeatureFlags() error {
}); err != nil {
return err
}
return nil
return fr.setBoolFlag(FlagFeatureTLSConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.TLSConfig = value
})
}
func (fr *flagReader) readApiFlags() (err error) {
if err = fr.setIntFlag(FlagApiPort, func(cgf *types.Config, value int) {
func (fr *flagReader) readAPIFlags() error {
if err := fr.setIntFlag(FlagAPIPort, func(cgf *types.Config, value int) {
fr.cfg.API.Port = value
}); err != nil {
return
return err
}
if err = fr.setStringFlag(FlagApiUsername, func(cgf *types.Config, value string) {
if err := fr.setStringFlag(FlagAPIUsername, func(cgf *types.Config, value string) {
fr.cfg.API.Username = value
}); err != nil {
return
return err
}
if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) {
if err := fr.setStringFlag(FlagAPIPassword, func(cgf *types.Config, value string) {
fr.cfg.API.Password = value
}); err != nil {
return
return err
}
if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) {
return fr.setBoolFlag(FlagAPIDarkMode, func(cgf *types.Config, value bool) {
fr.cfg.API.DarkMode = value
}); err != nil {
return
}
return
})
}
func (fr *flagReader) readRootFlags() (err error) {
if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
func (fr *flagReader) readRootFlags() error {
if err := fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
fr.cfg.Cron = value
}); err != nil {
return
return err
}
if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
fr.cfg.RunOnStart = value
}); err != nil {
return
return err
}
if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
fr.cfg.PrintConfigOnly = value
}); err != nil {
return
return err
}
if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
return fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
fr.cfg.ContinueOnError = value
}); err != nil {
return
}
return
})
}
type Flags interface {
@@ -249,33 +234,33 @@ type Flags interface {
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetString(name); err != nil {
var value string
if value, err = fr.flags.GetString(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
cb(fr.cfg, value)
}
return nil
}
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetBool(name); err != nil {
var value bool
if value, err = fr.flags.GetBool(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
cb(fr.cfg, value)
}
return nil
}
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetInt(name); err != nil {
var value int
if value, err = fr.flags.GetInt(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
cb(fr.cfg, value)
}
return nil
}

View File

@@ -3,11 +3,12 @@ package config
import (
"strings"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
"github.com/bakito/adguardhome-sync/internal/types"
)
var _ = Describe("Config", func() {
@@ -18,6 +19,7 @@ var _ = Describe("Config", func() {
)
BeforeEach(func() {
cfg = &types.Config{
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{},
Features: types.Features{
DNS: types.DNS{
@@ -88,7 +90,7 @@ var _ = Describe("Config", func() {
}))
})
})
Context("readApiFlags", func() {
Context("readAPIFlags", func() {
It("should change all values", func() {
cfg.API = types.API{
Port: 1111,
@@ -99,10 +101,10 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
return strings.HasPrefix(name, "api")
}).AnyTimes()
flags.EXPECT().GetInt(FlagApiPort).Return(9999, nil)
flags.EXPECT().GetString(FlagApiUsername).Return("aaaa", nil)
flags.EXPECT().GetString(FlagApiPassword).Return("bbbb", nil)
flags.EXPECT().GetBool(FlagApiDarkMode).Return(true, nil)
flags.EXPECT().GetInt(FlagAPIPort).Return(9999, nil)
flags.EXPECT().GetString(FlagAPIUsername).Return("aaaa", nil)
flags.EXPECT().GetString(FlagAPIPassword).Return("bbbb", nil)
flags.EXPECT().GetBool(FlagAPIDarkMode).Return(true, nil)
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
@@ -142,7 +144,7 @@ var _ = Describe("Config", func() {
})
Context("readOriginFlags", func() {
It("should change all values", func() {
cfg.Origin = types.AdGuardInstance{
cfg.Origin = &types.AdGuardInstance{
URL: "1",
WebURL: "2",
APIPath: "3",
@@ -154,7 +156,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(FlagOriginURL).Return(true)
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
flags.EXPECT().Changed(FlagOriginApiPath).Return(true)
flags.EXPECT().Changed(FlagOriginAPIPath).Return(true)
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
@@ -163,7 +165,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagOriginApiPath).Return("c", nil)
flags.EXPECT().GetString(FlagOriginAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
@@ -171,7 +173,7 @@ var _ = Describe("Config", func() {
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin).Should(Equal(types.AdGuardInstance{
Ω(cfg.Origin).Should(Equal(&types.AdGuardInstance{
URL: "a",
WebURL: "b",
APIPath: "c",
@@ -198,7 +200,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
flags.EXPECT().Changed(FlagReplicaApiPath).Return(true)
flags.EXPECT().Changed(FlagReplicaAPIPath).Return(true)
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
@@ -209,7 +211,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagReplicaApiPath).Return("c", nil)
flags.EXPECT().GetString(FlagReplicaAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)

View File

@@ -0,0 +1,89 @@
package config
import (
"bytes"
_ "embed"
"os"
"runtime"
"sort"
"strings"
"text/template"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/version"
)
//go:embed print-config.md
var printConfigTemplate string
func (ac *AppConfig) Print() error {
originVersion := aghVersion(*ac.cfg.Origin)
var replicaVersions []string
for _, replica := range ac.cfg.Replicas {
replicaVersions = append(replicaVersions, aghVersion(replica))
}
out, err := ac.printInternal(os.Environ(), originVersion, replicaVersions)
if err != nil {
return err
}
logger.Infof(
"Printing adguardhome-sync aggregated config (THE APPLICATION WILL NOT START IN THIS MODE):\n%s",
out,
)
return nil
}
func aghVersion(i types.AdGuardInstance) string {
cl, err := client.New(i)
if err != nil {
return "N/A"
}
stats, err := cl.Status()
if err != nil {
return "N/A"
}
return stats.Version
}
func (ac *AppConfig) printInternal(env []string, originVersion string, replicaVersions []string) (string, error) {
config, err := yaml.Marshal(ac.Get())
if err != nil {
return "", err
}
funcMap := template.FuncMap{
// The name "inc" is what the function will be called in the template text.
"inc": func(i int) int {
return i + 1
},
}
t, err := template.New("printConfigTemplate").Funcs(funcMap).Parse(printConfigTemplate)
if err != nil {
return "", err
}
sort.Strings(env)
var buf bytes.Buffer
err = t.Execute(&buf, map[string]any{
"Version": version.Version,
"Build": version.Build,
"OperatingSystem": runtime.GOOS,
"Architecture": runtime.GOARCH,
"AggregatedConfig": string(config),
"ConfigFilePath": ac.filePath,
"ConfigFileContent": ac.content,
"EnvironmentVariables": strings.Join(env, "\n"),
"OriginVersion": originVersion,
"ReplicaVersions": replicaVersions,
})
return buf.String(), err
}

View File

@@ -0,0 +1,36 @@
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
### Runtime
AdguardHome-Sync Version: {{ .Version }}
Build: {{ .Build }}
OperatingSystem: {{ .OperatingSystem }}
Architecture: {{ .Architecture }}
OriginVersion: {{ .OriginVersion }}
ReplicaVersions:
{{- range $i,$rep := .ReplicaVersions }}
- Replica {{ inc $i }}: {{ $rep }}
{{- end }}
### AdGuardHome sync aggregated config
```yaml
{{ .AggregatedConfig }}
```
{{- if .ConfigFilePath }}
### AdGuardHome sync unmodified config file
Config file path: {{ .ConfigFilePath }}
```yaml
{{ .ConfigFileContent }}
```
{{- end }}
### Environment Variables
```ini
{{ .EnvironmentVariables }}
```
<!-- END OF GITHUB ISSUE CONTENT -->

View File

@@ -0,0 +1,60 @@
package config
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"runtime"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/internal/test/matchers"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/version"
)
var _ = Describe("AppConfig", func() {
var (
ac *AppConfig
env []string
)
BeforeEach(func() {
ac = &AppConfig{
cfg: &types.Config{
Origin: &types.AdGuardInstance{
URL: "https://ha.xxxx.net:3000",
},
},
content: `
origin:
url: https://ha.xxxx.net:3000
`,
}
env = []string{"FOO=foo", "BAR=bar"}
})
Context("printInternal", func() {
It("should printInternal config without file", func() {
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
Ω(err).ShouldNot(HaveOccurred())
Ω(out).
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(1), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
})
It("should printInternal config with file", func() {
ac.filePath = "config.yaml"
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
Ω(err).ShouldNot(HaveOccurred())
Ω(out).
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
})
})
})
func expected(id int) string {
b, err := os.ReadFile(
filepath.Join("..", "..", "testdata", "config", fmt.Sprintf("print-config_test_expected%d.md", id)),
)
Ω(err).ShouldNot(HaveOccurred())
return string(b)
}

View File

@@ -0,0 +1,59 @@
package config
import (
_ "embed"
"fmt"
"os"
"strings"
"github.com/santhosh-tekuri/jsonschema/v6"
"gopkg.in/yaml.v3"
)
const schemaURL = "config-schema.json"
//go:embed config-schema.json
var schemaData string
func validateSchema(cfgFile string) error {
// ignore if file not exists
if _, err := os.Stat(cfgFile); err != nil {
// Config file does not exist or is not readable - ignore it
//nolint:nilerr
return nil
}
// Load YAML file
yamlContent, err := os.ReadFile(cfgFile)
if err != nil {
return fmt.Errorf("config file %q is invalid: %w", cfgFile, err)
}
return validateYAML(yamlContent)
}
func validateYAML(yamlContent []byte) error {
if yamlContent == nil || strings.TrimSpace(string(yamlContent)) == "" {
return nil
}
// Convert YAML to JSON
var yamlData any
err := yaml.Unmarshal(yamlContent, &yamlData)
if err != nil {
return err
}
// Load JSON schema
sch, err := jsonschema.UnmarshalJSON(strings.NewReader(schemaData))
if err != nil {
return err
}
c := jsonschema.NewCompiler()
if err := c.AddResource(schemaURL, sch); err != nil {
return err
}
schema := c.MustCompile(schemaURL)
// validateSchema
return schema.Validate(yamlData)
}

View File

@@ -0,0 +1,45 @@
package config
import (
"github.com/go-faker/faker/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/types"
)
var _ = Describe("Config", func() {
Context("validateSchema", func() {
DescribeTable("validateSchema config",
func(configFile string, expectFail bool) {
err := validateSchema(configFile)
if expectFail {
Ω(err).Should(HaveOccurred())
} else {
Ω(err).ShouldNot(HaveOccurred())
}
},
Entry(`Should be valid`, "../../testdata/config/config-valid.yaml", false),
Entry(`Should be valid if file doesn't exist`, "../../testdata/config/foo.bar", false),
Entry(`Should fail if file is not yaml`, "../../go.mod", true),
)
It("validate config with all fields randomly populated", func() {
cfg := &types.Config{}
err := faker.FakeData(cfg)
Ω(err).ShouldNot(HaveOccurred())
data, err := yaml.Marshal(&cfg)
Ω(err).ShouldNot(HaveOccurred())
err = validateYAML(data)
Ω(err).ShouldNot(HaveOccurred())
})
It("validate config with empty file", func() {
var data []byte
err := validateYAML(data)
Ω(err).ShouldNot(HaveOccurred())
})
})
})

View File

@@ -18,7 +18,7 @@ var (
logs []string
)
// GetLogger returns a named logger
// GetLogger returns a named logger.
func GetLogger(name string) *zap.SugaredLogger {
return rootLogger.Named(name).Sugar()
}
@@ -101,11 +101,16 @@ func (l *logList) Sync() error {
return nil
}
// Logs get the current logs
// Logs get the current logs.
func Logs() []string {
return logs
}
// Clear the current logs.
func Clear() {
logs = nil
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)

View File

@@ -1,16 +1,18 @@
package metrics
import (
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/constraints"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
)
const StatsTotal = "total"
var (
l = log.GetLogger("metrics")
// avgProcessingTime - Average processing time for a DNS query
// avgProcessingTime - Average processing time for a DNS query.
avgProcessingTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "avg_processing_time",
@@ -20,7 +22,7 @@ var (
[]string{"hostname"},
)
// dnsQueries - Number of DNS queries
// dnsQueries - Number of DNS queries.
dnsQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_dns_queries",
@@ -30,7 +32,7 @@ var (
[]string{"hostname"},
)
// blockedFiltering - Number of DNS queries blocked
// blockedFiltering - Number of DNS queries blocked.
blockedFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_blocked_filtering",
@@ -40,7 +42,7 @@ var (
[]string{"hostname"},
)
// parentalFiltering - Number of DNS queries replaced by parental control
// parentalFiltering - Number of DNS queries replaced by parental control.
parentalFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_parental",
@@ -50,7 +52,7 @@ var (
[]string{"hostname"},
)
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing.
safeBrowsingFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safebrowsing",
@@ -60,7 +62,7 @@ var (
[]string{"hostname"},
)
// safeSearchFiltering - Number of DNS queries replaced by safe search
// safeSearchFiltering - Number of DNS queries replaced by safe search.
safeSearchFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safesearch",
@@ -70,7 +72,7 @@ var (
[]string{"hostname"},
)
// topQueries - The number of top queries
// topQueries - The number of top queries.
topQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_queried_domains",
@@ -80,7 +82,7 @@ var (
[]string{"hostname", "domain"},
)
// topBlocked - The number of top domains blocked
// topBlocked - The number of top domains blocked.
topBlocked = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_blocked_domains",
@@ -90,7 +92,7 @@ var (
[]string{"hostname", "domain"},
)
// topClients - The number of top clients
// topClients - The number of top clients.
topClients = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_clients",
@@ -110,7 +112,7 @@ var (
[]string{"hostname", "type"},
)
// running - If Adguard is running
// running - If Adguard is running.
running = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "running",
@@ -120,7 +122,7 @@ var (
[]string{"hostname"},
)
// protectionEnabled - If Adguard protection is enabled
// protectionEnabled - If Adguard protection is enabled.
protectionEnabled = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "protection_enabled",
@@ -129,6 +131,25 @@ var (
},
[]string{"hostname"},
)
// aghsSyncDuration - the sync curation in seconds.
aghsSyncDuration = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "sync_duration_seconds",
Namespace: "adguard_home_sync",
Help: "This represents the duration of the last sync in seconds",
},
[]string{"hostname"},
)
// aghsSyncSuccessful - the sync result.
aghsSyncSuccessful = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "sync_successful",
Namespace: "adguard_home_sync",
Help: "This represents the whether the last sync was successful",
},
[]string{"hostname"},
)
stats = OverallStats{}
)
// Init initializes all Prometheus metrics made available by AdGuard exporter.
@@ -145,6 +166,8 @@ func Init() {
initMetric("query_types", queryTypes)
initMetric("running", running)
initMetric("protection_enabled", protectionEnabled)
initMetric("sync_duration_seconds", aghsSyncDuration)
initMetric("sync_successful", aghsSyncSuccessful)
}
func initMetric(name string, metric *prometheus.GaugeVec) {
@@ -152,23 +175,33 @@ func initMetric(name string, metric *prometheus.GaugeVec) {
l.With("name", name).Info("New Prometheus metric registered")
}
func Update(ims ...InstanceMetrics) {
for _, im := range ims {
update(im)
func UpdateInstances(iml InstanceMetricsList) {
for _, im := range iml.Metrics {
updateMetrics(im)
stats[im.HostName] = im.Stats
}
l.Debug("updated")
}
func update(im InstanceMetrics) {
func UpdateResult(host string, ok bool, duration float64) {
if ok {
aghsSyncSuccessful.WithLabelValues(host).Set(1)
} else {
aghsSyncSuccessful.WithLabelValues(host).Set(0)
}
aghsSyncDuration.WithLabelValues(host).Set(duration)
}
func updateMetrics(im InstanceMetrics) {
// Status
var isRunning int = 0
isRunning := 0
if im.Status.Running {
isRunning = 1
}
running.WithLabelValues(im.HostName).Set(float64(isRunning))
var isProtected int = 0
isProtected := 0
if im.Status.ProtectionEnabled {
isProtected = 1
}
@@ -214,7 +247,7 @@ func update(im InstanceMetrics) {
if len(dnsanswer) > 0 {
for _, dnsa := range dnsanswer {
dnsType := *dnsa.Type
m[dnsType] += 1
m[dnsType]++
}
}
}
@@ -226,6 +259,10 @@ func update(im InstanceMetrics) {
}
}
type InstanceMetricsList struct {
Metrics []InstanceMetrics `faker:"slice_len=5"`
}
type InstanceMetrics struct {
HostName string
Status *model.ServerStatus
@@ -233,13 +270,28 @@ type InstanceMetrics struct {
QueryLog *model.QueryLog
}
func safeMetric[T Number](v *T) float64 {
type OverallStats map[string]*model.Stats
func (os OverallStats) consolidate() OverallStats {
consolidated := OverallStats{StatsTotal: model.NewStats()}
for host, stats := range os {
consolidated[host] = stats
consolidated[StatsTotal].Add(stats)
}
return consolidated
}
func safeMetric[T int | float64 | float32](v *T) float64 {
if v == nil {
return 0
}
return float64(*v)
}
type Number interface {
constraints.Float | constraints.Integer
func getStats() OverallStats {
return stats.consolidate()
}
func (os OverallStats) Total() *model.Stats {
return os[StatsTotal]
}

View File

@@ -0,0 +1,13 @@
package metrics_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSync(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Metrics Suite")
}

View File

@@ -0,0 +1,102 @@
package metrics
import (
"github.com/go-faker/faker/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
var _ = Describe("Metrics", func() {
BeforeEach(func() {
stats = make(OverallStats)
})
Context("UpdateInstances / getStats", func() {
It("generate correct stats", func() {
UpdateInstances(InstanceMetricsList{[]InstanceMetrics{
{HostName: "foo", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(100),
DnsQueries: ptr.To(
[]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
{HostName: "bar", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(200),
DnsQueries: ptr.To(
[]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
{HostName: "aaa", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(300),
DnsQueries: ptr.To(
[]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
}})
Ω(stats).Should(HaveKey("foo"))
Ω(stats["foo"].NumDnsQueries).Should(Equal(ptr.To(100)))
Ω(stats).Should(HaveKey("bar"))
Ω(stats["bar"].NumDnsQueries).Should(Equal(ptr.To(200)))
Ω(stats).Should(HaveKey("aaa"))
Ω(stats["aaa"].NumDnsQueries).Should(Equal(ptr.To(300)))
os := getStats()
tot := os.Total()
Ω(*tot.NumDnsQueries).Should(Equal(600))
Ω(
*tot.DnsQueries,
).Should(Equal([]int{60, 120, 180, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
foo := os["foo"]
bar := os["bar"]
aaa := os["aaa"]
Ω(*foo.NumDnsQueries).Should(Equal(100))
Ω(*bar.NumDnsQueries).Should(Equal(200))
Ω(*aaa.NumDnsQueries).Should(Equal(300))
Ω(
*foo.DnsQueries,
).Should(Equal([]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
Ω(
*bar.DnsQueries,
).Should(Equal([]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
Ω(
*aaa.DnsQueries,
).Should(Equal([]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
})
})
Context("StatsGraph", func() {
var metrics InstanceMetricsList
BeforeEach(func() {
err := faker.FakeData(&metrics)
Ω(err).ShouldNot(HaveOccurred())
})
It("should provide correct results with faked values", func() {
UpdateInstances(metrics)
_, dns, blocked, malware, adult := StatsGraph()
verifyStats(dns)
verifyStats(blocked)
verifyStats(malware)
verifyStats(adult)
})
})
})
func verifyStats(lines []Line) {
var total Line
sum := make([]int, len(lines[0].Data))
for _, l := range lines {
if l.Title == labelTotal {
total = l
} else {
for i, d := range l.Data {
sum[i] += d
}
}
}
Ω(sum).Should(Equal(total.Data))
}

130
internal/metrics/stats.go Normal file
View File

@@ -0,0 +1,130 @@
package metrics
import (
"slices"
"strings"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
const labelTotal = "Total"
var (
blue = []int{78, 141, 245}
blueAlternatives = [][]int{
{44, 95, 163},
{122, 166, 247},
{30, 61, 92},
{93, 158, 255},
{58, 123, 213},
}
red = []int{255, 94, 94}
redAlternatives = [][]int{
{204, 59, 59},
{255, 127, 127},
{140, 36, 36},
{255, 153, 153},
{255, 66, 66},
}
yellow = []int{232, 198, 78}
yellowAlternatives = [][]int{
{196, 163, 60},
{255, 220, 110},
{140, 114, 36},
{250, 233, 156},
{212, 180, 84},
}
green = []int{110, 224, 122}
greenAlternatives = [][]int{
{68, 160, 80},
{142, 255, 158},
{44, 140, 63},
{163, 255, 192},
{85, 198, 102},
}
)
func StatsGraph() (t *model.Stats, dns, blocked, malware, adult []Line) {
s := getStats()
t = s.Total()
dns = graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
return safeStats(s.DnsQueries)
})
blocked = graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
return safeStats(s.BlockedFiltering)
})
malware = graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
return safeStats(s.ReplacedSafebrowsing)
})
adult = graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
return safeStats(s.ReplacedParental)
})
return t, dns, blocked, malware, adult
}
func safeStats(stats *[]int) []int {
if stats == nil {
return make([]int, 0)
}
return *stats
}
func graphLines(
t *model.Stats,
s OverallStats,
baseColor []int,
altColors [][]int,
dataCB func(s *model.Stats) []int,
) []Line {
g := &graph{
total: Line{
Fill: true,
Title: labelTotal,
Data: dataCB(t),
R: baseColor[0],
G: baseColor[1],
B: baseColor[2],
},
}
var i int
for name, data := range s {
if name != StatsTotal {
g.replicas = append(g.replicas, Line{
Fill: false,
Title: name,
Data: dataCB(data),
R: altColors[i%len(altColors)][0],
G: altColors[i%len(altColors)][1],
B: altColors[i%len(altColors)][2],
})
i++
}
}
lines := []Line{g.total}
slices.SortFunc(g.replicas, func(a, b Line) int {
return strings.Compare(a.Title, b.Title)
})
lines = append(lines, g.replicas...)
return lines
}
type graph struct {
total Line
replicas []Line
}
type Line struct {
Data []int `json:"data"`
R int `json:"r"`
G int `json:"g"`
B int `json:"b"`
Title string `json:"title"`
Fill bool `json:"fill"`
}

View File

@@ -1,9 +1,9 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
// Source: github.com/bakito/adguardhome-sync/internal/client (interfaces: Client)
//
// Generated by this command:
//
// mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
// mockgen -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
//
// Package client is a generated GoMock package.
@@ -12,7 +12,7 @@ package client
import (
reflect "reflect"
model "github.com/bakito/adguardhome-sync/pkg/client/model"
model "github.com/bakito/adguardhome-sync/internal/client/model"
gomock "go.uber.org/mock/gomock"
)
@@ -20,6 +20,7 @@ import (
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
isgomock struct{}
}
// MockClientMockRecorder is the mock recorder for MockClient.
@@ -55,52 +56,52 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
}
// AddClient mocks base method.
func (m *MockClient) AddClient(arg0 *model.Client) error {
func (m *MockClient) AddClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddClient", arg0)
ret := m.ctrl.Call(m, "AddClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) AddClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), client)
}
// AddDHCPStaticLease mocks base method.
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
func (m *MockClient) AddDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0)
ret := m.ctrl.Call(m, "AddDHCPStaticLease", lease)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) AddDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), lease)
}
// AddFilter mocks base method.
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
func (m *MockClient) AddFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1)
ret := m.ctrl.Call(m, "AddFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 any) *gomock.Call {
func (mr *MockClientMockRecorder) AddFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), whitelist, f)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
func (m *MockClient) AddRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range arg0 {
for _, a := range e {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
@@ -109,24 +110,9 @@ func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
}
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...any) *gomock.Call {
func (mr *MockClientMockRecorder) AddRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
}
// BlockedServices mocks base method.
func (m *MockClient) BlockedServices() (*[]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockedServices")
ret0, _ := ret[0].(*[]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockedServices indicates an expected call of BlockedServices.
func (mr *MockClientMockRecorder) BlockedServices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServices", reflect.TypeOf((*MockClient)(nil).BlockedServices))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), e...)
}
// BlockedServicesSchedule mocks base method.
@@ -175,52 +161,52 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
}
// DeleteClient mocks base method.
func (m *MockClient) DeleteClient(arg0 *model.Client) error {
func (m *MockClient) DeleteClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteClient", arg0)
ret := m.ctrl.Call(m, "DeleteClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), client)
}
// DeleteDHCPStaticLease mocks base method.
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
func (m *MockClient) DeleteDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0)
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", lease)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), lease)
}
// DeleteFilter mocks base method.
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
func (m *MockClient) DeleteFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1)
ret := m.ctrl.Call(m, "DeleteFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 any) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), whitelist, f)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
func (m *MockClient) DeleteRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range arg0 {
for _, a := range e {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
@@ -229,9 +215,9 @@ func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
}
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...any) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), e...)
}
// DhcpConfig mocks base method.
@@ -309,25 +295,25 @@ func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
}
// QueryLog mocks base method.
func (m *MockClient) QueryLog(arg0 int) (*model.QueryLog, error) {
func (m *MockClient) QueryLog(limit int) (*model.QueryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLog", arg0)
ret := m.ctrl.Call(m, "QueryLog", limit)
ret0, _ := ret[0].(*model.QueryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLog indicates an expected call of QueryLog.
func (mr *MockClientMockRecorder) QueryLog(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) QueryLog(limit any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), limit)
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) {
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*model.QueryLogConfig)
ret0, _ := ret[0].(*model.QueryLogConfigWithIgnored)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -339,17 +325,17 @@ func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
}
// RefreshFilters mocks base method.
func (m *MockClient) RefreshFilters(arg0 bool) error {
func (m *MockClient) RefreshFilters(whitelist bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshFilters", arg0)
ret := m.ctrl.Call(m, "RefreshFilters", whitelist)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) RefreshFilters(whitelist any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), whitelist)
}
// RewriteList mocks base method.
@@ -398,143 +384,143 @@ func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
}
// SetAccessList mocks base method.
func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
func (m *MockClient) SetAccessList(accessList *model.AccessList) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", arg0)
ret := m.ctrl.Call(m, "SetAccessList", accessList)
ret0, _ := ret[0].(error)
return ret0
}
// SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetAccessList(accessList any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
}
// SetBlockedServices mocks base method.
func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServices", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServices indicates an expected call of SetBlockedServices.
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), accessList)
}
// SetBlockedServicesSchedule mocks base method.
func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSchedule) error {
func (m *MockClient) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", arg0)
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", schedule)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(schedule any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), schedule)
}
// SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(arg0 *[]string) error {
func (m *MockClient) SetCustomRules(rules *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
ret := m.ctrl.Call(m, "SetCustomRules", rules)
ret0, _ := ret[0].(error)
return ret0
}
// SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetCustomRules(rules any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), rules)
}
// SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
func (m *MockClient) SetDNSConfig(config *model.DNSConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
ret := m.ctrl.Call(m, "SetDNSConfig", config)
ret0, _ := ret[0].(error)
return ret0
}
// SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetDNSConfig(config any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), config)
}
// SetDhcpConfig mocks base method.
func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
func (m *MockClient) SetDhcpConfig(status *model.DhcpStatus) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDhcpConfig", arg0)
ret := m.ctrl.Call(m, "SetDhcpConfig", status)
ret0, _ := ret[0].(error)
return ret0
}
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetDhcpConfig(status any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), status)
}
// SetProfileInfo mocks base method.
func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error {
func (m *MockClient) SetProfileInfo(settings *model.ProfileInfo) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetProfileInfo", arg0)
ret := m.ctrl.Call(m, "SetProfileInfo", settings)
ret0, _ := ret[0].(error)
return ret0
}
// SetProfileInfo indicates an expected call of SetProfileInfo.
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetProfileInfo(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), settings)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error {
func (m *MockClient) SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0)
ret := m.ctrl.Call(m, "SetQueryLogConfig", ql)
ret0, _ := ret[0].(error)
return ret0
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetQueryLogConfig(ql any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), ql)
}
// SetSafeSearchConfig mocks base method.
func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error {
func (m *MockClient) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetSafeSearchConfig", arg0)
ret := m.ctrl.Call(m, "SetSafeSearchConfig", settings)
ret0, _ := ret[0].(error)
return ret0
}
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetSafeSearchConfig(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), settings)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
func (m *MockClient) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
ret := m.ctrl.Call(m, "SetStatsConfig", sc)
ret0, _ := ret[0].(error)
return ret0
}
// SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), sc)
}
// SetTLSConfig mocks base method.
func (m *MockClient) SetTLSConfig(tls *model.TlsConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetTLSConfig", tls)
ret0, _ := ret[0].(error)
return ret0
}
// SetTLSConfig indicates an expected call of SetTLSConfig.
func (mr *MockClientMockRecorder) SetTLSConfig(tls any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTLSConfig", reflect.TypeOf((*MockClient)(nil).SetTLSConfig), tls)
}
// Setup mocks base method.
@@ -567,10 +553,10 @@ func (mr *MockClientMockRecorder) Stats() *gomock.Call {
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) {
func (m *MockClient) StatsConfig() (*model.GetStatsConfigResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*model.StatsConfig)
ret0, _ := ret[0].(*model.GetStatsConfigResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -596,86 +582,101 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
}
// ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
// TLSConfig mocks base method.
func (m *MockClient) TLSConfig() (*model.TlsConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
ret := m.ctrl.Call(m, "TLSConfig")
ret0, _ := ret[0].(*model.TlsConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TLSConfig indicates an expected call of TLSConfig.
func (mr *MockClientMockRecorder) TLSConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSConfig", reflect.TypeOf((*MockClient)(nil).TLSConfig))
}
// ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", enabled, interval)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 any) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleFiltering(enabled, interval any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), enabled, interval)
}
// ToggleParental mocks base method.
func (m *MockClient) ToggleParental(arg0 bool) error {
func (m *MockClient) ToggleParental(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleParental", arg0)
ret := m.ctrl.Call(m, "ToggleParental", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleParental(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), enable)
}
// ToggleProtection mocks base method.
func (m *MockClient) ToggleProtection(arg0 bool) error {
func (m *MockClient) ToggleProtection(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleProtection", arg0)
ret := m.ctrl.Call(m, "ToggleProtection", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleProtection(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), enable)
}
// ToggleSafeBrowsing mocks base method.
func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
func (m *MockClient) ToggleSafeBrowsing(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", arg0)
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), enable)
}
// UpdateClient mocks base method.
func (m *MockClient) UpdateClient(arg0 *model.Client) error {
func (m *MockClient) UpdateClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateClient", arg0)
ret := m.ctrl.Call(m, "UpdateClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(arg0 any) *gomock.Call {
func (mr *MockClientMockRecorder) UpdateClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), client)
}
// UpdateFilter mocks base method.
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
func (m *MockClient) UpdateFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1)
ret := m.ctrl.Call(m, "UpdateFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 any) *gomock.Call {
func (mr *MockClientMockRecorder) UpdateFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), whitelist, f)
}

View File

@@ -1,9 +1,9 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags)
// Source: github.com/bakito/adguardhome-sync/internal/config (interfaces: Flags)
//
// Generated by this command:
//
// mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
// mockgen -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
//
// Package client is a generated GoMock package.
@@ -19,6 +19,7 @@ import (
type MockFlags struct {
ctrl *gomock.Controller
recorder *MockFlagsMockRecorder
isgomock struct{}
}
// MockFlagsMockRecorder is the mock recorder for MockFlags.
@@ -39,60 +40,60 @@ func (m *MockFlags) EXPECT() *MockFlagsMockRecorder {
}
// Changed mocks base method.
func (m *MockFlags) Changed(arg0 string) bool {
func (m *MockFlags) Changed(name string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Changed", arg0)
ret := m.ctrl.Call(m, "Changed", name)
ret0, _ := ret[0].(bool)
return ret0
}
// Changed indicates an expected call of Changed.
func (mr *MockFlagsMockRecorder) Changed(arg0 any) *gomock.Call {
func (mr *MockFlagsMockRecorder) Changed(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), name)
}
// GetBool mocks base method.
func (m *MockFlags) GetBool(arg0 string) (bool, error) {
func (m *MockFlags) GetBool(name string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBool", arg0)
ret := m.ctrl.Call(m, "GetBool", name)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBool indicates an expected call of GetBool.
func (mr *MockFlagsMockRecorder) GetBool(arg0 any) *gomock.Call {
func (mr *MockFlagsMockRecorder) GetBool(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), name)
}
// GetInt mocks base method.
func (m *MockFlags) GetInt(arg0 string) (int, error) {
func (m *MockFlags) GetInt(name string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInt", arg0)
ret := m.ctrl.Call(m, "GetInt", name)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInt indicates an expected call of GetInt.
func (mr *MockFlagsMockRecorder) GetInt(arg0 any) *gomock.Call {
func (mr *MockFlagsMockRecorder) GetInt(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), name)
}
// GetString mocks base method.
func (m *MockFlags) GetString(arg0 string) (string, error) {
func (m *MockFlags) GetString(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetString", arg0)
ret := m.ctrl.Call(m, "GetString", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetString indicates an expected call of GetString.
func (mr *MockFlagsMockRecorder) GetString(arg0 any) *gomock.Call {
func (mr *MockFlagsMockRecorder) GetString(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), name)
}

View File

@@ -1,17 +1,18 @@
package sync
import (
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/utils"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var (
actionProfileInfo = func(ac *actionContext) error {
if pro, err := ac.client.ProfileInfo(); err != nil {
return err
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo); merged != nil {
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo, ac.cfg.Features.Theme); merged != nil {
return ac.client.SetProfileInfo(merged)
}
return nil
@@ -63,7 +64,7 @@ var (
if err != nil {
return err
}
if ac.origin.statsConfig.Interval != sc.Interval {
if !sc.Equals(ac.origin.statsConfig) {
return ac.client.SetStatsConfig(ac.origin.statsConfig)
}
return nil
@@ -94,10 +95,10 @@ var (
return err
}
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.continueOnError); err != nil {
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.cfg.ContinueOnError); err != nil {
return err
}
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.continueOnError); err != nil {
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.cfg.ContinueOnError); err != nil {
return err
}
@@ -105,23 +106,13 @@ var (
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
}
if ac.origin.filters.Enabled != rf.Enabled || ac.origin.filters.Interval != rf.Interval {
if !utils.PtrEquals(ac.origin.filters.Enabled, rf.Enabled) ||
!utils.PtrEquals(ac.origin.filters.Interval, rf.Interval) {
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
}
return nil
}
actionBlockedServices = func(ac *actionContext) error {
rs, err := ac.client.BlockedServices()
if err != nil {
return err
}
if !model.EqualsStringSlice(ac.origin.blockedServices, rs, true) {
return ac.client.SetBlockedServices(ac.origin.blockedServices)
}
return nil
}
actionBlockedServicesSchedule = func(ac *actionContext) error {
rbss, err := ac.client.BlockedServicesSchedule()
if err != nil {
@@ -144,7 +135,7 @@ var (
for _, client := range r {
if err := ac.client.DeleteClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
if !ac.continueOnError {
if !ac.cfg.ContinueOnError {
return err
}
}
@@ -153,7 +144,7 @@ var (
for _, client := range a {
if err := ac.client.AddClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
if !ac.continueOnError {
if !ac.cfg.ContinueOnError {
return err
}
}
@@ -162,7 +153,7 @@ var (
for _, client := range u {
if err := ac.client.UpdateClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
if !ac.continueOnError {
if !ac.cfg.ContinueOnError {
return err
}
}
@@ -186,6 +177,9 @@ var (
if err != nil {
return err
}
// dc.Sanitize(ac.rl)
if !dc.Equals(ac.origin.dnsConfig) {
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
return err
@@ -226,7 +220,7 @@ var (
for _, lease := range r {
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
if !ac.continueOnError {
if !ac.cfg.ContinueOnError {
return err
}
}
@@ -235,7 +229,23 @@ var (
for _, lease := range a {
if err := ac.client.AddDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
if !ac.continueOnError {
if !ac.cfg.ContinueOnError {
return err
}
}
}
return nil
}
tlsConfig = func(ac *actionContext) error {
tlsc, err := ac.client.TLSConfig()
if err != nil {
return err
}
if !tlsc.Equals(ac.origin.tlsConfig) {
if err := ac.client.SetTLSConfig(ac.origin.tlsConfig); err != nil {
ac.rl.With("enabled", ac.origin.tlsConfig.Enabled, "error", err).Error("error setting tls config")
if !ac.cfg.ContinueOnError {
return err
}
}
@@ -244,7 +254,14 @@ var (
}
)
func syncFilterType(rl *zap.SugaredLogger, of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client, continueOnError bool) error {
func syncFilterType(
rl *zap.SugaredLogger,
of *[]model.Filter,
rFilters *[]model.Filter,
whitelist bool,
replica client.Client,
continueOnError bool,
) error {
fa, fu, fd := model.MergeFilters(rFilters, of)
for _, f := range fd {

View File

@@ -1,10 +1,11 @@
package sync
import (
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/types"
)
func setupActions(cfg *types.Config) (actions []syncAction) {
@@ -17,6 +18,11 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
action("safe browsing", actionSafeBrowsing),
)
}
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.QueryLogConfig {
actions = append(actions,
action("query log config", actionQueryLogConfig),
@@ -39,7 +45,6 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
}
if cfg.Features.Services {
actions = append(actions,
action("blocked services", actionBlockedServices),
action("blocked services schedule", actionBlockedServicesSchedule),
)
}
@@ -53,12 +58,6 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
action("DNS access lists", actionDNSAccessLists),
)
}
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.DHCP.ServerConfig {
actions = append(actions,
action("DHCP server config", actionDHCPServerConfig),
@@ -69,6 +68,11 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
action("DHCP static leases", actionDHCPStaticLeases),
)
}
if cfg.Features.TLSConfig {
actions = append(actions,
action("TLS config", tlsConfig),
)
}
return actions
}
@@ -78,12 +82,12 @@ type syncAction interface {
}
type actionContext struct {
rl *zap.SugaredLogger
origin *origin
client client.Client
replicaStatus *model.ServerStatus
continueOnError bool
replica types.AdGuardInstance
rl *zap.SugaredLogger
origin *origin
client client.Client
replicaStatus *model.ServerStatus
replica types.AdGuardInstance
cfg *types.Config
}
type defaultAction struct {

222
internal/sync/http.go Normal file
View File

@@ -0,0 +1,222 @@
package sync
import (
"context"
_ "embed"
"errors"
"fmt"
"html/template"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/metrics"
"github.com/bakito/adguardhome-sync/internal/sync/static"
"github.com/bakito/adguardhome-sync/version"
)
func (w *worker) handleSync(c *gin.Context) {
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
w.sync()
}
func (w *worker) handleRoot(c *gin.Context) {
total, dns, blocked, malware, adult := metrics.StatsGraph()
c.HTML(http.StatusOK, "index.html", map[string]any{
"DarkMode": w.cfg.API.DarkMode,
"Metrics": w.cfg.API.Metrics.Enabled,
"Version": version.Version,
"Build": version.Build,
"SyncStatus": w.status(),
"Stats": map[string]any{
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": percent(total.NumBlockedFiltering, total.NumDnsQueries),
"Malware": malware,
"MalwarePercentage": percent(total.NumReplacedSafebrowsing, total.NumDnsQueries),
"Adult": adult,
"AdultPercentage": percent(total.NumReplacedParental, total.NumDnsQueries),
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,
"TotalMalware": total.NumReplacedSafebrowsing,
"TotalAdult": total.NumReplacedParental,
},
},
)
}
func percent(a, b *int) string {
if a == nil || b == nil || *b == 0 {
return "0.00"
}
return fmt.Sprintf("%.2f", (float64(*a)*100.0)/float64(*b))
}
func (w *worker) handleLogs(c *gin.Context) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
}
func (w *worker) handleClearLogs(c *gin.Context) {
log.Clear()
c.Status(http.StatusOK)
}
func (w *worker) handleStatus(c *gin.Context) {
c.JSON(http.StatusOK, w.status())
}
func (w *worker) handleHealthz(c *gin.Context) {
status := w.status()
if status.Origin.Status != "success" {
c.Status(http.StatusServiceUnavailable)
return
}
for _, replica := range status.Replicas {
if replica.Status != "success" {
c.Status(http.StatusServiceUnavailable)
return
}
}
c.Status(http.StatusOK)
}
func (w *worker) listenAndServe() {
sl := l.With("port", w.cfg.API.Port)
if w.cfg.API.TLS.Enabled() {
c, k := w.cfg.API.TLS.Certs()
sl = sl.With("tls-cert", c).With("tls-key", k)
}
sl.Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background())
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
r.HEAD("/healthz", w.handleHealthz)
r.GET("/healthz", w.handleHealthz)
var group gin.IRouter = r
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
group = r.Group("/", gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
}
group.POST("/api/v1/sync", w.handleSync)
group.GET("/api/v1/logs", w.handleLogs)
group.POST("/api/v1/clear-logs", w.handleClearLogs)
group.GET("/api/v1/status", w.handleStatus)
static.HandleResources(group, w.cfg.API.DarkMode)
group.GET("/", w.handleRoot)
if w.cfg.API.Metrics.Enabled {
group.GET("/metrics", metrics.Handler())
go w.startScraping()
}
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: r,
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadHeaderTimeout: 1 * time.Second,
}
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(static.Index())))
go func() {
var err error
if w.cfg.API.TLS.Enabled() {
err = httpServer.ListenAndServeTLS(w.cfg.API.TLS.Certs())
} else {
err = httpServer.ListenAndServe()
}
if !errors.Is(err, http.ErrServerClosed) {
l.With("error", err).Fatalf("HTTP server ListenAndServe")
}
}()
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP, // kill -SIGHUP XXXX
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGQUIT, // kill -SIGQUIT XXXX
)
<-signalChan
l.Info("os.Interrupt - shutting down...")
go func() {
<-signalChan
l.Fatal("os.Kill - terminating...")
}()
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
if w.cron != nil {
l.Info("Stopping cron")
w.cron.Stop()
}
if err := httpServer.Shutdown(gracefulCtx); err != nil {
l.With("error", err).Error("Shutdown error")
defer os.Exit(1)
} else {
l.Info("API server stopped")
}
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
cancel()
}
type syncStatus struct {
SyncRunning bool `json:"syncRunning"`
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
}
type replicaStatus struct {
Host string `json:"host"`
URL string `json:"url"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"`
}
func getLast24Hours() []string {
var result []string
currentTime := time.Now()
// Loop to get the last 24 hours
for i := range 24 {
// Calculate the time for the current hour in the loop
timeInstance := currentTime.Add(time.Duration(-i) * time.Hour)
timeInstance = timeInstance.Truncate(time.Hour)
// Format the time as "14 Dec 17:00"
formattedTime := timeInstance.Format("02 Jan 15:04")
result = append(result, formattedTime)
}
// Reverse the slice to get the correct order (from oldest to latest)
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return result
}

View File

@@ -0,0 +1,30 @@
package sync
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Percent", func() {
DescribeTable("calculating percentage",
func(a, b *int, want string) {
Expect(percent(a, b)).To(Equal(want))
},
Entry("both inputs are nil", nil, nil, "0.00"),
Entry("a is nil, b is non-zero", nil, intPtr(10), "0.00"),
Entry("b is nil, a is non-zero", intPtr(10), nil, "0.00"),
Entry("b is zero", intPtr(10), intPtr(0), "0.00"),
Entry("normal case with positive int values", intPtr(25), intPtr(100), "25.00"),
Entry("a and b are equal", intPtr(50), intPtr(50), "100.00"),
Entry("a is zero, b is positive", intPtr(0), intPtr(50), "0.00"),
Entry("large positive values", intPtr(1000), intPtr(4000), "25.00"),
Entry("a greater than b", intPtr(150), intPtr(100), "150.00"),
Entry("negative values for a and b", intPtr(-25), intPtr(-50), "50.00"),
Entry("a is positive, b is negative", intPtr(25), intPtr(-50), "-50.00"),
Entry("a is negative, b is positive", intPtr(-25), intPtr(50), "-50.00"),
)
})
func intPtr(i int) *int {
return &i
}

View File

@@ -3,8 +3,8 @@ package sync
import (
"time"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/metrics"
"github.com/bakito/adguardhome-sync/internal/types"
)
func (w *worker) startScraping() {
@@ -26,25 +26,26 @@ func (w *worker) startScraping() {
}
func (w *worker) scrape() {
var ims []metrics.InstanceMetrics
var iml metrics.InstanceMetricsList
ims = append(ims, w.getMetrics(w.cfg.Origin))
iml.Metrics = append(iml.Metrics, w.getMetrics(*w.cfg.Origin))
for _, replica := range w.cfg.Replicas {
ims = append(ims, w.getMetrics(replica))
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
}
metrics.Update(ims...)
metrics.UpdateInstances(iml)
}
func (w *worker) getMetrics(inst types.AdGuardInstance) (im metrics.InstanceMetrics) {
func (w *worker) getMetrics(inst types.AdGuardInstance) metrics.InstanceMetrics {
var im metrics.InstanceMetrics
client, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return
return im
}
im.HostName = inst.Host
im.Status, _ = client.Status()
im.Stats, _ = client.Stats()
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
return
return im
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,242 @@
<html lang="en">
<head>
<title>AdGuard Home sync</title>
<script type="text/javascript" src="lib/jquery.js"></script>
<link rel="stylesheet" href="lib/bootstrap.css">
<script type="text/javascript">
$(document).ready(function () {
$("#showLogs").click(function () {
$.get("api/v1/logs", {}, function (data) {
$('#logs').html(data);
}
);
$.get("api/v1/status", {}, function (status) {
$('#origin').removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
status.replicas.forEach(function (replica, i) {
$('#replica_' + i).removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + replica.status).attr('title', replica.error);
});
}
);
});
$("#clearLogs").click(function () {
$.post("api/v1/clear-logs", {}, function () {
$('#logs').html("");
}
);
});
$("#sync").click(function () {
$.post("api/v1/sync", {}, function (data) {
});
$("#showLogs").click();
});
$("#showLogs").click();
});
</script>
<link rel="shortcut icon" href="favicon.ico">
<style>
.stat-card {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
padding: 15px;
text-align: left;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
{{- if .Metrics }}
.stat-card h3 {
margin: 0;
font-size: 2rem;
}
.stat-card p {
margin: 5px 0;
font-size: 0.9rem;
}
.percentage {
font-size: 0.9rem;
text-align: right;
height: 20px;
}
canvas {
flex-grow: 1;
height: 100px !important;
}
{{- end }}
.button-row {
margin-top: 20px;
}
.btn-group {
margin: 5px;
}
</style>
</head>
<body>
<div class="container-fluid px-4">
<div class="row">
<div class="d-flex align-items-center mb-3">
<img src="logo.svg" alt="Logo" class="me-3" style="height: 4em;">
<div>
<h1 class="mb-0">AdGuard Home sync</h1>
<p class="h6 text-muted mb-0">{{ .Version }} ({{ .Build }})</p>
</div>
</div>
</div>
{{- if .Metrics }}
<div class="row g-4 d-flex">
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage"></div>
<h3 style="color: rgb(78, 141, 245);">{{.Stats.TotalDNS}}</h3>
<p>DNS Queries</p>
<canvas id="dnsQueriesChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(255, 94, 94);">{{.Stats.BlockedPercentage}}%</div>
<h3 style="color: rgb(255, 94, 94);">{{.Stats.TotalBlocked}}</h3>
<p>Blocked by Filters</p>
<canvas id="blockedFiltersChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(110, 224, 122);">{{.Stats.MalwarePercentage}}%</div>
<h3 style="color: rgb(110, 224, 122);">{{.Stats.TotalMalware}}</h3>
<p>Blocked malware/phishing</p>
<canvas id="malwareChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(232, 198, 78);">{{.Stats.AdultPercentage}}%</div>
<h3 style="color: rgb(232, 198, 78);">{{.Stats.TotalAdult}}</h3>
<p>Blocked adult websites</p>
<canvas id="adultWebsitesChart"></canvas>
</div>
</div>
</div>
{{- end }}
<div class="row button-row">
<div class="col">
<div class="btn-group" role="group">
<button type="button" class="btn btn-success" id="sync">Synchronize</button>
<button type="button" class="btn btn-secondary" id="showLogs">Update Logs</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="clearLogs">Clear Logs</a>
</div>
</div>
</div>
<div class="col col-md-auto">
<div class="btn-group float-right" role="group">
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
{{ range $i, $r := .SyncStatus.Replicas }}
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
type="button" id="replica_{{ $i }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
{{ end }}
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-md-12">
<div class="stat-card">
<pre class="p-3 border"><code id="logs"></code></pre>
</div>
</div>
</div>
</div>
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
<script src="lib/popper.js" ></script>
<script src="lib/bootstrap.js" ></script>
{{- if .Metrics }}
<script src="lib/chart.js" ></script>
<script>
// Function to create minimal line charts
function createChart(canvasId, data) {
const ctx = document.getElementById(canvasId).getContext('2d');
const datasets = Array(data.length);
for (let i = 0; i < data.length; i++) {
datasets[i] = {
data: data[i].data,
title: data[i].title,
backgroundColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 0.2)`,
borderColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 1)`,
borderWidth: 3,
fill: data[i].fill,
pointRadius: 0,
}
}
new Chart(ctx, {
type: 'line',
data: {
labels: {{.Stats.Labels}},
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
bodyFont: {
size: 20
},
titleFont: {
size: 20
},
displayColors: false,
callbacks: {
label: function(tooltipItem) {
if (tooltipItem.dataset.title) {
return tooltipItem.raw + " - " + tooltipItem.dataset.title;
}
return tooltipItem.raw;
}
}
}
},
scales: {
x: { display: false,
title: {
display: true
}
},
y: { display: false,
min: 0,
title: {
display: true
} }
}
}
});
}
createChart('dnsQueriesChart', {{.Stats.DNS}});
createChart('blockedFiltersChart', {{.Stats.Blocked}});
createChart('malwareChart', {{.Stats.Malware}});
createChart('adultWebsitesChart', {{.Stats.Adult}});
</script>
{{- end }}
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,80 @@
package static
import (
_ "embed"
"net/http"
"github.com/gin-gonic/gin"
)
var (
//go:embed index.html
index string
//go:embed favicon.ico
favicon []byte
//go:embed logo.svg
logo []byte
//go:embed bootstrap.min-5.3.3.js
bootstrapJS []byte
// https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
//go:embed bootstrap.min-5.3.3.css
bootstrapCSS []byte
// https://bootswatch.com/5/darkly/bootstrap.min.css
//go:embed bootstrap.min-darkly-5.3.css
bootstrapDarkCSS []byte
// https://code.jquery.com/jquery-3.7.1.min.js
//go:embed jquery-3.7.1.min.js
jquery []byte
// https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js
//go:embed popper.min-2.9.2.js
popper []byte
// https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js
//go:embed chart.umd.min-4.4.7.js
chart []byte
)
func handleFavicon(c *gin.Context) {
c.Data(http.StatusOK, "image/x-icon", favicon)
}
func handleLogo(c *gin.Context) {
c.Data(http.StatusOK, "image/svg+xml", logo)
}
func Index() string {
return index
}
func HandleResources(group gin.IRouter, dark bool) {
group.GET("/favicon.ico", handleFavicon)
group.GET("/logo.svg", handleLogo)
group.GET("/lib/jquery.js", handleJS(jquery))
group.GET("/lib/popper.js", handleJS(popper))
group.GET("/lib/chart.js", handleJS(chart))
group.GET("/lib/bootstrap.js", handleJS(bootstrapJS))
if dark {
group.GET("/lib/bootstrap.css", handleCSS(bootstrapDarkCSS))
} else {
group.GET("/lib/bootstrap.css", handleCSS(bootstrapCSS))
}
}
func handleJS(bytes []byte) gin.HandlerFunc {
return func(c *gin.Context) {
c.Data(http.StatusOK, "application/javascript", bytes)
}
}
func handleCSS(bytes []byte) gin.HandlerFunc {
return func(c *gin.Context) {
c.Data(http.StatusOK, "text/css", bytes)
}
}

View File

@@ -3,31 +3,34 @@ package sync
import (
"errors"
"fmt"
"net/http"
"runtime"
"sort"
"time"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions"
"github.com/bakito/adguardhome-sync/version"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/metrics"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/internal/utils"
"github.com/bakito/adguardhome-sync/internal/versions"
"github.com/bakito/adguardhome-sync/version"
)
var l = log.GetLogger("sync")
// Sync config from origin to replica
// Sync config from origin to replica.
func Sync(cfg *types.Config) error {
if cfg.Origin.URL == "" {
return fmt.Errorf("origin URL is required")
return errors.New("origin URL is required")
}
if len(cfg.UniqueReplicas()) == 0 {
return fmt.Errorf("no replicas configured")
return errors.New("no replicas configured")
}
l.With(
@@ -41,10 +44,8 @@ func Sync(cfg *types.Config) error {
cfg.Origin.AutoSetup = false
w := &worker{
cfg: cfg,
createClient: func(ai types.AdGuardInstance) (client.Client, error) {
return client.New(ai)
},
cfg: cfg,
createClient: client.New,
}
if cfg.Cron != "" {
w.cron = cron.New()
@@ -66,16 +67,12 @@ func Sync(cfg *types.Config) error {
if cfg.API.Port != 0 {
w.cron.Start()
} else {
runOnStartAsync(cfg, w)
w.cron.Run()
}
}
if cfg.API.Port != 0 {
if cfg.RunOnStart {
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
runOnStartAsync(cfg, w)
w.listenAndServe()
} else if cfg.RunOnStart {
l.Info("Running sync on startup")
@@ -85,6 +82,15 @@ func Sync(cfg *types.Config) error {
return nil
}
func runOnStartAsync(cfg *types.Config, w *worker) {
if cfg.RunOnStart {
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
}
type worker struct {
cfg *types.Config
running bool
@@ -95,7 +101,7 @@ type worker struct {
func (w *worker) status() *syncStatus {
syncStatus := &syncStatus{
Origin: w.getStatus(w.cfg.Origin),
Origin: w.getStatus(*w.cfg.Origin),
}
for _, replica := range w.cfg.Replicas {
@@ -115,15 +121,15 @@ func (w *worker) status() *syncStatus {
return syncStatus
}
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
oc, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
st.Status = "danger"
st.Error = err.Error()
return
return st
}
sl := l.With("from", inst.WebHost)
status, err := oc.Status()
@@ -131,16 +137,16 @@ func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
if errors.Is(err, client.ErrSetupNeeded) {
st.Status = "warning"
st.Error = err.Error()
return
return st
}
sl.With("error", err).Error("Error getting origin status")
st.Status = "danger"
st.Error = err.Error()
return
return st
}
st.Status = "success"
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
return
return st
}
func (w *worker) sync() {
@@ -149,9 +155,11 @@ func (w *worker) sync() {
return
}
w.running = true
defer func() { w.running = false }()
defer func() {
w.running = false
}()
oc, err := w.createClient(w.cfg.Origin)
oc, err := w.createClient(*w.cfg.Origin)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return
@@ -167,7 +175,8 @@ func (w *worker) sync() {
}
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
sl.With("error", err, "version", o.status.Version).
Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
return
}
@@ -176,7 +185,14 @@ func (w *worker) sync() {
o.profileInfo, err = oc.ProfileInfo()
if err != nil {
sl.With("error", err).Error("Error getting profileInfo info")
return
// Workaround for https://github.com/AdguardTeam/AdGuardHome/issues/7987
// and https://github.com/AdguardTeam/AdGuardHome/issues/7985
clientErr := &client.Error{}
if !w.cfg.ContinueOnError || !errors.As(err, &clientErr) || clientErr.Code() != http.StatusUnauthorized {
return
}
}
o.parental, err = oc.Parental()
@@ -201,12 +217,6 @@ func (w *worker) sync() {
return
}
o.blockedServices, err = oc.BlockedServices()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services")
return
}
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services schedule")
@@ -254,6 +264,14 @@ func (w *worker) sync() {
}
}
if w.cfg.Features.TLSConfig {
o.tlsConfig, err = oc.TLSConfig()
if err != nil {
sl.With("error", err).Error("Error getting tls config")
return
}
}
w.actions = setupActions(w.cfg)
replicas := w.cfg.UniqueReplicas()
@@ -271,45 +289,65 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl := l.With("to", rc.Host())
rl.Info("Start sync")
start := time.Now()
withError := false
delta := time.Since(start).Seconds()
defer func() {
metrics.UpdateResult(rc.Host(), !withError, delta)
doneLog := rl.With("duration", fmt.Sprintf("%vs", delta))
if withError {
doneLog.Error("Sync done")
} else {
doneLog.Info("Sync done")
}
}()
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
if err != nil {
rl.With("error", err).Error("Error getting replica status")
withError = true
return
}
rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
rl.With("error", err, "version", replicaStatus.Version).
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
withError = true
return
}
if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).
Warn("Versions do not match")
}
ac := &actionContext{
continueOnError: w.cfg.ContinueOnError,
rl: rl,
origin: o,
replicaStatus: replicaStatus,
client: rc,
replica: replica,
cfg: w.cfg,
rl: rl,
origin: o,
replicaStatus: replicaStatus,
client: rc,
replica: replica,
}
for _, action := range w.actions {
if err := action.sync(ac); err != nil {
rl.With("error", err).Errorf("Error syncing %s", action.name())
withError = true
if !w.cfg.ContinueOnError {
return
}
}
}
rl.Info("Sync done")
}
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*model.ServerStatus, error) {
func (w *worker) statusWithSetup(
rl *zap.SugaredLogger,
replica types.AdGuardInstance,
rc client.Client,
) (*model.ServerStatus, error) {
rs, err := rc.Status()
if err != nil {
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
@@ -327,12 +365,11 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
type origin struct {
status *model.ServerStatus
rewrites *model.RewriteEntries
blockedServices *model.BlockedServicesArray
blockedServicesSchedule *model.BlockedServicesSchedule
filters *model.FilterStatus
clients *model.Clients
queryLogConfig *model.QueryLogConfig
statsConfig *model.StatsConfig
queryLogConfig *model.QueryLogConfigWithIgnored
statsConfig *model.GetStatsConfigResponse
accessList *model.AccessList
dnsConfig *model.DNSConfig
dhcpServerConfig *model.DhcpStatus
@@ -340,4 +377,5 @@ type origin struct {
safeSearch *model.SafeSearchConfig
profileInfo *model.ProfileInfo
safeBrowsing bool
tlsConfig *model.TlsConfig
}

View File

@@ -3,16 +3,17 @@ package sync
import (
"errors"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
clientmock "github.com/bakito/adguardhome-sync/internal/mocks/client"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/internal/utils"
"github.com/bakito/adguardhome-sync/internal/versions"
)
var _ = Describe("Sync", func() {
@@ -48,6 +49,7 @@ var _ = Describe("Sync", func() {
GeneralSettings: true,
StatsConfig: true,
QueryLogConfig: true,
Theme: true,
},
Replicas: []types.AdGuardInstance{
{},
@@ -57,8 +59,8 @@ var _ = Describe("Sync", func() {
te = errors.New(uuid.NewString())
ac = &actionContext{
continueOnError: false,
rl: l,
cfg: w.cfg,
rl: l,
origin: &origin{
profileInfo: &model.ProfileInfo{
Name: "origin",
@@ -67,8 +69,8 @@ var _ = Describe("Sync", func() {
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfig{},
statsConfig: &model.StatsConfig{},
queryLogConfig: &model.QueryLogConfigWithIgnored{},
statsConfig: &model.PutStatsConfigUpdateRequest{},
},
replicaStatus: &model.ServerStatus{},
client: cl,
@@ -258,16 +260,32 @@ var _ = Describe("Sync", func() {
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not change theme if feature is disabled", func() {
ac.origin.profileInfo.Language = "de"
ac.cfg.Features.Theme = false
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
Language: "de",
Name: "replica",
Theme: "",
})
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if language is not set", func() {
ac.origin.profileInfo.Language = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if theme is not set", func() {
ac.origin.profileInfo.Theme = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
@@ -289,9 +307,9 @@ var _ = Describe("Sync", func() {
})
})
Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfig
var qlc *model.QueryLogConfigWithIgnored
BeforeEach(func() {
qlc = &model.QueryLogConfig{}
qlc = &model.QueryLogConfigWithIgnored{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
@@ -302,15 +320,16 @@ var _ = Describe("Sync", func() {
var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
cl.EXPECT().
SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var sc *model.StatsConfig
var sc *model.PutStatsConfigUpdateRequest
BeforeEach(func() {
sc = &model.StatsConfig{}
sc = &model.PutStatsConfigUpdateRequest{}
})
It("should have no changes", func() {
cl.EXPECT().StatsConfig().Return(sc, nil)
@@ -318,10 +337,10 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
var interval model.StatsConfigInterval = 123
ac.origin.statsConfig.Interval = &interval
var interval float32 = 123
ac.origin.statsConfig.Interval = interval
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
cl.EXPECT().SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
@@ -359,26 +378,6 @@ var _ = Describe("Sync", func() {
Ω(st).Should(BeNil())
})
})
Context("actionBlockedServices", func() {
var rbs *model.BlockedServicesArray
BeforeEach(func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
rbs = &model.BlockedServicesArray{"foo"}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServices().Return(rbs, nil)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices changes", func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
cl.EXPECT().BlockedServices().Return(rbs, nil)
cl.EXPECT().SetBlockedServices(ac.origin.blockedServices)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionBlockedServicesSchedule", func() {
var rbss *model.BlockedServicesSchedule
BeforeEach(func() {
@@ -441,7 +440,9 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred())
})
It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}})
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}},
)
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
@@ -451,19 +452,25 @@ var _ = Describe("Sync", func() {
})
It("should abort after failed added filter", func() {
ac.continueOnError = false
ac.cfg.ContinueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
err := actionFilters(ac)
Ω(err).Should(HaveOccurred())
})
It("should continue after failed added filter", func() {
ac.continueOnError = true
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}})
ac.cfg.ContinueOnError = true
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}},
)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
@@ -569,7 +576,7 @@ var _ = Describe("Sync", func() {
Context("sync", func() {
BeforeEach(func() {
w.cfg = &types.Config{
Origin: types.AdGuardInstance{},
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{URL: "foo"},
Features: types.Features{
DHCP: types.DHCP{
@@ -587,27 +594,28 @@ var _ = Describe("Sync", func() {
GeneralSettings: true,
StatsConfig: true,
QueryLogConfig: true,
TLSConfig: true,
},
}
})
It("should have no changes", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host()
@@ -616,39 +624,39 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
w.sync()
})
It("should not sync DHCP", func() {
w.cfg.Features.DHCP.ServerConfig = false
w.cfg.Features.DHCP.StaticLeases = false
// origin
cl.EXPECT().Host()
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host()
@@ -657,17 +665,17 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
w.sync()
})
It("origin version is too small", func() {
@@ -685,18 +693,18 @@ var _ = Describe("Sync", func() {
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync()
})

View File

@@ -0,0 +1,44 @@
package matchers
import (
"strings"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)
type equalIgnoringLineEndingsMatcher struct {
expected string
}
func (matcher *equalIgnoringLineEndingsMatcher) Match(actual any) (success bool, err error) {
actualStr, ok := actual.(string)
if !ok {
return false, nil
}
normalizedActual := strings.ReplaceAll(actualStr, "\r\n", "\n")
normalizedExpected := strings.ReplaceAll(matcher.expected, "\r\n", "\n")
return normalizedActual == normalizedExpected, nil
}
func (matcher *equalIgnoringLineEndingsMatcher) FailureMessage(actual any) (message string) {
actualString, actualOK := actual.(string)
if actualOK {
return format.MessageWithDiff(actualString, "to equal", matcher.expected)
}
return format.Message(actual, "to equal", matcher.expected)
}
func (matcher *equalIgnoringLineEndingsMatcher) NegatedFailureMessage(actual any) (message string) {
return format.Message(actual, "not to equal", matcher.expected)
}
// EqualIgnoringLineEndings returns a new matcher.
func EqualIgnoringLineEndings(expected string) types.GomegaMatcher {
return &equalIgnoringLineEndingsMatcher{
expected: expected,
}
}

104
internal/types/features.go Normal file
View File

@@ -0,0 +1,104 @@
package types
import (
"go.uber.org/zap"
)
func NewFeatures(enabled bool) Features {
return Features{
DNS: DNS{
AccessLists: enabled,
ServerConfig: enabled,
Rewrites: enabled,
},
DHCP: DHCP{
ServerConfig: enabled,
StaticLeases: enabled,
},
GeneralSettings: enabled,
QueryLogConfig: enabled,
StatsConfig: enabled,
ClientSettings: enabled,
Services: enabled,
Filters: enabled,
Theme: enabled,
TLSConfig: false,
}
}
// Features feature flags.
type Features struct {
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" documentation:"Sync general settings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" documentation:"Sync query log config" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" documentation:"Sync stats config" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" documentation:"Sync client settings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" documentation:"Sync services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" documentation:"Sync filters" env:"FEATURES_FILTERS"`
Theme bool `json:"theme" yaml:"theme" documentation:"Sync the web UI theme" env:"FEATURES_THEME"`
TLSConfig bool `json:"tlsConfig" yaml:"tlsConfig" documentation:"Sync the TLS config" env:"FEATURES_TLS_CONFIG"`
}
// DHCP features.
type DHCP struct {
ServerConfig bool `documentation:"Sync DHCP server config" env:"FEATURES_DHCP_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
StaticLeases bool `documentation:"Sync DHCP static leases" env:"FEATURES_DHCP_STATIC_LEASES" json:"staticLeases" yaml:"staticLeases"`
}
// DNS features.
type DNS struct {
AccessLists bool `documentation:"Sync DNS access lists" env:"FEATURES_DNS_ACCESS_LISTS" json:"accessLists" yaml:"accessLists"`
ServerConfig bool `documentation:"Sync DNS server config" env:"FEATURES_DNS_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
Rewrites bool `documentation:"Sync DNS rewrites" env:"FEATURES_DNS_REWRITES" json:"rewrites" yaml:"rewrites"`
}
// LogDisabled log all disabled features.
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features := f.collectDisabled()
if len(features) > 0 {
l.With("features", features).Info("Disabled features")
}
}
func (f *Features) collectDisabled() []string {
var features []string
if !f.DHCP.ServerConfig {
features = append(features, "DHCP.ServerConfig")
}
if !f.DHCP.StaticLeases {
features = append(features, "DHCP.StaticLeases")
}
if !f.DNS.AccessLists {
features = append(features, "DNS.AccessLists")
}
if !f.DNS.ServerConfig {
features = append(features, "DNS.ServerConfig")
}
if !f.DNS.Rewrites {
features = append(features, "DNS.Rewrites")
}
if !f.GeneralSettings {
features = append(features, "GeneralSettings")
}
if !f.QueryLogConfig {
features = append(features, "QueryLogConfig")
}
if !f.StatsConfig {
features = append(features, "StatsConfig")
}
if !f.ClientSettings {
features = append(features, "ClientSettings")
}
if !f.Services {
features = append(features, "BlockedServices")
}
if !f.Filters {
features = append(features, "Filters")
}
if !f.TLSConfig {
features = append(features, "TLSConfig")
}
return features
}

222
internal/types/types.go Normal file
View File

@@ -0,0 +1,222 @@
// Package types
// +kubebuilder:object:generate=true
package types
import (
"fmt"
"net/url"
"path/filepath"
"strings"
"time"
"go.uber.org/zap"
)
const (
// DefaultAPIPath default api path.
DefaultAPIPath = "/control"
)
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Cron string `documentation:"Cron expression for the sync interval" env:"CRON" json:"cron,omitempty" yaml:"cron,omitempty"`
RunOnStart bool `documentation:"Run the sync on startup" env:"RUN_ON_START" json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
PrintConfigOnly bool `documentation:"Print current config only and stop the application" env:"PRINT_CONFIG_ONLY" json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty"`
ContinueOnError bool `documentation:"Continue sync on errors" env:"CONTINUE_ON_ERROR" json:"continueOnError,omitempty" yaml:"continueOnError,omitempty"`
// Origin adguardhome instance
Origin *AdGuardInstance `documentation:"Origin instance" json:"origin" yaml:"origin"`
// One single replica adguardhome instance
Replica *AdGuardInstance `documentation:"Single or replica instance (don't use in combination with replicas')" json:"replica,omitempty" yaml:"replica,omitempty"`
// Multiple replica instances
Replicas []AdGuardInstance `documentation:"List or replica instances (don't use in combination with replicas')" json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
API API ` json:"api,omitempty" yaml:"api,omitempty"`
Features Features ` json:"features,omitempty" yaml:"features,omitempty"`
}
// API configuration.
type API struct {
Port int `documentation:"API port (API is disabled if port is set to 0)" env:"API_PORT" json:"port,omitempty" yaml:"port,omitempty"`
Username string `documentation:"API username" env:"API_USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
Password string `documentation:"API password" env:"API_PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
DarkMode bool `documentation:"API dark mode" env:"API_DARK_MODE" json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
Metrics Metrics ` json:"metrics,omitempty" yaml:"metrics,omitempty"`
TLS TLS ` json:"tls,omitempty" yaml:"tls,omitempty"`
}
// Metrics configuration.
type Metrics struct {
Enabled bool `documentation:"Enable metrics" env:"API_METRICS_ENABLED" json:"enabled,omitempty" yaml:"enabled,omitempty"`
ScrapeInterval time.Duration `documentation:"Interval for metrics scraping" env:"API_METRICS_SCRAPE_INTERVAL" json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty"`
QueryLogLimit int `documentation:"Metrics log query limit" env:"API_METRICS_QUERY_LOG_LIMIT" json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty"`
}
// TLS configuration.
type TLS struct {
CertDir string `documentation:"API TLS certificate directory" env:"API_TLS_CERT_DIR" json:"certDir,omitempty" yaml:"certDir,omitempty"`
CertName string `documentation:"API TLS certificate file name" env:"API_TLS_CERT_NAME" json:"certName,omitempty" yaml:"certName,omitempty"`
KeyName string `documentation:"API TLS key file name" env:"API_TLS_KEY_NAME" json:"keyName,omitempty" yaml:"keyName,omitempty"`
}
func (t TLS) Enabled() bool {
return strings.TrimSpace(t.CertDir) != ""
}
func (t TLS) Certs() (cert, key string) {
cert = filepath.Join(t.CertDir, defaultIfEmpty(t.CertName, "tls.crt"))
key = filepath.Join(t.CertDir, defaultIfEmpty(t.KeyName, "tls.key"))
return cert, key
}
func defaultIfEmpty(val, fallback string) string {
if strings.TrimSpace(val) == "" {
return fallback
}
return val
}
// Mask maks username and password.
func (a *API) Mask() {
a.Username = mask(a.Username)
a.Password = mask(a.Password)
}
// UniqueReplicas get unique replication instances.
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil && cfg.Replica.URL != "" {
if cfg.Replica.APIPath == "" {
cfg.Replica.APIPath = DefaultAPIPath
}
dedup[cfg.Replica.Key()] = *cfg.Replica
}
for _, replica := range cfg.Replicas {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
if replica.URL != "" {
dedup[replica.Key()] = replica
}
}
var r []AdGuardInstance
for _, replica := range dedup {
r = append(r, replica)
}
return r
}
// Log the current config.
func (cfg *Config) Log(l *zap.SugaredLogger) {
c := cfg.mask()
l.With("config", c).Debug("Using config")
}
func (cfg *Config) mask() *Config {
c := cfg.DeepCopy()
c.Origin.Mask()
if c.Replica != nil {
if c.Replica.URL == "" {
c.Replica = nil
} else {
c.Replica.Mask()
}
}
for i := range c.Replicas {
c.Replicas[i].Mask()
}
c.API.Mask()
return c
}
func (cfg *Config) Init() error {
if err := cfg.Origin.Init(); err != nil {
return err
}
for i := range cfg.Replicas {
replica := &cfg.Replicas[i]
if err := replica.Init(); err != nil {
return err
}
}
return nil
}
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `documentation:"URL of adguardhome instance" env:"URL" faker:"url" json:"url" yaml:"url"`
WebURL string `documentation:"Web URL of adguardhome instance" env:"WEB_URL" faker:"url" json:"webURL" yaml:"webURL"`
APIPath string `documentation:"API Path" env:"API_PATH" json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
Username string `documentation:"Adguardhome username" env:"USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
Password string `documentation:"Adguardhome password" env:"PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
Cookie string `documentation:"Adguardhome cookie" env:"COOKIE" json:"cookie,omitempty" yaml:"cookie,omitempty"`
RequestHeaders map[string]string `documentation:"Request Headers 'key1:value1,key2:value2'" env:"REQUEST_HEADERS" json:"requestHeaders,omitempty" yaml:"requestHeaders,omitempty"`
InsecureSkipVerify bool `documentation:"Skip TLS verification" env:"INSECURE_SKIP_VERIFY" json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
AutoSetup bool `documentation:"Automatically setup the instance if it is not initialized" env:"AUTO_SETUP" json:"autoSetup" yaml:"autoSetup"`
InterfaceName string `documentation:"Network interface name" env:"INTERFACE_NAME" json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
DHCPServerEnabled *bool `documentation:"Enable DHCP server" env:"DHCP_SERVER_ENABLED" json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"`
}
// Key AdGuardInstance key.
func (i *AdGuardInstance) Key() string {
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
}
// Mask maks username and password.
func (i *AdGuardInstance) Mask() {
i.Username = mask(i.Username)
i.Password = mask(i.Password)
}
func (i *AdGuardInstance) Init() error {
u, err := url.Parse(i.URL)
if err != nil {
return err
}
i.Host = u.Host
if i.WebURL == "" {
i.WebHost = i.Host
i.WebURL = i.URL
} else {
u, err := url.Parse(i.WebURL)
if err != nil {
return err
}
i.WebHost = u.Host
}
return nil
}
func mask(s string) string {
if len(s) < 3 {
return strings.Repeat("*", len(s))
}
mask := strings.Repeat("*", len(s)-2)
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
}
// Protection API struct.
type Protection struct {
ProtectionEnabled bool `json:"protection_enabled"`
}
// InstallConfig AdguardHome install config.
type InstallConfig struct {
Web InstallPort `json:"web"`
DNS InstallPort `json:"dns"`
Username string `json:"username"`
Password string `json:"password"`
}
// InstallPort AdguardHome install config port.
type InstallPort struct {
IP string `json:"ip"`
Port int `json:"port"`
Status string `json:"status"`
CanAutofix bool `json:"can_autofix"`
}

View File

@@ -0,0 +1,15 @@
package types
import (
"testing"
)
func FuzzMask(f *testing.F) {
testcases := []string{"", "a", "ab", "abc", "abcd"}
for _, tc := range testcases {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, value string) {
_ = mask(value)
})
}

View File

@@ -1,6 +1,8 @@
package types
import (
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
@@ -39,6 +41,7 @@ var _ = Describe("Types", func() {
Context("Config", func() {
Context("init", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "https://localhost:3000"},
},
@@ -53,6 +56,7 @@ var _ = Describe("Types", func() {
Context("UniqueReplicas", func() {
It("should return unique replicas in the array", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "a"},
{URL: "a", APIPath: DefaultAPIPath},
@@ -68,6 +72,7 @@ var _ = Describe("Types", func() {
Context("mask", func() {
It("should mask all names and passwords", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "a", Username: "user", Password: "pass"},
},
@@ -83,17 +88,65 @@ var _ = Describe("Types", func() {
Ω(masked.API.Password).Should(Equal("p**s"))
})
})
DescribeTable("mask should work correctly",
func(value, expected string) {
Ω(mask(value)).Should(Equal(expected))
},
Entry(`Empty password`, "", ""),
Entry(`1 char password`, "a", "*"),
Entry(`2 char password`, "ab", "**"),
Entry(`3 char password`, "abc", "a*c"),
)
})
Context("Feature", func() {
Context("LogDisabled", func() {
It("should log all features", func() {
f := NewFeatures(false)
Ω(f.collectDisabled()).Should(HaveLen(11))
Ω(f.collectDisabled()).Should(HaveLen(12))
})
It("should log no features", func() {
f := NewFeatures(true)
Ω(f.collectDisabled()).Should(BeEmpty())
Ω(f.collectDisabled()).Should(HaveLen(1))
})
})
})
Context("TLS", func() {
var t TLS
BeforeEach(func() {
t = TLS{
CertDir: "/path/to/certs",
}
})
Context("Enabled", func() {
It("should use enabled", func() {
Ω(t.Enabled()).Should(BeTrue())
})
It("should use disabled", func() {
t.CertDir = " "
Ω(t.Enabled()).Should(BeFalse())
})
})
Context("Certs", func() {
It("should use default crt and key", func() {
crt, key := t.Certs()
crt = normalizePath(crt)
key = normalizePath(key)
Ω(crt).Should(Equal("/path/to/certs/tls.crt"))
Ω(key).Should(Equal("/path/to/certs/tls.key"))
})
It("should use custom crt and key", func() {
t.CertName = "foo.crt"
t.KeyName = "bar.key"
crt, key := t.Certs()
crt = normalizePath(crt)
key = normalizePath(key)
Ω(crt).Should(Equal("/path/to/certs/foo.crt"))
Ω(key).Should(Equal("/path/to/certs/bar.key"))
})
})
})
})
func normalizePath(path string) string {
return strings.ReplaceAll(path, "\\", "/")
}

View File

@@ -0,0 +1,209 @@
//go:build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package types
import ()
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *API) DeepCopyInto(out *API) {
*out = *in
out.Metrics = in.Metrics
out.TLS = in.TLS
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new API.
func (in *API) DeepCopy() *API {
if in == nil {
return nil
}
out := new(API)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
*out = *in
if in.RequestHeaders != nil {
in, out := &in.RequestHeaders, &out.RequestHeaders
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.DHCPServerEnabled != nil {
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
if in == nil {
return nil
}
out := new(AdGuardInstance)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Config) DeepCopyInto(out *Config) {
*out = *in
if in.Origin != nil {
in, out := &in.Origin, &out.Origin
*out = new(AdGuardInstance)
(*in).DeepCopyInto(*out)
}
if in.Replica != nil {
in, out := &in.Replica, &out.Replica
*out = new(AdGuardInstance)
(*in).DeepCopyInto(*out)
}
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = make([]AdGuardInstance, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.API = in.API
out.Features = in.Features
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
func (in *Config) DeepCopy() *Config {
if in == nil {
return nil
}
out := new(Config)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DHCP) DeepCopyInto(out *DHCP) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCP.
func (in *DHCP) DeepCopy() *DHCP {
if in == nil {
return nil
}
out := new(DHCP)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNS) DeepCopyInto(out *DNS) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNS.
func (in *DNS) DeepCopy() *DNS {
if in == nil {
return nil
}
out := new(DNS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Features) DeepCopyInto(out *Features) {
*out = *in
out.DNS = in.DNS
out.DHCP = in.DHCP
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features.
func (in *Features) DeepCopy() *Features {
if in == nil {
return nil
}
out := new(Features)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InstallConfig) DeepCopyInto(out *InstallConfig) {
*out = *in
out.Web = in.Web
out.DNS = in.DNS
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallConfig.
func (in *InstallConfig) DeepCopy() *InstallConfig {
if in == nil {
return nil
}
out := new(InstallConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InstallPort) DeepCopyInto(out *InstallPort) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPort.
func (in *InstallPort) DeepCopy() *InstallPort {
if in == nil {
return nil
}
out := new(InstallPort)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Metrics) DeepCopyInto(out *Metrics) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metrics.
func (in *Metrics) DeepCopy() *Metrics {
if in == nil {
return nil
}
out := new(Metrics)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Protection) DeepCopyInto(out *Protection) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Protection.
func (in *Protection) DeepCopy() *Protection {
if in == nil {
return nil
}
out := new(Protection)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
func (in *TLS) DeepCopy() *TLS {
if in == nil {
return nil
}
out := new(TLS)
in.DeepCopyInto(out)
return out
}

18
internal/utils/clone.go Normal file
View File

@@ -0,0 +1,18 @@
package utils
import (
"bytes"
"encoding/json"
)
func Clone[I any](in, out I) I {
b, _ := json.Marshal(in)
_ = json.Unmarshal(b, out)
return out
}
func JSONEquals(a, b any) bool {
ja, _ := json.Marshal(a)
jb, _ := json.Marshal(b)
return bytes.Equal(ja, jb)
}

24
internal/utils/ptr.go Normal file
View File

@@ -0,0 +1,24 @@
package utils
import "fmt"
func Ptr[I any](i I) *I {
return &i
}
func PtrToString[I any](i *I) string {
if i == nil {
return ""
}
return fmt.Sprintf("%v", i)
}
func PtrEquals[I comparable](a, b *I) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}

View File

@@ -3,15 +3,15 @@ package versions
import "golang.org/x/mod/semver"
const (
// MinAgh minimal adguardhome version
// MinAgh minimal adguardhome version.
MinAgh = "v0.107.40"
)
func IsNewerThan(v1 string, v2 string) bool {
func IsNewerThan(v1, v2 string) bool {
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
}
func IsSame(v1 string, v2 string) bool {
func IsSame(v1, v2 string) bool {
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
}

View File

@@ -1,9 +1,10 @@
package versions_test
import (
"github.com/bakito/adguardhome-sync/pkg/versions"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/internal/versions"
)
var _ = Describe("Versions", func() {
@@ -13,6 +14,11 @@ var _ = Describe("Versions", func() {
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
// tests for #607
Ω(versions.IsNewerThan("v0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
Ω(versions.IsNewerThan("0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
Ω(versions.IsNewerThan(versions.MinAgh, "v0.108.0-b.72")).Should(BeFalse())
Ω(versions.IsNewerThan(versions.MinAgh, "0.108.0-b.72")).Should(BeFalse())
})
})
Context("IsSame", func() {

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,138 +0,0 @@
package config_test
import (
"fmt"
"os"
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var envVars = []string{
"FEATURES_GENERAL_SETTINGS",
"FEATURES_QUERY_LOG_CONFIG",
"FEATURES_STATS_CONFIG",
"FEATURES_CLIENT_SETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVER_CONFIG",
"FEATURES_DHCP_STATIC_LEASES",
"FEATURES_DNS_SERVER_CONFIG",
"FEATURES_DNS_ACCESS_LISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACE_NAME",
"REPLICA1_DHCP_SERVER_ENABLED",
}
var deprecatedEnvVars = []string{
"FEATURES_GENERALSETTINGS",
"FEATURES_QUERYLOGCONFIG",
"FEATURES_STATSCONFIG",
"FEATURES_CLIENTSETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVERCONFIG",
"FEATURES_DHCP_STATICLEASES",
"FEATURES_DNS_SERVERCONFIG",
"FEATURES_DNS_ACCESSLISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACENAME",
"REPLICA1_DHCPSERVERENABLED",
}
var _ = Describe("Config", func() {
Context("deprecated", func() {
BeforeEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
})
AfterEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("Get", func() {
It("features should be false", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
})
})
Context("current", func() {
BeforeEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("Get", func() {
It("features should be true by default", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be true by default", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
Context("interface name", func() {
It("should set interface name of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
})
Context("dhcp server", func() {
It("should enable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
})
It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
})
})
})
func verifyFeatures(cfg *types.Config, value bool) {
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
Ω(cfg.Features.StatsConfig).Should(Equal(value))
Ω(cfg.Features.ClientSettings).Should(Equal(value))
Ω(cfg.Features.Services).Should(Equal(value))
Ω(cfg.Features.Filters).Should(Equal(value))
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
}

View File

@@ -1,145 +0,0 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/caarlos0/env/v10"
)
func handleDeprecatedEnvVars(cfg *types.Config) {
if val, ok := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START"); ok {
cfg.RunOnStart, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE"); ok {
cfg.API.DarkMode, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS"); ok {
cfg.Features.GeneralSettings, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG"); ok {
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG"); ok {
cfg.Features.StatsConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS"); ok {
cfg.Features.ClientSettings, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG"); ok {
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES"); ok {
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS"); ok {
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG"); ok {
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(val)
}
if cfg.Replica != nil {
if val, ok := checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL"); ok {
cfg.Replica.WebURL = val
}
if val, ok := checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP"); ok {
cfg.Replica.AutoSetup, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME"); ok {
cfg.Replica.InterfaceName = val
}
if val, ok := checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED"); ok {
if b, err := strconv.ParseBool(val); err != nil {
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
}
}
}
}
func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) {
old, oldOK := os.LookupEnv(oldName)
if oldOK {
logger.With("deprecated", oldName, "replacement", newName).
Warn("Deprecated env variable is used, please use the correct one")
}
new, newOK := os.LookupEnv(newName)
if newOK {
return new, true
}
return old, oldOK
}
func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) {
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
}
// Manually collect replicas from env.
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
id, _ := strconv.Atoi(sm[1])
if id <= 0 {
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
}
if id > len(initialReplicas) {
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
} else {
re := initialReplicas[id-1]
re.URL = sm[2]
replicas = append(replicas, re)
}
}
}
if len(replicas) == 0 {
replicas = initialReplicas
}
for i := range replicas {
reID := i + 1
// keep previously set value
replicaDhcpServer := replicas[i].DHCPServerEnabled
replicas[i].DHCPServerEnabled = nil
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
return nil, err
}
if replicas[i].DHCPServerEnabled == nil {
replicas[i].DHCPServerEnabled = replicaDhcpServer
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok {
replicas[i].APIPath = val
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INSECURESKIPVERIFY", "REPLICA%d_INSECURE_SKIP_VERIFY", reID); ok {
replicas[i].InsecureSkipVerify = strings.EqualFold(val, "true")
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_AUTOSETUP", "REPLICA%d_AUTO_SETUP", reID); ok {
replicas[i].AutoSetup = strings.EqualFold(val, "true")
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INTERFACENAME", "REPLICA%d_INTERFACE_NAME", reID); ok {
replicas[i].InterfaceName = val
}
if dhcpEnabled, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_DHCPSERVERENABLED", "REPLICA%d_DHCP_SERVER_ENABLED", reID); ok {
if strings.EqualFold(dhcpEnabled, "true") {
replicas[i].DHCPServerEnabled = utils.Ptr(true)
} else if strings.EqualFold(dhcpEnabled, "false") {
replicas[i].DHCPServerEnabled = utils.Ptr(false)
}
}
if replicas[i].APIPath == "" {
replicas[i].APIPath = "/control"
}
}
return replicas, nil
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,143 +0,0 @@
package sync
import (
"context"
_ "embed"
"errors"
"fmt"
"html/template"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/version"
"github.com/gin-gonic/gin"
)
var (
//go:embed index.html
index []byte
//go:embed favicon.ico
favicon []byte
)
func (w *worker) handleSync(c *gin.Context) {
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
w.sync()
}
func (w *worker) handleRoot(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
"DarkMode": w.cfg.API.DarkMode,
"Version": version.Version,
"Build": version.Build,
"SyncStatus": w.status(),
},
)
}
func (w *worker) handleFavicon(c *gin.Context) {
c.Data(http.StatusOK, "image/x-icon", favicon)
}
func (w *worker) handleLogs(c *gin.Context) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
}
func (w *worker) handleStatus(c *gin.Context) {
c.JSON(http.StatusOK, w.status())
}
func (w *worker) listenAndServe() {
l.With("port", w.cfg.API.Port).Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background())
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
r.Use(gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
}
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: r,
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadHeaderTimeout: 1 * time.Second,
}
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index))))
r.POST("/api/v1/sync", w.handleSync)
r.GET("/api/v1/logs", w.handleLogs)
r.GET("/api/v1/status", w.handleStatus)
r.GET("/favicon.ico", w.handleFavicon)
r.GET("/", w.handleRoot)
if w.cfg.API.Metrics.Enabled {
r.GET("/metrics", metrics.Handler())
go w.startScraping()
}
go func() {
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
l.With("error", err).Fatalf("HTTP server ListenAndServe")
}
}()
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP, // kill -SIGHUP XXXX
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGQUIT, // kill -SIGQUIT XXXX
)
<-signalChan
l.Info("os.Interrupt - shutting down...")
go func() {
<-signalChan
l.Fatal("os.Kill - terminating...")
}()
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
if w.cron != nil {
l.Info("Stopping cron")
w.cron.Stop()
}
if err := httpServer.Shutdown(gracefulCtx); err != nil {
l.With("error", err).Error("Shutdown error")
defer os.Exit(1)
} else {
l.Info("API server stopped")
}
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
cancel()
defer os.Exit(0)
}
type syncStatus struct {
SyncRunning bool `json:"syncRunning"`
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
}
type replicaStatus struct {
Host string `json:"host"`
URL string `json:"url"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"`
}

View File

@@ -1,78 +0,0 @@
<html lang="en">
<head>
<title>AdGuardHome sync</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js">
</script>
{{- if .DarkMode }}
<link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css"
crossorigin="anonymous">
{{- else }}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossOrigin="anonymous">
{{- end }}
<script type="text/javascript">
$(document).ready(function () {
$("#showLogs").click(function () {
$.get("api/v1/logs", {}, function (data) {
$('#logs').html(data);
}
);
$.get("api/v1/status", {}, function (status) {
$('#origin').removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
status.replicas.forEach(function (replica, i) {
$('#replica_' + i).removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + replica.status).attr('title', replica.error);
});
}
);
});
$("#sync").click(function () {
$.post("api/v1/sync", {}, function (data) {
});
$("#showLogs").click();
});
$("#showLogs").click();
});
</script>
<link rel="shortcut icon" href="favicon.ico">
</head>
<body>
<div class="container-fluid px-4">
<div class="row">
<p class="h1">
AdGuardHome sync
<p class="h6">{{ .Version }} ({{ .Build }})</p>
</p>
</div>
<div class="row">
<div class="col">
<div class="btn-group" role="group">
<input class="btn btn-success" type="button" id="sync" value="Synchronize"/>
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/>
</div>
</div>
<div class="col col-md-auto">
<div class="float-right">
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
{{ range $i, $r := .SyncStatus.Replicas }}
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
type="button" id="replica_{{ $i }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
{{ end }}
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<pre class="p-3 border"><code id="logs"></code></pre>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,58 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by deepcopy-gen. DO NOT EDIT.
package types
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
*out = *in
if in.DHCPServerEnabled != nil {
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
if in == nil {
return nil
}
out := new(AdGuardInstance)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Config) DeepCopyInto(out *Config) {
*out = *in
in.Origin.DeepCopyInto(&out.Origin)
if in.Replica != nil {
in, out := &in.Replica, &out.Replica
*out = new(AdGuardInstance)
(*in).DeepCopyInto(*out)
}
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = make([]AdGuardInstance, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.API = in.API
out.Features = in.Features
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
func (in *Config) DeepCopy() *Config {
if in == nil {
return nil
}
out := new(Config)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,97 +0,0 @@
package types
import (
"go.uber.org/zap"
)
func NewFeatures(enabled bool) Features {
return Features{
DNS: DNS{
AccessLists: enabled,
ServerConfig: enabled,
Rewrites: enabled,
},
DHCP: DHCP{
ServerConfig: enabled,
StaticLeases: enabled,
},
GeneralSettings: enabled,
QueryLogConfig: enabled,
StatsConfig: enabled,
ClientSettings: enabled,
Services: enabled,
Filters: enabled,
}
}
// Features feature flags
type Features struct {
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
}
// DHCP features
type DHCP struct {
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"`
}
// DNS features
type DNS struct {
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"`
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
}
// LogDisabled log all disabled features
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features := f.collectDisabled()
if len(features) > 0 {
l.With("features", features).Info("Disabled features")
}
}
func (f *Features) collectDisabled() []string {
var features []string
if !f.DHCP.ServerConfig {
features = append(features, "DHCP.ServerConfig")
}
if !f.DHCP.StaticLeases {
features = append(features, "DHCP.StaticLeases")
}
if !f.DNS.AccessLists {
features = append(features, "DNS.AccessLists")
}
if !f.DNS.ServerConfig {
features = append(features, "DNS.ServerConfig")
}
if !f.DNS.Rewrites {
features = append(features, "DNS.Rewrites")
}
if !f.GeneralSettings {
features = append(features, "GeneralSettings")
}
if !f.QueryLogConfig {
features = append(features, "QueryLogConfig")
}
if !f.StatsConfig {
features = append(features, "StatsConfig")
}
if !f.ClientSettings {
features = append(features, "ClientSettings")
}
if !f.Services {
features = append(features, "BlockedServices")
}
if !f.Filters {
features = append(features, "Filters")
}
return features
}

View File

@@ -1,190 +0,0 @@
package types
import (
"fmt"
"net/url"
"strings"
"time"
"go.uber.org/zap"
)
const (
// DefaultAPIPath default api path
DefaultAPIPath = "/control"
)
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
}
// API configuration
type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
}
// Metrics configuration
type Metrics struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
ScrapeInterval time.Duration `json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty" env:"API_METRICS_SCRAPE_INTERVAL"`
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
}
// Mask maks username and password
func (a *API) Mask() {
a.Username = mask(a.Username)
a.Password = mask(a.Password)
}
// UniqueReplicas get unique replication instances
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil && cfg.Replica.URL != "" {
if cfg.Replica.APIPath == "" {
cfg.Replica.APIPath = DefaultAPIPath
}
dedup[cfg.Replica.Key()] = *cfg.Replica
}
for _, replica := range cfg.Replicas {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
if replica.URL != "" {
dedup[replica.Key()] = replica
}
}
var r []AdGuardInstance
for _, replica := range dedup {
r = append(r, replica)
}
return r
}
// Log the current config
func (cfg *Config) Log(l *zap.SugaredLogger) {
c := cfg.mask()
l.With("config", c).Debug("Using config")
}
func (cfg *Config) mask() *Config {
c := cfg.DeepCopy()
c.Origin.Mask()
if c.Replica != nil {
if c.Replica.URL == "" {
c.Replica = nil
} else {
c.Replica.Mask()
}
}
for i := range c.Replicas {
c.Replicas[i].Mask()
}
c.API.Mask()
return c
}
func (cfg *Config) Init() error {
if err := cfg.Origin.Init(); err != nil {
return err
}
for i := range cfg.Replicas {
replica := &cfg.Replicas[i]
if err := replica.Init(); err != nil {
return err
}
}
return nil
}
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `json:"url" yaml:"url" env:"URL"`
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"`
Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"`
}
// Key AdGuardInstance key
func (i *AdGuardInstance) Key() string {
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
}
// Mask maks username and password
func (i *AdGuardInstance) Mask() {
i.Username = mask(i.Username)
i.Password = mask(i.Password)
}
func (i *AdGuardInstance) Init() error {
u, err := url.Parse(i.URL)
if err != nil {
return err
}
i.Host = u.Host
if i.WebURL == "" {
i.WebHost = i.Host
i.WebURL = i.URL
} else {
u, err := url.Parse(i.WebURL)
if err != nil {
return err
}
i.WebHost = u.Host
}
return nil
}
func mask(s string) string {
if s == "" {
return "***"
}
mask := strings.Repeat("*", len(s)-2)
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
}
// Protection API struct
type Protection struct {
ProtectionEnabled bool `json:"protection_enabled"`
}
// InstallConfig AdguardHome install config
type InstallConfig struct {
Web InstallPort `json:"web"`
DNS InstallPort `json:"dns"`
Username string `json:"username"`
Password string `json:"password"`
}
// InstallPort AdguardHome install config port
type InstallPort struct {
IP string `json:"ip"`
Port int `json:"port"`
Status string `json:"status"`
CanAutofix bool `json:"can_autofix"`
}

View File

@@ -1,15 +0,0 @@
package utils
import "encoding/json"
func Clone[I interface{}](in I, out I) I {
b, _ := json.Marshal(in)
_ = json.Unmarshal(b, out)
return out
}
func JsonEquals(a interface{}, b interface{}) bool {
ja, _ := json.Marshal(a)
jb, _ := json.Marshal(b)
return string(ja) == string(jb)
}

Some files were not shown because too many files have changed in this diff Show More