Compare commits

...

123 Commits
v0.7.3 ... 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
97 changed files with 2072 additions and 1205 deletions

View File

@@ -42,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

@@ -45,7 +45,7 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Modify Dockerfile
run: |

View File

@@ -24,7 +24,7 @@ jobs:
protocol: http
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup jq
uses: dcarbone/install-jq-action@v3

View File

@@ -15,29 +15,44 @@ jobs:
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: Cache toolbox binaries
uses: actions/cache@v4
with:
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
@@ -45,7 +60,9 @@ jobs:
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
@@ -55,12 +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: Cache toolbox binaries
uses: actions/cache@v4
with:
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.'

View File

@@ -162,6 +162,12 @@ linters:
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'

View File

@@ -10,11 +10,13 @@ builds:
- linux
- windows
- darwin
- openbsd
goarch:
- 386
- amd64
- arm
- arm64
- riscv64
goarm:
- 5
- 6
@@ -24,14 +26,22 @@ 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 ]] && [[ "{{ .Path }}" != *darwin* ]]; 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:
@@ -45,3 +55,5 @@ changelog:
- '^chore'
release:
prerelease: auto
sboms:
- artifacts: archive

View File

@@ -6,99 +6,97 @@ TB_LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || p
## Location to install dependencies to
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
$(TB_LOCALBIN):
mkdir -p $(TB_LOCALBIN)
if [ ! -e $(TB_LOCALBIN) ]; then mkdir -p $(TB_LOCALBIN); fi
# Helper functions
STRIP_V = $(patsubst v%,%,$(1))
## Tool Binaries
TB_DEEPCOPY_GEN ?= $(TB_LOCALBIN)/deepcopy-gen
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
TB_GOFUMPT ?= $(TB_LOCALBIN)/gofumpt
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
TB_GOLINES ?= $(TB_LOCALBIN)/golines
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=k8s.io/code-generator/cmd/deepcopy-gen
TB_DEEPCOPY_GEN_VERSION ?= v0.32.3
# renovate: packageName=mvdan.cc/gofumpt
TB_GOFUMPT_VERSION ?= v0.8.0
# renovate: packageName=github.com/golangci/golangci-lint/v2/cmd/golangci-lint
TB_GOLANGCI_LINT_VERSION ?= v2.1.2
# renovate: packageName=github.com/segmentio/golines
TB_GOLINES_VERSION ?= v0.12.2
# 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.8.2
# renovate: packageName=go.uber.org/mock/mockgen
TB_MOCKGEN_VERSION ?= v0.5.1
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
TB_OAPI_CODEGEN_VERSION ?= v2.4.1
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.3
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.deepcopy-gen
tb.deepcopy-gen: $(TB_DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
$(TB_DEEPCOPY_GEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/deepcopy-gen || GOBIN=$(TB_LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(TB_DEEPCOPY_GEN_VERSION)
.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: $(TB_GINKGO) ## Download ginkgo locally if necessary.
$(TB_GINKGO): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/ginkgo || GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: tb.gofumpt
tb.gofumpt: $(TB_GOFUMPT) ## Download gofumpt locally if necessary.
$(TB_GOFUMPT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/gofumpt || GOBIN=$(TB_LOCALBIN) go install mvdan.cc/gofumpt@$(TB_GOFUMPT_VERSION)
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: $(TB_GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(TB_GOLANGCI_LINT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golangci-lint || GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
.PHONY: tb.golines
tb.golines: $(TB_GOLINES) ## Download golines locally if necessary.
$(TB_GOLINES): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golines || GOBIN=$(TB_LOCALBIN) go install github.com/segmentio/golines@$(TB_GOLINES_VERSION)
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: $(TB_GORELEASER) ## Download goreleaser locally if necessary.
$(TB_GORELEASER): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/goreleaser || GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
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: $(TB_MOCKGEN) ## Download mockgen locally if necessary.
$(TB_MOCKGEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/mockgen || GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
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: $(TB_OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
$(TB_OAPI_CODEGEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/oapi-codegen || GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
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: $(TB_SEMVER) ## Download semver locally if necessary.
$(TB_SEMVER): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/semver || GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
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_LOCALBIN)/deepcopy-gen \
$(TB_LOCALBIN)/ginkgo \
$(TB_LOCALBIN)/gofumpt \
$(TB_LOCALBIN)/golangci-lint \
$(TB_LOCALBIN)/golines \
$(TB_LOCALBIN)/goreleaser \
$(TB_LOCALBIN)/mockgen \
$(TB_LOCALBIN)/oapi-codegen \
$(TB_LOCALBIN)/semver
$(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 \
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator \
mvdan.cc/gofumpt@github.com/mvdan/gofumpt \
github.com/golangci/golangci-lint/v2/cmd/golangci-lint \
github.com/segmentio/golines \
github.com/goreleaser/goreleaser/v2 \
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
github.com/bakito/semver?-version \
github.com/anchore/syft/cmd/syft?--version
## toolbox - end

View File

@@ -1,4 +1,4 @@
FROM golang:1.24-alpine AS builder
FROM golang:1.25-alpine AS builder
WORKDIR /go/src/app

View File

@@ -9,19 +9,21 @@ lint: tb.golangci-lint
tidy:
go mod tidy
generate: tb.deepcopy-gen
generate: model mocks deepcopy-gen
deepcopy-gen: tb.controller-gen
@mkdir -p ./tmp
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
$(TB_DEEPCOPY_GEN) --go-header-file ./tmp/deepcopy-gen-boilerplate.go.txt --bounding-dirs ./pkg/types
$(TB_CONTROLLER_GEN) paths=./internal/types object
fmt: tb.golines tb.gofumpt
$(TB_GOLINES) --base-formatter="$(TB_GOFUMPT)" --max-len=120 --write-output .
.PHONY: docs
docs:
go run cmd/docs/main.go
# Run tests
test: generate fmt lint test-ci
test: generate lint test-ci
fuzz:
go test -fuzz=FuzzMask -v ./pkg/types/ -fuzztime=60s
go test -fuzz=FuzzMask -v ./internal/types/ -fuzztime=60s
# Run ci tests
test-ci: mocks tidy tb.ginkgo
@@ -30,16 +32,16 @@ test-ci: mocks tidy tb.ginkgo
go tool cover -func=coverage.out
mocks: tb.mockgen
$(TB_MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
$(TB_MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
$(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: tb.semver tb.goreleaser
release: tb.semver tb.goreleaser tb.syft
@version=$$($(TB_SEMVER)); \
git tag -s $$version -m"Release $$version"
$(TB_GORELEASER) --clean
PATH=$(TB_LOCALBIN):$${PATH} $(TB_GORELEASER) --clean --parallelism 2
test-release: tb.goreleaser
$(TB_GORELEASER) --skip=publish --snapshot --clean
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
@@ -73,16 +75,16 @@ kind-test:
@./testdata/e2e/bin/install-chart.sh
# renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.60
ADGUARD_HOME_VERSION ?= v0.107.69
model: tb.oapi-codegen
@mkdir -p tmp
go run openapi/main.go $(ADGUARD_HOME_VERSION)
$(TB_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 $(ADGUARD_HOME_VERSION)
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:

288
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.
@@ -43,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
@@ -181,145 +318,36 @@ services:
restart: unless-stopped
```
## Config via environment variables
## Unraid
For Replicas replace `#` with the index number for the replica. E.g: `REPLICA#_URL` -> `REPLICA1_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.
| Name | Type | Description |
|:-------------------------------------|--------|:----------------------------------------------------------|
| 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_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#_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 |
| CRON (string) | string | Cron expression for the sync interval |
| RUN_ON_START (bool) | bool | Run the sung on startup |
| PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
| CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
| API_PORT (int) | int | API port |
| 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 weg UI theme |
Note when running the Docker container in Unraid please remove unneeded env variables.
If replica2 isn't used, this can cause sync errors.
### Unraid
## Home Assistant AdGuard Home Add-on users
⚠️ Disclaimer: Tere exists an unraid tepmlate for this application. This template is not managed by this project.
Also, as unraid is not known to me, I can not give any support on unraind templates.
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
Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
If replica2 isn't used this can cause sync errors.
![show-disabled-ports](https://github.com/user-attachments/assets/1df5f352-37a2-4508-82ec-7f270087d0b4)
### Config file
And then set the port of your choice for the Web interface
location: $HOME/.adguardhome-sync.yaml
![web-interface-port](https://github.com/user-attachments/assets/286ed030-4831-4f49-8b29-53e8802129c3)
```yaml
# cron expression to run in daemon mode. (default; "" = runs only once)
cron: "0 */2 * * *"
Don't forget to save and restart the add-on.
# runs the synchronisation on startup
runOnStart: true
Depending on your setup, you may also need to disable SSL for the add-on.
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
continueOnError: false
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.
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
# 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
# enable tls for the api server
# tls:
# # the directory of the provided tls certs
# certDir: /path/to/certs
# # the name of the cert file (default: tls.crt)
# certName: foo.crt
# # the name of the key file (default: tls.key)
# keyName: bar.key
# 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

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

@@ -87,10 +87,6 @@ func main() {
}
func correctEntries(schema map[string]any) {
// https://github.com/AdguardTeam/AdGuardHome/pull/7678
if err := unstructured.SetNestedField(schema, "string", "components", "schemas", "QueryLogItem", "properties", "client_proto", "type"); err != nil {
log.Fatalln(err)
}
}
func addFakeTags(schema map[string]any) {

View File

@@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/version"
)

View File

@@ -3,9 +3,9 @@ package cmd
import (
"github.com/spf13/cobra"
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"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.
@@ -67,6 +67,7 @@ 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().

View File

@@ -1,57 +0,0 @@
// Print the available environment variables
package main
import (
"fmt"
"reflect"
"strings"
"github.com/bakito/adguardhome-sync/pkg/types"
)
func main() {
_, _ = fmt.Println("| Name | Type | Description |")
_, _ = fmt.Println("| :--- | ---- |:----------- |")
printEnvTags(reflect.TypeOf(types.Config{}), "")
}
// printEnvTags recursively prints all fields with `env` tags.
func printEnvTags(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 != "" { // unexported field
continue
}
envTag := field.Tag.Get("env")
if envTag == "REPLICA" {
envTag = "REPLICA#"
}
combinedTag := envTag
if prefix != "" && envTag != "" {
combinedTag = prefix + "_" + envTag
} else if prefix != "" {
combinedTag = prefix
}
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if ft.Kind() == reflect.Struct && ft.Name() != "Time" { // skip time.Time
printEnvTags(ft, strings.TrimSuffix(combinedTag, "_"))
} else if envTag != "" {
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
docs := field.Tag.Get("documentation")
_, _ = fmt.Printf("| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
}
}
}

85
go.mod
View File

@@ -1,77 +1,82 @@
module github.com/bakito/adguardhome-sync
go 1.24.1
go 1.25.4
require (
github.com/caarlos0/env/v11 v11.3.1
github.com/gin-gonic/gin v1.10.0
github.com/go-faker/faker/v4 v4.6.0
github.com/go-resty/resty/v2 v2.16.5
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/jinzhu/copier v0.4.0
github.com/oapi-codegen/runtime v1.1.1
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/prometheus/client_golang v1.22.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/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/spf13/cobra v1.9.1
go.uber.org/mock v0.5.1
go.uber.org/zap v1.27.0
golang.org/x/mod v0.24.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.32.3
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
k8s.io/apimachinery v0.34.2
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
)
require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // 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.5 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // 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.2 // 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.26.0 // 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/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // 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.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // indirect
github.com/spf13/pflag v1.0.6 // 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/ugorji/go/codec v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.16.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
google.golang.org/protobuf v1.36.6 // 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
k8s.io/klog/v2 v2.130.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

215
go.sum
View File

@@ -1,70 +1,76 @@
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-faker/faker/v4 v4.7.0 h1:VboC02cXHl/NuQh5lM2W8b87yp4iFXIu59x4w0RZi4E=
github.com/go-faker/faker/v4 v4.7.0/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-resty/resty/v2 v2.17.0 h1:pW9DeXcaL4Rrym4EZ8v7L19zZiIlWPg5YXAcVmt+gN0=
github.com/go-resty/resty/v2 v2.17.0/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
@@ -72,10 +78,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -84,120 +88,137 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -206,17 +227,17 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -14,14 +14,27 @@ import (
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
"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/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.
@@ -36,7 +49,10 @@ func detailedError(resp *resty.Response, err error) error {
if err != nil {
e += ": " + err.Error()
}
return errors.New(e)
return &Error{
message: e,
errorCode: resp.StatusCode(),
}
}
// New create a new client.
@@ -52,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})
@@ -131,6 +147,8 @@ type Client interface {
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 {
@@ -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

@@ -12,10 +12,10 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"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/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 (

View File

@@ -13,9 +13,9 @@ import (
"go.uber.org/zap"
"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/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
)
var l = log.GetLogger("client")

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

@@ -5,7 +5,7 @@ import (
"github.com/go-resty/resty/v2"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
var _ model.HttpRequestDoer = &adapter{}

View File

@@ -9,7 +9,7 @@ import (
"go.uber.org/zap"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/internal/utils"
)
// Clone the config.
@@ -490,3 +490,7 @@ func sumUp(t, o *[]int) *[]int {
}
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/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
package model
import (
@@ -195,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"`
@@ -211,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.
@@ -303,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"`
@@ -369,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"`
@@ -426,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"`
@@ -618,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
@@ -629,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.
@@ -830,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)
@@ -844,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.
@@ -919,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
@@ -985,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.
@@ -1165,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
@@ -1199,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.
@@ -1310,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
@@ -1707,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)
@@ -2831,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 {
@@ -4256,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
@@ -5320,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
@@ -6266,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)
@@ -7602,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
@@ -8810,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...)
@@ -10335,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

@@ -5,8 +5,8 @@ import (
"github.com/onsi/gomega"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var _ = Describe("Types", func() {

View File

@@ -8,9 +8,9 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"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() {

View File

@@ -36,6 +36,9 @@
"webURL": {
"format": "uri",
"type": "string"
},
"requestHeaders": {
"type": "object"
}
},
"type": "object"
@@ -147,6 +150,9 @@
},
"theme": {
"type": "boolean"
},
"tlsConfig": {
"type": "boolean"
}
},
"type": "object"

View File

@@ -6,8 +6,8 @@ import (
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
)
var (
@@ -63,18 +63,27 @@ func Get(configFile string, flags Flags) (*AppConfig, error) {
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
cfg.Replica.DHCPServerEnabled = nil
// ignore replicas form env parsing as they are handled separately
// 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
}
// restore the replica
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
@@ -82,10 +91,6 @@ func Get(configFile string, flags Flags) (*AppConfig, error) {
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 == "" {
@@ -97,8 +102,6 @@ func Get(configFile string, flags Flags) (*AppConfig, 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
@@ -112,7 +115,7 @@ func Get(configFile string, flags Flags) (*AppConfig, error) {
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

@@ -7,8 +7,8 @@ import (
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
"github.com/bakito/adguardhome-sync/pkg/config"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
"github.com/bakito/adguardhome-sync/internal/config"
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
)
var _ = Describe("Config", func() {
@@ -44,6 +44,18 @@ 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()
@@ -221,15 +233,26 @@ var _ = Describe("Config", func() {
Ω(err).ShouldNot(HaveOccurred())
Ω(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.Get().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

@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/types"
)
func readFile(cfg *types.Config, path string) (string, error) {

View File

@@ -19,7 +19,8 @@ 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())

View File

@@ -22,6 +22,7 @@ const (
FlagFeatureClient = "feature-client-settings"
FlagFeatureServices = "feature-services"
FlagFeatureFilters = "feature-filters"
FlagFeatureTLSConfig = "feature-tls-config"
FlagOriginURL = "origin-url"
FlagOriginWebURL = "origin-web-url"

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 {
@@ -173,8 +173,13 @@ func (fr *flagReader) readFeatureFlags() error {
}); err != nil {
return err
}
return fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
fr.cfg.Features.Filters = value
}); err != nil {
return err
}
return fr.setBoolFlag(FlagFeatureTLSConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.TLSConfig = value
})
}

View File

@@ -7,8 +7,8 @@ import (
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
"github.com/bakito/adguardhome-sync/pkg/types"
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
"github.com/bakito/adguardhome-sync/internal/types"
)
var _ = Describe("Config", func() {
@@ -19,6 +19,7 @@ var _ = Describe("Config", func() {
)
BeforeEach(func() {
cfg = &types.Config{
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{},
Features: types.Features{
DNS: types.DNS{
@@ -143,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",
@@ -172,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",

View File

@@ -11,8 +11,8 @@ import (
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/version"
)
@@ -20,7 +20,7 @@ import (
var printConfigTemplate string
func (ac *AppConfig) Print() error {
originVersion := aghVersion(ac.cfg.Origin)
originVersion := aghVersion(*ac.cfg.Origin)
var replicaVersions []string
for _, replica := range ac.cfg.Replicas {
replicaVersions = append(replicaVersions, aghVersion(replica))

View File

@@ -10,7 +10,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/test/matchers"
"github.com/bakito/adguardhome-sync/internal/types"
"github.com/bakito/adguardhome-sync/version"
)
@@ -22,7 +23,7 @@ var _ = Describe("AppConfig", func() {
BeforeEach(func() {
ac = &AppConfig{
cfg: &types.Config{
Origin: types.AdGuardInstance{
Origin: &types.AdGuardInstance{
URL: "https://ha.xxxx.net:3000",
},
},
@@ -37,17 +38,15 @@ origin:
It("should printInternal config without file", func() {
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
Ω(err).ShouldNot(HaveOccurred())
Ω(
out,
).Should(Equal(fmt.Sprintf(expected(1), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
Ω(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(Equal(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
Ω(out).
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
})
})
})

View File

@@ -6,7 +6,7 @@ import (
. "github.com/onsi/gomega"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/internal/types"
)
var _ = Describe("Config", func() {

View File

@@ -3,8 +3,8 @@ package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
)
const StatsTotal = "total"
@@ -131,7 +131,24 @@ 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{}
)
@@ -149,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) {
@@ -156,7 +175,7 @@ func initMetric(name string, metric *prometheus.GaugeVec) {
l.With("name", name).Info("New Prometheus metric registered")
}
func Update(iml InstanceMetricsList) {
func UpdateInstances(iml InstanceMetricsList) {
for _, im := range iml.Metrics {
updateMetrics(im)
stats[im.HostName] = im.Stats
@@ -165,6 +184,15 @@ func Update(iml InstanceMetricsList) {
l.Debug("updated")
}
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
isRunning := 0

View File

@@ -6,16 +6,16 @@ import (
. "github.com/onsi/gomega"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
var _ = Describe("Metrics", func() {
BeforeEach(func() {
stats = make(OverallStats)
})
Context("Update / getStats", func() {
Context("UpdateInstances / getStats", func() {
It("generate correct stats", func() {
Update(InstanceMetricsList{[]InstanceMetrics{
UpdateInstances(InstanceMetricsList{[]InstanceMetrics{
{HostName: "foo", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(100),
DnsQueries: ptr.To(
@@ -74,7 +74,7 @@ var _ = Describe("Metrics", func() {
Ω(err).ShouldNot(HaveOccurred())
})
It("should provide correct results with faked values", func() {
Update(metrics)
UpdateInstances(metrics)
_, dns, blocked, malware, adult := StatsGraph()

View File

@@ -4,7 +4,7 @@ import (
"slices"
"strings"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
const labelTotal = "Total"

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"
)
@@ -509,6 +509,20 @@ func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
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.
func (m *MockClient) Setup() error {
m.ctrl.T.Helper()
@@ -539,10 +553,10 @@ func (mr *MockClientMockRecorder) Stats() *gomock.Call {
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*model.PutStatsConfigUpdateRequest, error) {
func (m *MockClient) StatsConfig() (*model.GetStatsConfigResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*model.PutStatsConfigUpdateRequest)
ret0, _ := ret[0].(*model.GetStatsConfigResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -568,6 +582,21 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
}
// TLSConfig mocks base method.
func (m *MockClient) TLSConfig() (*model.TlsConfig, error) {
m.ctrl.T.Helper()
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()

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.

View File

@@ -3,9 +3,9 @@ package sync
import (
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var (
@@ -236,6 +236,22 @@ var (
}
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
}
}
}
return nil
}
)
func syncFilterType(

View File

@@ -3,9 +3,9 @@ package sync
import (
"go.uber.org/zap"
"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/internal/client"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/types"
)
func setupActions(cfg *types.Config) (actions []syncAction) {
@@ -68,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
}

View File

@@ -16,18 +16,12 @@ import (
"github.com/gin-gonic/gin"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"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"
)
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()
@@ -43,35 +37,28 @@ func (w *worker) handleRoot(c *gin.Context) {
"Build": version.Build,
"SyncStatus": w.status(),
"Stats": map[string]any{
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries),
),
"Malware": malware,
"MalwarePercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries),
),
"Adult": adult,
"AdultPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries),
),
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,
"TotalMalware": total.NumReplacedSafebrowsing,
"TotalAdult": total.NumReplacedParental,
"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 (w *worker) handleFavicon(c *gin.Context) {
c.Data(http.StatusOK, "image/x-icon", favicon)
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) {
@@ -87,6 +74,24 @@ 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() {
@@ -100,9 +105,27 @@ func (w *worker) listenAndServe() {
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 != "" {
r.Use(gin.BasicAuth(map[string]string{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,
@@ -110,18 +133,7 @@ func (w *worker) listenAndServe() {
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.POST("/api/v1/clear-logs", w.handleClearLogs)
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()
}
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(static.Index())))
go func() {
var err error

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() {
@@ -28,11 +28,11 @@ func (w *worker) startScraping() {
func (w *worker) scrape() {
var iml metrics.InstanceMetricsList
iml.Metrics = append(iml.Metrics, w.getMetrics(w.cfg.Origin))
iml.Metrics = append(iml.Metrics, w.getMetrics(*w.cfg.Origin))
for _, replica := range w.cfg.Replicas {
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
}
metrics.Update(iml)
metrics.UpdateInstances(iml)
}
func (w *worker) getMetrics(inst types.AdGuardInstance) metrics.InstanceMetrics {

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

@@ -1,16 +1,8 @@
<html lang="en">
<head>
<title>AdGuardHome sync</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.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.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
{{- end }}
<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 () {
@@ -86,10 +78,13 @@
<body>
<div class="container-fluid px-4">
<div class="row">
<p class="h1">
AdGuardHome sync
<p class="h6">{{ .Version }} ({{ .Build }})</p>
</p>
<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">
@@ -165,16 +160,10 @@
</div>
</div>
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous">
</script>
<script src="lib/popper.js" ></script>
<script src="lib/bootstrap.js" ></script>
{{- if .Metrics }}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"ŝ
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC" crossorigin="anonymous">
</script>
<script src="lib/chart.js" ></script>
<script>
// Function to create minimal line charts
function createChart(canvasId, data) {

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

@@ -2,6 +2,8 @@ package sync
import (
"errors"
"fmt"
"net/http"
"runtime"
"sort"
"time"
@@ -9,12 +11,13 @@ import (
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"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/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"
)
@@ -98,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 {
@@ -152,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
@@ -180,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()
@@ -252,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()
@@ -269,10 +289,23 @@ 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
}
@@ -281,6 +314,7 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
withError = true
return
}
@@ -297,16 +331,16 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
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(
@@ -343,4 +377,5 @@ type origin struct {
safeSearch *model.SafeSearchConfig
profileInfo *model.ProfileInfo
safeBrowsing bool
tlsConfig *model.TlsConfig
}

View File

@@ -8,12 +8,12 @@ import (
. "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
"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/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() {
@@ -576,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{
@@ -594,12 +594,13 @@ 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()
@@ -614,6 +615,7 @@ var _ = Describe("Sync", func() {
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()
@@ -633,13 +635,14 @@ var _ = Describe("Sync", func() {
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()
@@ -653,6 +656,7 @@ var _ = Describe("Sync", func() {
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()
@@ -671,6 +675,7 @@ var _ = Describe("Sync", func() {
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() {
@@ -696,9 +701,10 @@ var _ = Describe("Sync", func() {
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,
}
}

View File

@@ -22,6 +22,7 @@ func NewFeatures(enabled bool) Features {
Services: enabled,
Filters: enabled,
Theme: enabled,
TLSConfig: false,
}
}
@@ -35,7 +36,8 @@ type Features struct {
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 weg UI theme" env:"FEATURES_THEME"`
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.
@@ -95,5 +97,8 @@ func (f *Features) collectDisabled() []string {
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

@@ -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"},
},
@@ -97,11 +102,11 @@ var _ = Describe("Types", 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))
})
})
})
@@ -124,6 +129,8 @@ var _ = Describe("Types", func() {
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"))
})
@@ -131,9 +138,15 @@ var _ = Describe("Types", 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
}

View File

@@ -4,7 +4,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/versions"
"github.com/bakito/adguardhome-sync/internal/versions"
)
var _ = Describe("Versions", func() {
@@ -14,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,139 +0,0 @@
package config_test
import (
"fmt"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/config"
)
var envVars = []string{
"FEATURES_GENERAL_SETTINGS",
"FEATURES_QUERY_LOG_CONFIG",
"FEATURES_STATS_CONFIG",
"FEATURES_CLIENT_SETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_THEME",
"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.Get().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.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().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.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
})
})
})
func verifyFeatures(cfg *config.AppConfig, value bool) {
Ω(cfg.Get().Features.GeneralSettings).Should(Equal(value))
Ω(cfg.Get().Features.QueryLogConfig).Should(Equal(value))
Ω(cfg.Get().Features.StatsConfig).Should(Equal(value))
Ω(cfg.Get().Features.ClientSettings).Should(Equal(value))
Ω(cfg.Get().Features.Services).Should(Equal(value))
Ω(cfg.Get().Features.Filters).Should(Equal(value))
Ω(cfg.Get().Features.DHCP.ServerConfig).Should(Equal(value))
Ω(cfg.Get().Features.DHCP.StaticLeases).Should(Equal(value))
Ω(cfg.Get().Features.DNS.ServerConfig).Should(Equal(value))
Ω(cfg.Get().Features.DNS.AccessLists).Should(Equal(value))
Ω(cfg.Get().Features.DNS.Rewrites).Should(Equal(value))
}

View File

@@ -1,146 +0,0 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
)
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, 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")
}
newVal, newOK := os.LookupEnv(newName)
if newOK {
return newVal, true
}
return old, oldOK
}
func checkDeprecatedReplicaEnvVar(oldPattern, 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,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,219 +0,0 @@
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 {
// Origin adguardhome instance
Origin AdGuardInstance `env:"ORIGIN" json:"origin" yaml:"origin"`
// One single replica adguardhome instance
Replica *AdGuardInstance `env:"REPLICA" json:"replica,omitempty" yaml:"replica,omitempty"`
// Multiple replica instances
Replicas []AdGuardInstance ` json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
Cron string `env:"CRON" json:"cron,omitempty" yaml:"cron,omitempty" documentation:"Cron expression for the sync interval"`
RunOnStart bool `env:"RUN_ON_START" json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" documentation:"Run the sung on startup"`
PrintConfigOnly bool `env:"PRINT_CONFIG_ONLY" json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" documentation:"Print current config only and stop the application"`
ContinueOnError bool `env:"CONTINUE_ON_ERROR" json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" documentation:"Continue sync on errors"`
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" 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"`
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

@@ -5,8 +5,8 @@
"customType": "regex",
"datasourceTemplate": "go",
"description": "Update toolbox tools in .toolbox.mk",
"fileMatch": [
"^\\.toolbox\\.mk$"
"managerFilePatterns": [
".toolbox.mk"
],
"matchStrings": [
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
@@ -16,8 +16,8 @@
"customType": "regex",
"datasourceTemplate": "github-releases",
"description": "Update github _VERSION Makefile",
"fileMatch": [
"^Makefile$"
"managerFilePatterns": [
"Makefile"
],
"matchStrings": [
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"

View File

@@ -16,6 +16,9 @@ replicas:
autoSetup: false
interfaceName: eth3
dhcpServerEnabled: false
requestHeaders:
FOO: bar
Client-ID: xxxx
cron: '*/15 * * * *'
runOnStart: true
printConfigOnly: true

View File

@@ -1,6 +1,6 @@
#!/bin/bash
set -e
echo "## AdGuardHome.yaml of latest replica" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
kubectl exec adguardhome-replica-latest -- cat /opt/adguardhome/conf/AdGuardHome.yaml >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "## AdGuardHome.yaml of latest replica" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
kubectl exec adguardhome-replica-latest -- cat /opt/adguardhome/conf/AdGuardHome.yaml >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

View File

@@ -1,3 +1,5 @@
#!/bin/bash
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/adguardhome-sync --timeout=1m
kubectl describe pod/adguardhome-sync
kubectl logs pod/adguardhome-sync

View File

@@ -153,7 +153,12 @@ filtering:
blocking_mode: default
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: []
rewrites:
- domain: foo.com
answer: 1.2.3.4
- domain: bar.com
answer: 1.2.3.3
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576

View File

@@ -1,11 +1,12 @@
replica:
versions:
- v0.107.40
- v0.107.43
- v0.107.63
- v0.107.67
- v0.107.68
- latest
mode: env
kubectl:
repository: bitnami/kubectl
tag: "1.30"
repository: bitnamisecure/kubectl
tag: "latest"

View File

@@ -1,5 +1,4 @@
//go:build tools
// +build tools
package tools