`plugNPiN.piholeOptions.targetDomain`
| If provided, a CNAME record will be created **instead** of a DNS record | |
-
-
-## Usage
-
-### Docker Compose
-
-It is **highly recommended** to use a Docker socket proxy to avoid giving the container direct access to the Docker daemon. This improves security by limiting the container's privileges.
-
-#### Recommended: Using a Docker Socket Proxy
-
-```yaml
-services:
- socket-proxy:
- image: lscr.io/linuxserver/socket-proxy:latest
- container_name: socket-proxy
- environment:
- # Allow access to the container list
- - CONTAINERS=1
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock:ro
- read_only: true
- tmpfs:
- - /run
-
- plugnpin:
- image: ghcr.io/deepspace2/plugnpin:latest
- container_name: plugnpin
- depends_on:
- - socket-proxy
- environment:
- - DOCKER_HOST=tcp://socket-proxy:2375
- - NGINX_PROXY_MANAGER_HOST=...
- - NGINX_PROXY_MANAGER_USERNAME=...
- - NGINX_PROXY_MANAGER_PASSWORD=...
- - PIHOLE_HOST=...
- - PIHOLE_PASSWORD=...
- restart: unless-stopped
-```
-
-#### Not Recommended: Mounting the Docker Socket
-
-```yaml
-services:
- plugnpin:
- image: ghcr.io/deepspace2/plugnpin:latest
- container_name: plugnpin
- environment:
- - NGINX_PROXY_MANAGER_HOST=...
- - NGINX_PROXY_MANAGER_USERNAME=...
- - NGINX_PROXY_MANAGER_PASSWORD=...
- - PIHOLE_HOST=...
- - PIHOLE_PASSWORD=...
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- restart: unless-stopped
-```
-
-## Contributing
-
-Contributions are very welcome! If you have a feature request, bug report, or want to contribute yourself, please feel free to open an issue or submit a pull request.
-
-*[NPM]: Nginx Proxy Manager
+See [Per Container Configuration ➔ Pi-Hole](./configuration.md#pi-hole).
diff --git a/docs/usage.md b/docs/usage.md
new file mode 100644
index 0000000..1e57fcb
--- /dev/null
+++ b/docs/usage.md
@@ -0,0 +1,60 @@
+# Usage
+
+## CLI Flags
+
+| Flag {: style="width:35%" } | Description |
+| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| `--dry-run`, `-d` | Simulates the process of adding DNS/CNAME records and proxy hosts without making any actual changes to Pi-Hole or Nginx Proxy Manager. |
+
+## Docker Compose
+
+It is **highly recommended** to use a Docker socket proxy to avoid giving the container direct access to the Docker daemon. This improves security by limiting the container's privileges.
+
+=== "Recommended: Using a Docker Socket Proxy"
+
+ ```yaml
+ services:
+ socket-proxy:
+ image: lscr.io/linuxserver/socket-proxy:latest
+ container_name: socket-proxy
+ environment:
+ # Allow access to the container list
+ - CONTAINERS=1
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ read_only: true
+ tmpfs:
+ - /run
+
+ plugnpin:
+ image: ghcr.io/deepspace2/plugnpin:latest
+ container_name: plugnpin
+ depends_on:
+ - socket-proxy
+ environment:
+ - DOCKER_HOST=tcp://socket-proxy:2375
+ - NGINX_PROXY_MANAGER_HOST=...
+ - NGINX_PROXY_MANAGER_USERNAME=...
+ - NGINX_PROXY_MANAGER_PASSWORD=...
+ - PIHOLE_HOST=...
+ - PIHOLE_PASSWORD=...
+ restart: unless-stopped
+ ```
+
+=== "Not Recommended: Mounting the Docker Socket"
+
+ ```yaml
+ services:
+ plugnpin:
+ image: ghcr.io/deepspace2/plugnpin:latest
+ container_name: plugnpin
+ environment:
+ - NGINX_PROXY_MANAGER_HOST=...
+ - NGINX_PROXY_MANAGER_USERNAME=...
+ - NGINX_PROXY_MANAGER_PASSWORD=...
+ - PIHOLE_HOST=...
+ - PIHOLE_PASSWORD...
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
+ restart: unless-stopped
+ ```
diff --git a/e2e_tests/e2e_test.go b/e2e_tests/e2e_test.go
index f10006b..97fbbe3 100644
--- a/e2e_tests/e2e_test.go
+++ b/e2e_tests/e2e_test.go
@@ -128,7 +128,7 @@ func startRequiredContainers(t *testing.T, ctx context.Context, dockerCli *docke
}
func setClients(t *testing.T, containers []Container) (*docker.Client, *pihole.Client, *npm.Client, *adguardhome.Client) {
- dockerClient, err := docker.NewClient()
+ dockerClient, err := docker.NewClient("")
if err != nil {
t.Fatalf("Failed to create docker client: %v", err)
}
@@ -308,7 +308,7 @@ func TestE2E(t *testing.T) {
time.Sleep(2 * time.Second)
proc := processor.New(
- dockerClient,
+ map[string]*docker.Client{dockerClient.Host: dockerClient},
adguardHomeClient,
piholeClient,
npmClient,
diff --git a/main.go b/main.go
index 85dd6f0..df2c26b 100644
--- a/main.go
+++ b/main.go
@@ -9,10 +9,7 @@ import (
"syscall"
"github.com/deepspace2/plugnpin/pkg/cli"
- "github.com/deepspace2/plugnpin/pkg/clients/adguardhome"
- "github.com/deepspace2/plugnpin/pkg/clients/docker"
- "github.com/deepspace2/plugnpin/pkg/clients/npm"
- "github.com/deepspace2/plugnpin/pkg/clients/pihole"
+ "github.com/deepspace2/plugnpin/pkg/clients"
"github.com/deepspace2/plugnpin/pkg/config"
"github.com/deepspace2/plugnpin/pkg/logging"
"github.com/deepspace2/plugnpin/pkg/processor"
@@ -20,18 +17,6 @@ import (
var log = logging.GetLogger()
-func shutdown(cancelCtx context.CancelFunc, wg *sync.WaitGroup) {
- shutdownChan := make(chan os.Signal, 1)
- signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM)
-
- <-shutdownChan
-
- log.Info("Shutdown signal received, exiting gracefully.")
- cancelCtx()
- wg.Wait()
- log.Info("Shutdown complete.")
-}
-
func main() {
cliFlags := cli.ParseFlags()
@@ -51,40 +36,12 @@ func main() {
log.Info(fmt.Sprintf("Will run every %v", conf.RunInterval))
}
- var adguardHomeClient *adguardhome.Client
- var piholeClient *pihole.Client
- var npmClient *npm.Client
-
- if !cliFlags.DryRun {
- if !conf.PiholeDisabled {
- piholeClient = pihole.NewClient(conf.PiholeHost)
- err = piholeClient.Login(conf.PiholePassword)
- if err != nil {
- log.Error("Failed to login to Pi-Hole", "error", err)
- os.Exit(1)
- }
- }
-
- if !conf.AdguardHomeDisabled {
- adguardHomeClient = adguardhome.NewClient(conf.AdguardHomeHost, conf.AdguardHomeUsername, conf.AdguardHomePassword)
- }
-
- npmClient = npm.NewClient(conf.NpmHost, conf.NpmUsername, conf.NpmPassword)
- err = npmClient.Login()
- if err != nil {
- log.Error("Failed to login to Nginx Proxy Manager", "error", err)
- os.Exit(1)
- }
- }
-
- dockerClient, err := docker.NewClient()
+ dockerClients, adguardHomeClient, piholeClient, npmClient, err := clients.GetClients(cliFlags, conf)
if err != nil {
- log.Error("Failed to create docker client", "error", err)
os.Exit(1)
}
- defer dockerClient.Close()
- proc := processor.New(dockerClient, adguardHomeClient, piholeClient, npmClient, cliFlags.DryRun)
+ proc := processor.New(dockerClients, adguardHomeClient, piholeClient, npmClient, cliFlags.DryRun)
if conf.RunInterval == 0 {
log.Info("RUN_INTERVAL is 0, will run once")
@@ -109,3 +66,15 @@ func main() {
shutdown(cancel, &wg)
}
+
+func shutdown(cancelCtx context.CancelFunc, wg *sync.WaitGroup) {
+ shutdownChan := make(chan os.Signal, 1)
+ signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM)
+
+ <-shutdownChan
+
+ log.Info("Shutdown signal received, exiting gracefully.")
+ cancelCtx()
+ wg.Wait()
+ log.Info("Shutdown complete.")
+}
diff --git a/mkdocs.yml b/mkdocs.yml
index bc310e1..0c69294 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -3,10 +3,11 @@
site_name: PlugNPiN
site_url: https://deepspace2.github.io/plugnpin
repo_url: https://github.com/deepspace2/plugnpin
-extra_css:
- - css/anchor-jump-highlight.css
-extra_javascript:
- - js/anchor-jump-highlight.js
+nav:
+ - About: index.md
+ - Configuration: configuration.md
+ - Usage: usage.md
+ - Contributing: contributing.md
theme:
name: material
custom_dir: docs/theme/overrides
@@ -15,6 +16,9 @@ theme:
features:
- content.code.copy
- content.tooltips
+ - navigation.footer
+ - navigation.instant
+ - navigation.instant.progress
- navigation.top
- search.highlight
- search.share
@@ -45,14 +49,12 @@ extra:
alias: true
provider: mike
markdown_extensions:
- - admonition
- abbr
- attr_list
- - pymdownx.details
- - pymdownx.inlinehilite
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- - pymdownx.snippets
+ - pymdownx.tabbed:
+ alternate_style: true
- pymdownx.superfences
diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go
index 57e6970..b39eb41 100644
--- a/pkg/cli/cli.go
+++ b/pkg/cli/cli.go
@@ -4,14 +4,14 @@ import (
flag "github.com/spf13/pflag"
)
-type f struct {
+type Flags struct {
DryRun bool
}
-var flags f = f{}
+var flags = Flags{}
-func ParseFlags() f {
- flag.BoolVarP(&flags.DryRun, "dry-run", "d", false, "Simulates the process of adding DNS records and proxy hosts without making any actual changes to Pi-Hole or Nginx Proxy Manager.")
+func ParseFlags() Flags {
+ flag.BoolVarP(&flags.DryRun, "dry-run", "d", false, "Simulates the process of adding DNS records and proxy hosts without applying changes to Pi-Hole, AdGuard Home or Nginx Proxy Manager.")
flag.Parse()
return flags
}
diff --git a/pkg/clients/adguardhome/adguardhome.go b/pkg/clients/adguardhome/adguardhome.go
index fdde405..4c67214 100644
--- a/pkg/clients/adguardhome/adguardhome.go
+++ b/pkg/clients/adguardhome/adguardhome.go
@@ -7,7 +7,7 @@ import (
"fmt"
"net/http"
- "github.com/deepspace2/plugnpin/pkg/clients"
+ "github.com/deepspace2/plugnpin/pkg/clients/common"
"github.com/deepspace2/plugnpin/pkg/logging"
)
@@ -34,7 +34,7 @@ func NewClient(baseURL, username, password string) *Client {
}
func (ad *Client) GetDnsRewrites() (DnsRewrites, error) {
- dnsRewritesResponseString, _, err := clients.Get(&ad.Client, ad.baseURL+"/rewrite/list", headers)
+ dnsRewritesResponseString, _, err := common.Get(&ad.Client, ad.baseURL+"/rewrite/list", headers)
if err != nil {
return nil, err
}
@@ -65,7 +65,7 @@ func (ad *Client) AddDnsRewrite(domain, ip string) error {
return err
}
payloadString := string(payload)
- _, statusCode, err := clients.Post(&ad.Client, ad.baseURL+"/rewrite/add", headers, &payloadString)
+ _, statusCode, err := common.Post(&ad.Client, ad.baseURL+"/rewrite/add", headers, &payloadString)
if err != nil {
return err
}
@@ -83,7 +83,7 @@ func (ad *Client) DeleteDnsRewrite(domain, ip string) error {
return err
}
payloadString := string(payload)
- _, statusCode, err := clients.Post(&ad.Client, ad.baseURL+"/rewrite/delete", headers, &payloadString)
+ _, statusCode, err := common.Post(&ad.Client, ad.baseURL+"/rewrite/delete", headers, &payloadString)
if err != nil {
return err
}
diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go
new file mode 100644
index 0000000..79668de
--- /dev/null
+++ b/pkg/clients/clients.go
@@ -0,0 +1,56 @@
+package clients
+
+import (
+ "github.com/deepspace2/plugnpin/pkg/cli"
+ "github.com/deepspace2/plugnpin/pkg/clients/adguardhome"
+ "github.com/deepspace2/plugnpin/pkg/clients/docker"
+ "github.com/deepspace2/plugnpin/pkg/clients/npm"
+ "github.com/deepspace2/plugnpin/pkg/clients/pihole"
+ "github.com/deepspace2/plugnpin/pkg/config"
+ "github.com/deepspace2/plugnpin/pkg/logging"
+)
+
+var log = logging.GetLogger()
+
+func GetClients(cliFlags cli.Flags, conf *config.Config) (map[string]*docker.Client, *adguardhome.Client, *pihole.Client, *npm.Client, error) {
+ var adguardHomeClient *adguardhome.Client
+ var piholeClient *pihole.Client
+ var npmClient *npm.Client
+
+ if !cliFlags.DryRun {
+ if !conf.PiholeDisabled {
+ piholeClient = pihole.NewClient(conf.PiholeHost)
+ err := piholeClient.Login(conf.PiholePassword)
+ if err != nil {
+ log.Error("Failed to login to Pi-Hole", "error", err)
+ return nil, nil, nil, nil, err
+ }
+ }
+
+ if !conf.AdguardHomeDisabled {
+ adguardHomeClient = adguardhome.NewClient(conf.AdguardHomeHost, conf.AdguardHomeUsername, conf.AdguardHomePassword)
+ }
+
+ npmClient = npm.NewClient(conf.NpmHost, conf.NpmUsername, conf.NpmPassword)
+ err := npmClient.Login()
+ if err != nil {
+ log.Error("Failed to login to Nginx Proxy Manager", "error", err)
+ return nil, nil, nil, nil, err
+ }
+ }
+
+ dockerClients := make(map[string]*docker.Client)
+ if len(conf.DockerHosts) == 0 {
+ conf.DockerHosts = append(conf.DockerHosts, conf.DockerHost)
+ }
+ for _, host := range conf.DockerHosts {
+ dockerClient, err := docker.NewClient(host)
+ if err != nil {
+ log.Error("Failed to create docker client", "host", host, "error", err)
+ continue
+ }
+ dockerClients[dockerClient.Host] = dockerClient
+ }
+
+ return dockerClients, adguardHomeClient, piholeClient, npmClient, nil
+}
diff --git a/pkg/clients/common.go b/pkg/clients/common/common.go
similarity index 99%
rename from pkg/clients/common.go
rename to pkg/clients/common/common.go
index afa7702..7a73e74 100644
--- a/pkg/clients/common.go
+++ b/pkg/clients/common/common.go
@@ -1,4 +1,4 @@
-package clients
+package common
import (
"io"
diff --git a/pkg/clients/docker/docker.go b/pkg/clients/docker/docker.go
index 966da5d..85337fc 100644
--- a/pkg/clients/docker/docker.go
+++ b/pkg/clients/docker/docker.go
@@ -46,13 +46,15 @@ const (
var labels []string = []string{IpLabel, UrlLabel}
-type Client struct {
- *dockerSdk.Client
-}
-
-func NewClient() (*Client, error) {
- client, err := dockerSdk.New(context.Background())
- return &Client{client}, err
+func NewClient(host string) (*Client, error) {
+ client, err := dockerSdk.New(context.Background(), dockerSdk.WithDockerHost(host))
+ var displayHost string
+ if host == "" {
+ displayHost = "local"
+ } else {
+ displayHost = host
+ }
+ return &Client{Client: client, Host: host, DisplayHost: displayHost}, err
}
func (d *Client) GetRelevantContainers() ([]container.Summary, error) {
@@ -61,7 +63,7 @@ func (d *Client) GetRelevantContainers() ([]container.Summary, error) {
f.Add("label", label)
}
- log.Info(fmt.Sprintf("Getting containers with labels: %v", strings.Join(labels, ", ")))
+ log.Info(fmt.Sprintf("Getting containers with labels: %v", strings.Join(labels, ", ")), "host", d.DisplayHost)
return d.ContainerList(
context.Background(),
diff --git a/pkg/clients/docker/events.go b/pkg/clients/docker/events.go
index fdd3585..ef17291 100644
--- a/pkg/clients/docker/events.go
+++ b/pkg/clients/docker/events.go
@@ -5,22 +5,17 @@ import (
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
- dockerClient "github.com/docker/docker/client"
)
-func Listen(ctx context.Context, handler func(events.Message)) error {
- c, err := dockerClient.NewClientWithOpts(dockerClient.WithHostFromEnv())
- if err != nil {
- return err
- }
- defer c.Close()
-
+func Listen(ctx context.Context, dockerClient *Client, handler func(events.Message)) error {
f := filters.NewArgs()
f.Add("type", "container")
f.Add("event", ContainerEvent.Start.String())
f.Add("event", ContainerEvent.Die.String())
- log.Info("Listening for Docker events...")
+ log.Info("Listening for Docker events...", "host", dockerClient.DisplayHost)
+
+ c, _ := dockerClient.Client.Client()
messages, errs := c.Events(ctx, events.ListOptions{
Filters: f,
@@ -29,13 +24,13 @@ func Listen(ctx context.Context, handler func(events.Message)) error {
for {
select {
case <-ctx.Done():
- log.Info("Stopping stream of Docker events")
+ log.Info("Stopping stream of Docker events", "host", dockerClient.DisplayHost)
return ctx.Err()
case event := <-messages:
handler(event)
case err := <-errs:
if err != nil {
- log.Error("Failed to receive event", "error", err)
+ log.Error("Failed to receive event", "host", dockerClient.DisplayHost, "error", err)
}
return err
}
diff --git a/pkg/clients/docker/types.go b/pkg/clients/docker/types.go
index 6493fe8..b3902c6 100644
--- a/pkg/clients/docker/types.go
+++ b/pkg/clients/docker/types.go
@@ -1,10 +1,18 @@
package docker
+import dockerSdk "github.com/docker/go-sdk/client"
+
const (
start = "start"
die = "die"
)
+type Client struct {
+ *dockerSdk.Client
+ DisplayHost string
+ Host string
+}
+
type EventType string
type ContainerEventEnum struct {
diff --git a/pkg/clients/npm/npm.go b/pkg/clients/npm/npm.go
index f0865de..d598026 100644
--- a/pkg/clients/npm/npm.go
+++ b/pkg/clients/npm/npm.go
@@ -10,7 +10,7 @@ import (
"sync"
"time"
- "github.com/deepspace2/plugnpin/pkg/clients"
+ "github.com/deepspace2/plugnpin/pkg/clients/common"
"github.com/deepspace2/plugnpin/pkg/logging"
)
@@ -58,7 +58,7 @@ func (n *Client) Login() error {
return err
}
payloadString := string(payloadBytes)
- loginResponseString, statusCode, err := clients.Post(&n.Client, n.baseURL+"/tokens", n.headers, &payloadString)
+ loginResponseString, statusCode, err := common.Post(&n.Client, n.baseURL+"/tokens", n.headers, &payloadString)
if err != nil {
return err
}
@@ -124,11 +124,11 @@ func (n *Client) makeRequest(method, url string, payload *string) (string, int,
doRequest := func() (string, int, error) {
switch method {
case http.MethodGet:
- return clients.Get(&n.Client, url, n.headers)
+ return common.Get(&n.Client, url, n.headers)
case http.MethodPost:
- return clients.Post(&n.Client, url, n.headers, payload)
+ return common.Post(&n.Client, url, n.headers, payload)
case http.MethodDelete:
- return clients.Delete(&n.Client, url, n.headers)
+ return common.Delete(&n.Client, url, n.headers)
default:
return "", 0, fmt.Errorf("unsupported http method: %s", method)
}
diff --git a/pkg/clients/pihole/pihole.go b/pkg/clients/pihole/pihole.go
index 177db03..012b835 100644
--- a/pkg/clients/pihole/pihole.go
+++ b/pkg/clients/pihole/pihole.go
@@ -8,7 +8,7 @@ import (
"os"
"strings"
- "github.com/deepspace2/plugnpin/pkg/clients"
+ "github.com/deepspace2/plugnpin/pkg/clients/common"
"github.com/deepspace2/plugnpin/pkg/logging"
)
@@ -36,7 +36,7 @@ func NewClient(baseURL string) *Client {
func (p *Client) Login(password string) error {
loginPayload := fmt.Sprintf(`{"password": "%v"}`, password)
- loginResponseString, statusCode, err := clients.Post(&p.Client, p.baseURL+"/auth", headers, &loginPayload)
+ loginResponseString, statusCode, err := common.Post(&p.Client, p.baseURL+"/auth", headers, &loginPayload)
if err != nil {
return err
}
@@ -73,7 +73,7 @@ func (p *Client) GetDnsRecords() (DnsRecords, error) {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- configResponseString, _, err := clients.Get(&p.Client, p.baseURL+"/config", headers)
+ configResponseString, _, err := common.Get(&p.Client, p.baseURL+"/config", headers)
if err != nil {
return nil, err
}
@@ -123,7 +123,7 @@ func (p *Client) AddDnsRecord(domain, ip string) error {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- resp, statusCode, err := clients.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
+ resp, statusCode, err := common.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
if err != nil {
return err
}
@@ -172,7 +172,7 @@ func (p *Client) DeleteDnsRecord(domain string) error {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- resp, statusCode, err := clients.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
+ resp, statusCode, err := common.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
if err != nil {
return err
}
@@ -210,7 +210,7 @@ func (p *Client) getCNameRecords() (CNameRecords, error) {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- configResponseString, _, err := clients.Get(&p.Client, p.baseURL+"/config", headers)
+ configResponseString, _, err := common.Get(&p.Client, p.baseURL+"/config", headers)
if err != nil {
return nil, err
}
@@ -260,7 +260,7 @@ func (p *Client) AddCNameRecord(domain, target string) error {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- resp, statusCode, err := clients.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
+ resp, statusCode, err := common.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
if err != nil {
return err
}
@@ -309,7 +309,7 @@ func (p *Client) DeleteCNameRecord(domain, target string) error {
os.Exit(1)
}
headers["X-FTL-SID"] = p.sid
- resp, statusCode, err := clients.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
+ resp, statusCode, err := common.Patch(&p.Client, p.baseURL+"/config", headers, string(payloadString))
if err != nil {
return err
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 6e84c92..668a4a1 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -30,6 +30,7 @@ type Config struct {
PiholePassword string `env:"PIHOLE_PASSWORD"`
DockerHost string `env:"DOCKER_HOST"`
+ DockerHosts []string `env:"DOCKER_HOSTS"`
RunInterval time.Duration `env:"RUN_INTERVAL" envDefault:"1h"`
}
diff --git a/pkg/processor/processor.go b/pkg/processor/processor.go
index b0c32b0..58a0706 100644
--- a/pkg/processor/processor.go
+++ b/pkg/processor/processor.go
@@ -19,16 +19,16 @@ import (
var log = logging.GetLogger()
type Processor struct {
- dockerClient *docker.Client
+ dockerClients map[string]*docker.Client
adguardHomeClient *adguardhome.Client
piholeClient *pihole.Client
npmClient *npm.Client
dryRun bool
}
-func New(dockerClient *docker.Client, adguardHomeClient *adguardhome.Client, piholeClient *pihole.Client, npmClient *npm.Client, dryRun bool) *Processor {
+func New(dockerClients map[string]*docker.Client, adguardHomeClient *adguardhome.Client, piholeClient *pihole.Client, npmClient *npm.Client, dryRun bool) *Processor {
return &Processor{
- dockerClient: dockerClient,
+ dockerClients: dockerClients,
adguardHomeClient: adguardHomeClient,
piholeClient: piholeClient,
npmClient: npmClient,
@@ -60,31 +60,37 @@ func (p *Processor) RunScheduled(ctx context.Context, interval time.Duration) {
}
func (p *Processor) ListenForEvents(ctx context.Context) {
- err := docker.Listen(ctx, func(event events.Message) {
- p.handleDockerEvent(event)
- })
-
- if err != nil && err != context.Canceled {
- log.Error("Docker event listener stopped", "error", err)
+ for _, client := range p.dockerClients {
+ go func(c *docker.Client) {
+ log.Info("Starting event listener", "host", c.DisplayHost)
+ err := docker.Listen(ctx, c, func(event events.Message) {
+ p.handleDockerEvent(event, c.DisplayHost)
+ })
+ if err != nil && err != context.Canceled {
+ log.Error("Docker event listener stopped", "host", c.DisplayHost, "error", err)
+ }
+ }(client)
}
}
func (p *Processor) RunOnce() {
- containers, err := p.dockerClient.GetRelevantContainers()
- if err != nil {
- log.Error("Failed to get containers", "error", err)
- return
- }
+ for _, dockerClient := range p.dockerClients {
+ containers, err := dockerClient.GetRelevantContainers()
+ if err != nil {
+ log.Error("Failed to get containers", "host", dockerClient.DisplayHost, "error", err)
+ continue
+ }
- log.Info(fmt.Sprintf("Found %v containers", len(containers)))
+ log.Info(fmt.Sprintf("Found %v containers", len(containers)), "host", dockerClient.DisplayHost)
- for _, container := range containers {
- p.preprocessContainer(container)
+ for _, container := range containers {
+ p.preprocessContainer(container, dockerClient.DisplayHost)
+ }
}
log.Info("Done")
}
-func (p *Processor) preprocessContainer(container container.Summary) {
+func (p *Processor) preprocessContainer(container container.Summary, host string) {
parsedContainerName := docker.GetParsedContainerName(container)
ip, url, port, opts, err := docker.GetValuesFromLabels(container.Labels)
@@ -97,13 +103,13 @@ func (p *Processor) preprocessContainer(container container.Summary) {
}
return
}
- p.processContainer(docker.ContainerEvent.Start, parsedContainerName, ip, url, port, opts)
+ p.processContainer(docker.ContainerEvent.Start, host, parsedContainerName, ip, url, port, opts)
}
-func (p *Processor) handleDockerEvent(event events.Message) {
+func (p *Processor) handleDockerEvent(event events.Message, host string) {
containerName, ok := event.Actor.Attributes["name"]
if !ok {
- log.Info(fmt.Sprintf("Skipping event for container with no name: %v", event.Actor.ID))
+ log.Info(fmt.Sprintf("Skipping event for container with no name: %v", event.Actor.ID), "host", host)
return
}
@@ -114,15 +120,15 @@ func (p *Processor) handleDockerEvent(event events.Message) {
// This is not an error, it just means the container is not relevant for us
return
case *errors.MalformedIPLabelError, *errors.InvalidSchemeError:
- log.Error("Failed to handle event for container", "container", containerName, "error", err)
+ log.Error("Failed to handle event for container", "host", host, "container", containerName, "error", err)
}
return
}
containerEvent, _ := docker.ContainerEvent.ParseString(string(event.Action))
- p.processContainer(containerEvent, containerName, ip, url, port, opts)
+ p.processContainer(containerEvent, host, containerName, ip, url, port, opts)
}
-func (p *Processor) handleAdguardHome(containerEvent docker.EventType, containerName, url, ip string, adguardHomeOptions adguardhome.AdguardHomeOptions) {
+func (p *Processor) handleAdguardHome(host string, containerEvent docker.EventType, containerName, url, ip string, adguardHomeOptions adguardhome.AdguardHomeOptions) {
if p.adguardHomeClient != nil {
if adguardHomeOptions.TargetDomain != "" {
// quick "workaround" for the fact that adguard unifies "local DNS records" and "CNAME records"
@@ -131,57 +137,57 @@ func (p *Processor) handleAdguardHome(containerEvent docker.EventType, container
switch containerEvent {
case docker.ContainerEvent.Start:
- log.Info("Adding a DNS rewrite to AdGuard Home", "container", containerName, "domain", url, "answer", ip)
+ log.Info("Adding a DNS rewrite to AdGuard Home", "host", host, "container", containerName, "domain", url, "answer", ip)
err := p.adguardHomeClient.AddDnsRewrite(url, ip)
if err != nil {
- log.Error("Failed to add a DNS rewrite to AdGuard Home", "container", containerName, "domain", url, "answer", ip, "error", err)
+ log.Error("Failed to add a DNS rewrite to AdGuard Home", "host", host, "container", containerName, "domain", url, "answer", ip, "error", err)
}
case docker.ContainerEvent.Die:
- log.Info("Deleting DNS rewrite from AdGuard Home", "container", containerName, "domain", url)
+ log.Info("Deleting DNS rewrite from AdGuard Home", "host", host, "container", containerName, "domain", url)
err := p.adguardHomeClient.DeleteDnsRewrite(url, ip)
if err != nil {
- log.Error("Failed to delete DNS rewrite from AdGuard Home", "container", containerName, "domain", url, "error", err)
+ log.Error("Failed to delete DNS rewrite from AdGuard Home", "host", host, "container", containerName, "domain", url, "error", err)
}
}
}
}
-func (p *Processor) handlePiHole(containerEvent docker.EventType, containerName, url, ip string, piholeOptions pihole.PiHoleOptions) {
+func (p *Processor) handlePiHole(host string, containerEvent docker.EventType, containerName, url, ip string, piholeOptions pihole.PiHoleOptions) {
if p.piholeClient != nil {
switch containerEvent {
case docker.ContainerEvent.Start:
if piholeOptions.TargetDomain == "" {
- log.Info("Adding a local DNS record to Pi-Hole", "container", containerName, "url", url, "ip", ip)
+ log.Info("Adding a local DNS record to Pi-Hole", "host", host, "container", containerName, "url", url, "ip", ip)
err := p.piholeClient.AddDnsRecord(url, ip)
if err != nil {
- log.Error("Failed to add a local DNS record to Pi-Hole", "container", containerName, "url", url, "ip", ip, "error", err)
+ log.Error("Failed to add a local DNS record to Pi-Hole", "host", host, "container", containerName, "url", url, "ip", ip, "error", err)
}
} else {
- log.Info("Adding a local CNAME record to Pi-Hole", "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain)
+ log.Info("Adding a local CNAME record to Pi-Hole", "host", host, "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain)
err := p.piholeClient.AddCNameRecord(url, piholeOptions.TargetDomain)
if err != nil {
- log.Error("Failed to add a local CNAME record to Pi-Hole", "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain, "error", err)
+ log.Error("Failed to add a local CNAME record to Pi-Hole", "host", host, "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain, "error", err)
}
}
case docker.ContainerEvent.Die:
if piholeOptions.TargetDomain == "" {
- log.Info("Deleting local DNS record from Pi-Hole", "container", containerName, "url", url)
+ log.Info("Deleting local DNS record from Pi-Hole", "host", host, "container", containerName, "url", url)
err := p.piholeClient.DeleteDnsRecord(url)
if err != nil {
- log.Error("Failed to delete local DNS record from Pi-Hole", "container", containerName, "url", url, "error", err)
+ log.Error("Failed to delete local DNS record from Pi-Hole", "host", host, "container", containerName, "url", url, "error", err)
}
} else {
- log.Info("Deleting local CNAME record from Pi-Hole", "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain)
+ log.Info("Deleting local CNAME record from Pi-Hole", "host", host, "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain)
err := p.piholeClient.DeleteCNameRecord(url, piholeOptions.TargetDomain)
if err != nil {
- log.Error("Failed to delete local CNAME record from Pi-Hole", "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain, "error", err)
+ log.Error("Failed to delete local CNAME record from Pi-Hole", "host", host, "container", containerName, "url", url, "targetDomain", piholeOptions.TargetDomain, "error", err)
}
}
}
}
}
-func (p *Processor) handleNpm(containerEvent docker.EventType, containerName, url, ip string, port int, npmProxyHostOptions npm.NpmProxyHostOptions) {
+func (p *Processor) handleNpm(host string, containerEvent docker.EventType, containerName, url, ip string, port int, npmProxyHostOptions npm.NpmProxyHostOptions) {
switch containerEvent {
case docker.ContainerEvent.Start:
npmProxyHost := npm.ProxyHost{
@@ -209,42 +215,42 @@ func (p *Processor) handleNpm(containerEvent docker.EventType, containerName, ur
}
}
- log.Info("Adding entry to Nginx Proxy Manager", "container", containerName)
+ log.Info("Adding entry to Nginx Proxy Manager", "host", host, "container", containerName)
err := p.npmClient.AddProxyHost(npmProxyHost)
if err != nil {
- log.Error("Failed to add entry to Nginx Proxy Manager", "container", containerName, "error", err)
+ log.Error("Failed to add entry to Nginx Proxy Manager", "host", host, "container", containerName, "error", err)
}
case docker.ContainerEvent.Die:
- log.Info("Deleting entry from Nginx Proxy Manager", "container", containerName)
+ log.Info("Deleting entry from Nginx Proxy Manager", "host", host, "container", containerName)
err := p.npmClient.DeleteProxyHost(url)
if err != nil {
- log.Error("Failed to delete entry from Nginx Proxy Manager", "container", containerName, "error", err)
+ log.Error("Failed to delete entry from Nginx Proxy Manager", "host", host, "container", containerName, "error", err)
}
}
}
-func (p *Processor) processContainer(containerEvent docker.EventType, containerName, ip, url string, port int, opts *docker.ClientOptions) {
+func (p *Processor) processContainer(containerEvent docker.EventType, host, containerName, ip, url string, port int, opts *docker.ClientOptions) {
msg := "Handling container"
if p.dryRun {
msg += ". In dry run mode, not doing anything."
- log.Info(msg, "container", containerName, "ip", ip, "port", port, "url", url)
+ log.Info(msg, "host", host, "container", containerName, "ip", ip, "port", port, "url", url)
return
}
- log.Info(msg, "container", containerName, "ip", ip, "port", port, "url", url)
+ log.Info(msg, "host", host, "container", containerName, "ip", ip, "port", port, "url", url)
if p.npmClient != nil {
npmHost := p.npmClient.GetIP()
if opts.AdguardHome != nil {
- p.handleAdguardHome(containerEvent, containerName, url, npmHost, *opts.AdguardHome)
+ p.handleAdguardHome(host, containerEvent, containerName, url, npmHost, *opts.AdguardHome)
}
if opts.Pihole != nil {
- p.handlePiHole(containerEvent, containerName, url, npmHost, *opts.Pihole)
+ p.handlePiHole(host, containerEvent, containerName, url, npmHost, *opts.Pihole)
}
if opts.NPM != nil {
- p.handleNpm(containerEvent, containerName, url, ip, port, *opts.NPM)
+ p.handleNpm(host, containerEvent, containerName, url, ip, port, *opts.NPM)
}
}
}