Compare commits

...

30 Commits

Author SHA1 Message Date
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
36 changed files with 930 additions and 180 deletions

View File

@@ -27,7 +27,11 @@ jobs:
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
@@ -45,7 +49,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

View File

@@ -162,6 +162,8 @@ linters:
disabled: true
- name: unused-receiver
disabled: true
- name: var-naming
disabled: true
staticcheck:
checks:
- 'all'

View File

@@ -6,10 +6,10 @@ 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
## 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
@@ -20,28 +20,28 @@ TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
TB_SEMVER ?= $(TB_LOCALBIN)/semver
## Tool Versions
# renovate: packageName=k8s.io/code-generator/cmd/deepcopy-gen
TB_DEEPCOPY_GEN_VERSION ?= v0.33.0
# renovate: packageName=sigs.k8s.io/controller-tools/cmd/controller-gen
TB_CONTROLLER_GEN_VERSION ?= v0.18.0
# 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/golangci/golangci-lint/v2
TB_GOLANGCI_LINT_VERSION ?= v2.2.2
# renovate: packageName=github.com/segmentio/golines
TB_GOLINES_VERSION ?= v0.12.2
# renovate: packageName=github.com/goreleaser/goreleaser/v2
TB_GORELEASER_VERSION ?= v2.8.2
TB_GORELEASER_VERSION ?= v2.11.0
# 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_MOCKGEN_VERSION ?= v0.5.2
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2
TB_OAPI_CODEGEN_VERSION ?= v2.4.1
# renovate: packageName=github.com/bakito/semver
TB_SEMVER_VERSION ?= v1.1.3
## 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: $(TB_CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(TB_CONTROLLER_GEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/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)
@@ -79,7 +79,7 @@ $(TB_SEMVER): $(TB_LOCALBIN)
.PHONY: tb.reset
tb.reset:
@rm -f \
$(TB_LOCALBIN)/deepcopy-gen \
$(TB_LOCALBIN)/controller-gen \
$(TB_LOCALBIN)/ginkgo \
$(TB_LOCALBIN)/gofumpt \
$(TB_LOCALBIN)/golangci-lint \
@@ -93,7 +93,7 @@ tb.reset:
.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 \
sigs.k8s.io/controller-tools/cmd/controller-gen@github.com/kubernetes-sigs/controller-tools \
mvdan.cc/gofumpt@github.com/mvdan/gofumpt \
github.com/golangci/golangci-lint/v2/cmd/golangci-lint \
github.com/segmentio/golines \

View File

@@ -9,10 +9,11 @@ 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=./pkg/types object
fmt: tb.golines tb.gofumpt
$(TB_GOLINES) --base-formatter="$(TB_GOFUMPT)" --max-len=120 --write-output .
@@ -73,7 +74,7 @@ kind-test:
@./testdata/e2e/bin/install-chart.sh
# renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.61
ADGUARD_HOME_VERSION ?= v0.107.63
model: tb.oapi-codegen
@mkdir -p tmp

View File

@@ -3,7 +3,9 @@
[![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 +45,7 @@ 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.
## Username / Password vs. Cookie
@@ -193,6 +195,7 @@ For Replicas replace `#` with the index number for the replica. E.g: `REPLICA#_U
| 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 |
@@ -203,6 +206,7 @@ For Replicas replace `#` with the index number for the replica. E.g: `REPLICA#_U
| 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 |
@@ -264,6 +268,8 @@ origin:
username: username
password: password
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
# requestHeaders: # Additional request headers
# AAA: bbb
# replicas instances
replicas:
@@ -278,6 +284,8 @@ replicas:
# 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>
# requestHeaders: # Additional request headers
# AAA: bbb
# Configure the sync API server, disabled if api port is 0
api:
@@ -320,9 +328,12 @@ features:
accessLists: true
rewrites: true
```
## Home Assistant AdGuard Home Add-on users
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
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
![show-disabled-ports](https://github.com/user-attachments/assets/1df5f352-37a2-4508-82ec-7f270087d0b4)
@@ -334,9 +345,12 @@ Don't forget to save and restart the add-on.
Depending on your setup, you may also need to disable SSL for the add-on.
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.
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.
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.
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

View File

@@ -30,9 +30,14 @@ func printEnvTags(t reflect.Type, prefix string) {
}
envTag := field.Tag.Get("env")
if envTag == "REPLICA" {
if envTag == "" {
switch field.Name {
case "Origin":
envTag = "ORIGIN"
case "Replica":
envTag = "REPLICA#"
}
}
combinedTag := envTag
if prefix != "" && envTag != "" {
combinedTag = prefix + "_" + envTag

18
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/bakito/adguardhome-sync
go 1.24.1
go 1.24.2
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/gin-gonic/gin v1.10.1
github.com/go-faker/faker/v4 v4.6.1
github.com/go-resty/resty/v2 v2.16.5
github.com/google/uuid v1.6.0
github.com/jinzhu/copier v0.4.0
@@ -14,14 +14,14 @@ require (
github.com/onsi/gomega v1.37.0
github.com/prometheus/client_golang v1.22.0
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/cobra v1.9.1
go.uber.org/mock v0.5.1
go.uber.org/mock v0.5.2
go.uber.org/zap v1.27.0
golang.org/x/mod v0.24.0
golang.org/x/mod v0.25.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.0
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
k8s.io/apimachinery v0.33.2
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
)
require (
@@ -42,7 +42,7 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416 // indirect
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // 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

32
go.sum
View File

@@ -28,10 +28,10 @@ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBv
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/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k=
github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY=
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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -54,8 +54,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
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/pprof v0.0.0-20250422154841-e1f9c1950416 h1:1/qwHx8P72glDXdyCKesJ+/c40x71SY4q2avOxJ2iYQ=
github.com/google/pprof v0.0.0-20250422154841-e1f9c1950416/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
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=
@@ -115,8 +115,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
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/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.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=
@@ -144,8 +144,8 @@ 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.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
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=
@@ -159,8 +159,8 @@ 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/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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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=
@@ -202,12 +202,12 @@ 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.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
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=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
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=

190
media/adguardhome-sync.svg Normal file
View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
viewBox="0 0 1000 1000"
id="svg3"
sodipodi:docname="adguardhome-sync.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
is_visible="true"
lpeversion="1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect18"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,54.646215,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,54.646212,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="skeletal"
id="path-effect13"
is_visible="true"
lpeversion="1"
pattern="m 78.30448,0.02132 c -1.76356,0.0996 -3.48821,0.55822 -5.06836,1.34765 L 5.80252,35.08773 c -18.554321,9.281429 -18.766576,9.393634 0,18.78124 l 67.4336,33.71876 c 8.62996,4.31519 18.78393,-1.9607 18.7832,-11.60938 v -11.5 h 213 c 6.62742,0 12,-5.372583 12,-12 v -16 c 0,-6.627417 -5.37258,-12 -12,-12 h -213 v -11.5 C 92.01887,5.52137 85.74946,-0.40162 78.30448,0.02132 Z"
copytype="repeated_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="100"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0"
pattern-nodetypes="cccccccssssccc" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect11"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,12,0,1 @ F,0,1,1,0,12,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect33"
is_visible="true"
lpeversion="1"
nodesatellites_param=""
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<linearGradient
id="swatch26"
inkscape:swatch="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop26" />
</linearGradient>
<linearGradient
id="swatch25"
inkscape:swatch="solid">
<stop
style="stop-color:#407b28;stop-opacity:1;"
offset="0"
id="stop25" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 500 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="1000 : 500 : 1"
inkscape:persp3d-origin="500 : 333.33333 : 1"
id="perspective6" />
<inkscape:path-effect
effect="skeletal"
id="path-effect13-9"
is_visible="true"
lpeversion="1"
pattern="m -435.41689,700.31394 -82.73956,27.87776 82.73956,31.49534 v -23.8092 l 434.54687,9.11337 v -33.59902 l -434.54687,9.11337 z"
copytype="repeated_stretched"
prop_scale="2"
scale_y_rel="false"
spacing="100"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0"
pattern-nodetypes="sccccccs" />
</defs>
<sodipodi:namedview
id="namedview3"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="0.70710678"
inkscape:cx="540.93669"
inkscape:cy="502.04582"
inkscape:window-width="1556"
inkscape:window-height="1360"
inkscape:window-x="1868"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg3" />
<path
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"
id="path1"
inkscape:label="shield" />
<path
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"
id="path2"
inkscape:label="half-shield" />
<path
fill="#ffffff"
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"
id="path3"
inkscape:label="checkmark"
style="display:none" />
<circle
style="display:none;fill:#ffffff;fill-opacity:0.5;stroke:#ffffff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:0.502604"
id="path18"
cx="-500"
cy="426"
r="400"
inkscape:label="badge"
transform="scale(-1,1)" />
<path
id="path12-8"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;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)"
inkscape:label="double-arrow"
inkscape:path-effect="#path-effect1"
inkscape:original-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" />
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -52,7 +52,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})

View File

@@ -1199,7 +1199,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.
@@ -4256,9 +4262,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

View File

@@ -36,6 +36,9 @@
"webURL": {
"format": "uri",
"type": "string"
},
"requestHeaders": {
"type": "object"
}
},
"type": "object"

View File

@@ -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 == "" {
@@ -112,7 +117,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

@@ -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()
@@ -232,6 +244,27 @@ var _ = Describe("Config", func() {
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
})
Context("Headers", func() {
It("have headers from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.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"))
})
})
})
})
})

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

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

@@ -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,6 +10,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/test/matchers"
"github.com/bakito/adguardhome-sync/pkg/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

@@ -539,10 +539,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
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -26,6 +26,8 @@ var (
index []byte
//go:embed favicon.ico
favicon []byte
//go:embed logo.svg
logo []byte
)
func (w *worker) handleSync(c *gin.Context) {
@@ -74,6 +76,10 @@ func (w *worker) handleFavicon(c *gin.Context) {
c.Data(http.StatusOK, "image/x-icon", favicon)
}
func (w *worker) handleLogo(c *gin.Context) {
c.Data(http.StatusOK, "image/svg+xml", logo)
}
func (w *worker) handleLogs(c *gin.Context) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
}
@@ -87,6 +93,10 @@ func (w *worker) handleStatus(c *gin.Context) {
c.JSON(http.StatusOK, w.status())
}
func (w *worker) handleHealthz(c *gin.Context) {
c.Status(w.healthz())
}
func (w *worker) listenAndServe() {
sl := l.With("port", w.cfg.API.Port)
if w.cfg.API.TLS.Enabled() {
@@ -100,9 +110,28 @@ 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)
group.GET("/favicon.ico", w.handleFavicon)
group.GET("/logo.svg", w.handleLogo)
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,
@@ -111,17 +140,6 @@ func (w *worker) listenAndServe() {
}
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()
}
go func() {
var err error

View File

@@ -1,6 +1,6 @@
<html lang="en">
<head>
<title>AdGuardHome sync</title>
<title>AdGuard Home sync</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
{{- if .DarkMode }}
@@ -86,10 +86,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">

190
pkg/sync/logo.svg Normal file
View File

@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
viewBox="0 0 1000 1000"
id="svg3"
sodipodi:docname="adguardhome-sync.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
is_visible="true"
lpeversion="1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect18"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,54.646215,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,54.646212,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="skeletal"
id="path-effect13"
is_visible="true"
lpeversion="1"
pattern="m 78.30448,0.02132 c -1.76356,0.0996 -3.48821,0.55822 -5.06836,1.34765 L 5.80252,35.08773 c -18.554321,9.281429 -18.766576,9.393634 0,18.78124 l 67.4336,33.71876 c 8.62996,4.31519 18.78393,-1.9607 18.7832,-11.60938 v -11.5 h 213 c 6.62742,0 12,-5.372583 12,-12 v -16 c 0,-6.627417 -5.37258,-12 -12,-12 h -213 v -11.5 C 92.01887,5.52137 85.74946,-0.40162 78.30448,0.02132 Z"
copytype="repeated_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="100"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0"
pattern-nodetypes="cccccccssssccc" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect11"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,12,0,1 @ F,0,1,1,0,12,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect33"
is_visible="true"
lpeversion="1"
nodesatellites_param=""
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<linearGradient
id="swatch26"
inkscape:swatch="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop26" />
</linearGradient>
<linearGradient
id="swatch25"
inkscape:swatch="solid">
<stop
style="stop-color:#407b28;stop-opacity:1;"
offset="0"
id="stop25" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 500 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="1000 : 500 : 1"
inkscape:persp3d-origin="500 : 333.33333 : 1"
id="perspective6" />
<inkscape:path-effect
effect="skeletal"
id="path-effect13-9"
is_visible="true"
lpeversion="1"
pattern="m -435.41689,700.31394 -82.73956,27.87776 82.73956,31.49534 v -23.8092 l 434.54687,9.11337 v -33.59902 l -434.54687,9.11337 z"
copytype="repeated_stretched"
prop_scale="2"
scale_y_rel="false"
spacing="100"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0"
pattern-nodetypes="sccccccs" />
</defs>
<sodipodi:namedview
id="namedview3"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="0.70710678"
inkscape:cx="540.93669"
inkscape:cy="502.04582"
inkscape:window-width="1556"
inkscape:window-height="1360"
inkscape:window-x="1868"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg3" />
<path
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"
id="path1"
inkscape:label="shield" />
<path
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"
id="path2"
inkscape:label="half-shield" />
<path
fill="#ffffff"
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"
id="path3"
inkscape:label="checkmark"
style="display:none" />
<circle
style="display:none;fill:#ffffff;fill-opacity:0.5;stroke:#ffffff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:0.502604"
id="path18"
cx="-500"
cy="426"
r="400"
inkscape:label="badge"
transform="scale(-1,1)" />
<path
id="path12-8"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;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)"
inkscape:label="double-arrow"
inkscape:path-effect="#path-effect1"
inkscape:original-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" />
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -28,7 +28,7 @@ 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))
}

View File

@@ -6,6 +6,7 @@ import (
"runtime"
"sort"
"time"
"regexp"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
@@ -20,7 +21,10 @@ import (
"github.com/bakito/adguardhome-sync/version"
)
var l = log.GetLogger("sync")
var (
l = log.GetLogger("sync")
fixVersionCompareRegExp = regexp.MustCompile(`[^0-9.]`)
)
// Sync config from origin to replica.
func Sync(cfg *types.Config) error {
@@ -100,7 +104,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 {
@@ -120,6 +124,22 @@ func (w *worker) status() *syncStatus {
return syncStatus
}
func (w *worker) healthz() int {
status := w.status()
if status.Origin.Status != "success" {
return 503
}
for _, replica := range status.Replicas {
if replica.Status != "success" {
return 503
}
}
return 200
}
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
@@ -158,7 +178,7 @@ func (w *worker) sync() {
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
@@ -293,6 +313,9 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
return
}
replicaStatus.Version = fixVersionCompareRegExp.ReplaceAllString(replicaStatus.Version, "")
o.status.Version = fixVersionCompareRegExp.ReplaceAllString(o.status.Version, "")
rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {

View File

@@ -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{

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

@@ -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,3 +1,5 @@
// Package types
// +kubebuilder:object:generate=true
package types
import (
@@ -19,17 +21,17 @@ const (
// +k8s:deepcopy-gen=true
type Config struct {
// Origin adguardhome instance
Origin AdGuardInstance `env:"ORIGIN" json:"origin" yaml:"origin"`
Origin *AdGuardInstance `json:"origin" yaml:"origin"`
// One single replica adguardhome instance
Replica *AdGuardInstance `env:"REPLICA" json:"replica,omitempty" yaml:"replica,omitempty"`
Replica *AdGuardInstance `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"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" documentation:"Cron expression for the sync interval" env:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" documentation:"Run the sung on startup" env:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" documentation:"Print current config only and stop the application" env:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" documentation:"Continue sync on errors" env:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty"`
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
}
// API configuration.
@@ -149,6 +151,7 @@ type AdGuardInstance struct {
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"`

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"},
},
@@ -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

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

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

@@ -2,6 +2,7 @@ replica:
versions:
- v0.107.40
- v0.107.43
- v0.107.63
- latest
mode: env