From b76e9c3d48207ef64bdda8cd3a96b4ab3db1886a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= <ales.mrazek@nic.cz>
Date: Fri, 21 Feb 2025 15:28:28 +0100
Subject: [PATCH 1/5] manager: files: watchdog for all files

---
 .../knot_resolver/manager/files/watchdog.py   | 82 +++++++++----------
 1 file changed, 41 insertions(+), 41 deletions(-)

diff --git a/python/knot_resolver/manager/files/watchdog.py b/python/knot_resolver/manager/files/watchdog.py
index e0abf56c7..b235ca192 100644
--- a/python/knot_resolver/manager/files/watchdog.py
+++ b/python/knot_resolver/manager/files/watchdog.py
@@ -1,7 +1,7 @@
 import logging
 from pathlib import Path
 from threading import Timer
-from typing import Any, List, Optional
+from typing import Any, Dict, List, Optional
 
 from knot_resolver.constants import WATCHDOG_LIB
 from knot_resolver.controller.registered_workers import command_registered_workers
@@ -20,6 +20,9 @@ def tls_cert_files_config(config: KresConfig) -> List[Any]:
     ]
 
 
+FilesToWatch = Dict[Path, str]
+
+
 if WATCHDOG_LIB:
     from watchdog.events import (
         FileSystemEvent,
@@ -27,20 +30,17 @@ if WATCHDOG_LIB:
     )
     from watchdog.observers import Observer
 
-    _tls_cert_watchdog: Optional["TLSCertWatchDog"] = None
-
-    class TLSCertEventHandler(FileSystemEventHandler):
-        def __init__(self, files: List[Path], cmd: str) -> None:
+    class FilesWatchdogEventHandler(FileSystemEventHandler):
+        def __init__(self, files: FilesToWatch) -> None:
             self._files = files
-            self._cmd = cmd
             self._timer: Optional[Timer] = None
 
-        def _reload(self) -> None:
+        def _reload(self, cmd: str) -> None:
             def command() -> None:
                 if compat.asyncio.is_event_loop_running():
-                    compat.asyncio.create_task(command_registered_workers(self._cmd))
+                    compat.asyncio.create_task(command_registered_workers(cmd))
                 else:
-                    compat.asyncio.run(command_registered_workers(self._cmd))
+                    compat.asyncio.run(command_registered_workers(cmd))
                 logger.info("Reloading of TLS certificate files has finished")
 
             # skipping if reload was already triggered
@@ -54,17 +54,17 @@ if WATCHDOG_LIB:
 
         def on_created(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
-            if src_path in self._files:
+            if src_path in self._files.keys():
                 logger.info(f"Watched file '{src_path}' has been created")
-                self._reload()
+                self._reload(self._files[src_path])
 
         def on_deleted(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
-            if src_path in self._files:
+            if src_path in self._files.keys():
                 logger.warning(f"Watched file '{src_path}' has been deleted")
                 if self._timer:
                     self._timer.cancel()
-            for file in self._files:
+            for file in self._files.keys():
                 if file.parent == src_path:
                     logger.warning(f"Watched directory '{src_path}' has been deleted")
                     if self._timer:
@@ -72,27 +72,23 @@ if WATCHDOG_LIB:
 
         def on_modified(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
-            if src_path in self._files:
+            if src_path in self._files.keys():
                 logger.info(f"Watched file '{src_path}' has been modified")
-                self._reload()
+                self._reload(self._files[src_path])
 
-    class TLSCertWatchDog:
-        def __init__(self, cert_file: Path, key_file: Path) -> None:
-            self._observer = Observer()
+    _files_watchdog: Optional["FilesWatchdog"] = None
 
-            cmd = f"net.tls('{cert_file}', '{key_file}')"
-
-            cert_files: List[Path] = []
-            cert_files.append(cert_file)
-            cert_files.append(key_file)
+    class FilesWatchdog:
+        def __init__(self, files_to_watch: FilesToWatch) -> None:
+            self._observer = Observer()
 
-            cert_dirs: List[Path] = []
-            cert_dirs.append(cert_file.parent)
-            if cert_file.parent != key_file.parent:
-                cert_dirs.append(key_file.parent)
+            event_handler = FilesWatchdogEventHandler(files_to_watch)
+            dirs_to_watch: List[Path] = []
+            for file in files_to_watch.keys():
+                if file.parent not in dirs_to_watch:
+                    dirs_to_watch.append(file.parent)
 
-            event_handler = TLSCertEventHandler(cert_files, cmd)
-            for d in cert_dirs:
+            for d in dirs_to_watch:
                 self._observer.schedule(
                     event_handler,
                     str(d),
@@ -109,22 +105,26 @@ if WATCHDOG_LIB:
 
 
 @only_on_real_changes_update(tls_cert_files_config)
-async def _init_tls_cert_watchdog(config: KresConfig) -> None:
+async def _init_files_watchdog(config: KresConfig) -> None:
     if WATCHDOG_LIB:
-        global _tls_cert_watchdog
+        global _files_watchdog
 
-        if _tls_cert_watchdog:
-            _tls_cert_watchdog.stop()
+        if _files_watchdog:
+            _files_watchdog.stop()
+        files_to_watch: FilesToWatch = {}
 
+        # network.tls
         if config.network.tls.files_watchdog and config.network.tls.cert_file and config.network.tls.key_file:
-            logger.info("Initializing TLS certificate files WatchDog")
-            _tls_cert_watchdog = TLSCertWatchDog(
-                config.network.tls.cert_file.to_path(),
-                config.network.tls.key_file.to_path(),
-            )
-            _tls_cert_watchdog.start()
+            net_tls = f"net.tls('{config.network.tls.cert_file}', '{config.network.tls.key_file}')"
+            files_to_watch[config.network.tls.cert_file.to_path()] = net_tls
+            files_to_watch[config.network.tls.key_file.to_path()] = net_tls
+
+        if files_to_watch:
+            logger.info("Initializing files watchdog")
+            _files_watchdog = FilesWatchdog(files_to_watch)
+            _files_watchdog.start()
 
 
 async def init_files_watchdog(config_store: ConfigStore) -> None:
-    # watchdog for TLS certificate files
-    await config_store.register_on_change_callback(_init_tls_cert_watchdog)
+    # register files watchdog callback
+    await config_store.register_on_change_callback(_init_files_watchdog)
-- 
GitLab


From ede1f1c924cf43d7deab5906c9d5f83c15552484 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= <ales.mrazek@nic.cz>
Date: Thu, 6 Feb 2025 11:44:41 +0100
Subject: [PATCH 2/5] datamodel: local-data: added watchdog for RPZSchema

---
 doc/_static/config.schema.json                | 15 ++++++++
 .../datamodel/local_data_schema.py            | 37 +++++++++++++++----
 2 files changed, 44 insertions(+), 8 deletions(-)

diff --git a/doc/_static/config.schema.json b/doc/_static/config.schema.json
index 0bedbbc4e..754403734 100644
--- a/doc/_static/config.schema.json
+++ b/doc/_static/config.schema.json
@@ -928,6 +928,21 @@
                                 "type": "string",
                                 "description": "Path to the RPZ zone file."
                             },
+                            "watchdog": {
+                                "anyOf": [
+                                    {
+                                        "type": "string",
+                                        "enum": [
+                                            "auto"
+                                        ]
+                                    },
+                                    {
+                                        "type": "boolean"
+                                    }
+                                ],
+                                "description": "Enables files watchdog for configured RPZ file. Requires the optional 'watchdog' dependency.",
+                                "default": "auto"
+                            },
                             "tags": {
                                 "type": [
                                     "array",
diff --git a/python/knot_resolver/datamodel/local_data_schema.py b/python/knot_resolver/datamodel/local_data_schema.py
index ee2297784..478a0e231 100644
--- a/python/knot_resolver/datamodel/local_data_schema.py
+++ b/python/knot_resolver/datamodel/local_data_schema.py
@@ -1,5 +1,6 @@
-from typing import Dict, List, Literal, Optional
+from typing import Any, Dict, List, Literal, Optional, Union
 
+from knot_resolver.constants import WATCHDOG_LIB
 from knot_resolver.datamodel.types import (
     DomainName,
     EscapedStr,
@@ -54,16 +55,36 @@ class RuleSchema(ConfigSchema):
 
 
 class RPZSchema(ConfigSchema):
-    """
-    Configuration or Response Policy Zone (RPZ).
+    class Raw(ConfigSchema):
+        """
+        Configuration or Response Policy Zone (RPZ).
 
-    ---
-    file: Path to the RPZ zone file.
-    tags: Tags to link with other policy rules.
-    """
+        ---
+        file: Path to the RPZ zone file.
+        watchdog: Enables files watchdog for configured RPZ file. Requires the optional 'watchdog' dependency.
+        tags: Tags to link with other policy rules.
+        """
+
+        file: ReadableFile
+        watchdog: Union[Literal["auto"], bool] = "auto"
+        tags: Optional[List[IDPattern]] = None
+
+    _LAYER = Raw
 
     file: ReadableFile
-    tags: Optional[List[IDPattern]] = None
+    watchdog: bool
+    tags: Optional[List[IDPattern]]
+
+    def _watchdog(self, obj: Raw) -> Any:
+        if obj.watchdog == "auto":
+            return WATCHDOG_LIB
+        return obj.watchdog
+
+    def _validate(self) -> None:
+        if self.watchdog and not WATCHDOG_LIB:
+            raise ValueError(
+                "'watchdog' is enabled, but the required 'watchdog' dependency (optional) is not installed"
+            )
 
 
 class LocalDataSchema(ConfigSchema):
-- 
GitLab


From 1d473674527c8b337db88321514fa336da445a86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= <ales.mrazek@nic.cz>
Date: Tue, 4 Mar 2025 15:13:20 +0100
Subject: [PATCH 3/5] manager: files: watchdog: added RPZ files

Separate timer for each command.
---
 NEWS                                          |   4 +
 .../knot_resolver/manager/files/watchdog.py   |  94 ++++++++++----
 python/knot_resolver/manager/server.py        |  18 +++
 tests/packaging/interactive/rpz_watchdog.sh   | 121 ++++++++++++++++++
 tests/packaging/interactive/watchdog.sh       |   2 +-
 5 files changed, 214 insertions(+), 25 deletions(-)
 create mode 100755 tests/packaging/interactive/rpz_watchdog.sh

diff --git a/NEWS b/NEWS
index 13fd8f428..b53ed68d4 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,10 @@ Bugfixes
 - /management/unix-socket: revert to absolute path (#926, !1664)
 - fix `tags` when used in /local-data/rules/*/records (!1670)
 
+Improvements
+------------
+- /local-data/rpz/*/watchdog: new configuration to enable watchdog for RPZ files (!1665)
+
 
 Knot Resolver 6.0.11 (2025-02-26)
 =================================
diff --git a/python/knot_resolver/manager/files/watchdog.py b/python/knot_resolver/manager/files/watchdog.py
index b235ca192..e74abec9f 100644
--- a/python/knot_resolver/manager/files/watchdog.py
+++ b/python/knot_resolver/manager/files/watchdog.py
@@ -2,27 +2,29 @@ import logging
 from pathlib import Path
 from threading import Timer
 from typing import Any, Dict, List, Optional
+from urllib.parse import quote
 
 from knot_resolver.constants import WATCHDOG_LIB
 from knot_resolver.controller.registered_workers import command_registered_workers
 from knot_resolver.datamodel import KresConfig
 from knot_resolver.manager.config_store import ConfigStore, only_on_real_changes_update
 from knot_resolver.utils import compat
+from knot_resolver.utils.requests import SocketDesc, request
 
 logger = logging.getLogger(__name__)
 
+FilesToWatch = Dict[Path, Optional[str]]
 
-def tls_cert_files_config(config: KresConfig) -> List[Any]:
+
+def watched_files_config(config: KresConfig) -> List[Any]:
     return [
         config.network.tls.files_watchdog,
         config.network.tls.cert_file,
         config.network.tls.key_file,
+        config.local_data.rpz,
     ]
 
 
-FilesToWatch = Dict[Path, str]
-
-
 if WATCHDOG_LIB:
     from watchdog.events import (
         FileSystemEvent,
@@ -31,58 +33,96 @@ if WATCHDOG_LIB:
     from watchdog.observers import Observer
 
     class FilesWatchdogEventHandler(FileSystemEventHandler):
-        def __init__(self, files: FilesToWatch) -> None:
+        def __init__(self, files: FilesToWatch, config: KresConfig) -> None:
             self._files = files
-            self._timer: Optional[Timer] = None
+            self._config = config
+            self._policy_timer: Optional[Timer] = None
+            self._timers: Dict[str, Timer] = {}
+
+        def _trigger(self, cmd: Optional[str]) -> None:
+            def policy_reload() -> None:
+                management = self._config.management
+                socket = SocketDesc(
+                    f'http+unix://{quote(str(management.unix_socket), safe="")}/',
+                    'Key "/management/unix-socket" in validated configuration',
+                )
+                if management.interface:
+                    socket = SocketDesc(
+                        f"http://{management.interface.addr}:{management.interface.port}",
+                        'Key "/management/interface" in validated configuration',
+                    )
+
+                response = request(socket, "POST", "renew")
+                if response.status != 200:
+                    logger.error(f"Failed to reload policy rules: {response.body}")
+                logger.info("Reloading policy rules has finished")
+
+            if not cmd:
+                # skipping if reload was already triggered
+                if self._policy_timer and self._policy_timer.is_alive():
+                    logger.info("Skipping reloading policy rules, it was already triggered")
+                    return
+                # start a 5sec timer
+                logger.info("Delayed policy rules reload has started")
+                self._policy_timer = Timer(5, policy_reload)
+                self._policy_timer.start()
+                return
 
-        def _reload(self, cmd: str) -> None:
             def command() -> None:
                 if compat.asyncio.is_event_loop_running():
                     compat.asyncio.create_task(command_registered_workers(cmd))
                 else:
                     compat.asyncio.run(command_registered_workers(cmd))
-                logger.info("Reloading of TLS certificate files has finished")
+                logger.info(f"Sending '{cmd}' command to reload watched files has finished")
 
-            # skipping if reload was already triggered
-            if self._timer and self._timer.is_alive():
-                logger.info("Skipping TLS certificate files reloading, reload command was already triggered")
+            # skipping if command was already triggered
+            if cmd in self._timers and self._timers[cmd].is_alive():
+                logger.info(f"Skipping sending '{cmd}' command, it was already triggered")
                 return
             # start a 5sec timer
-            logger.info("Delayed reload of TLS certificate files has started")
-            self._timer = Timer(5, command)
-            self._timer.start()
+            logger.info(f"Delayed send of '{cmd}' command has started")
+            self._timers[cmd] = Timer(5, command)
+            self._timers[cmd].start()
 
         def on_created(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
             if src_path in self._files.keys():
                 logger.info(f"Watched file '{src_path}' has been created")
-                self._reload(self._files[src_path])
+                self._trigger(self._files[src_path])
 
         def on_deleted(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
             if src_path in self._files.keys():
                 logger.warning(f"Watched file '{src_path}' has been deleted")
-                if self._timer:
-                    self._timer.cancel()
+                cmd = self._files[src_path]
+                if cmd in self._timers:
+                    self._timers[cmd].cancel()
             for file in self._files.keys():
                 if file.parent == src_path:
                     logger.warning(f"Watched directory '{src_path}' has been deleted")
-                    if self._timer:
-                        self._timer.cancel()
+                    cmd = self._files[file]
+                    if cmd in self._timers:
+                        self._timers[cmd].cancel()
+
+        def on_moved(self, event: FileSystemEvent) -> None:
+            src_path = Path(str(event.src_path))
+            if src_path in self._files.keys():
+                logger.info(f"Watched file '{src_path}' has been moved")
+                self._trigger(self._files[src_path])
 
         def on_modified(self, event: FileSystemEvent) -> None:
             src_path = Path(str(event.src_path))
             if src_path in self._files.keys():
                 logger.info(f"Watched file '{src_path}' has been modified")
-                self._reload(self._files[src_path])
+                self._trigger(self._files[src_path])
 
     _files_watchdog: Optional["FilesWatchdog"] = None
 
     class FilesWatchdog:
-        def __init__(self, files_to_watch: FilesToWatch) -> None:
+        def __init__(self, files_to_watch: FilesToWatch, config: KresConfig) -> None:
             self._observer = Observer()
 
-            event_handler = FilesWatchdogEventHandler(files_to_watch)
+            event_handler = FilesWatchdogEventHandler(files_to_watch, config)
             dirs_to_watch: List[Path] = []
             for file in files_to_watch.keys():
                 if file.parent not in dirs_to_watch:
@@ -104,7 +144,7 @@ if WATCHDOG_LIB:
             self._observer.join()
 
 
-@only_on_real_changes_update(tls_cert_files_config)
+@only_on_real_changes_update(watched_files_config)
 async def _init_files_watchdog(config: KresConfig) -> None:
     if WATCHDOG_LIB:
         global _files_watchdog
@@ -119,9 +159,15 @@ async def _init_files_watchdog(config: KresConfig) -> None:
             files_to_watch[config.network.tls.cert_file.to_path()] = net_tls
             files_to_watch[config.network.tls.key_file.to_path()] = net_tls
 
+        # local-data.rpz
+        if config.local_data.rpz:
+            for rpz in config.local_data.rpz:
+                if rpz.watchdog:
+                    files_to_watch[rpz.file.to_path()] = None
+
         if files_to_watch:
             logger.info("Initializing files watchdog")
-            _files_watchdog = FilesWatchdog(files_to_watch)
+            _files_watchdog = FilesWatchdog(files_to_watch, config)
             _files_watchdog.start()
 
 
diff --git a/python/knot_resolver/manager/server.py b/python/knot_resolver/manager/server.py
index 879ce80e4..c9b2e6fa1 100644
--- a/python/knot_resolver/manager/server.py
+++ b/python/knot_resolver/manager/server.py
@@ -138,6 +138,14 @@ class Server:
                 logger.error(f"Reloading of the configuration file failed: {e}")
                 logger.error("Configuration have NOT been changed.")
 
+    async def _renew_config(self) -> None:
+        try:
+            await self.config_store.renew()
+            logger.info("Configuration successfully renewed")
+        except KresManagerException as e:
+            logger.error(f"Renewing the configuration failed: {e}")
+            logger.error("Configuration have NOT been renewed.")
+
     async def sigint_handler(self) -> None:
         logger.info("Received SIGINT, triggering graceful shutdown")
         self.trigger_shutdown(0)
@@ -325,6 +333,15 @@ class Server:
         await self._reload_config()
         return web.Response(text="Reloading...")
 
+    async def _handler_renew(self, _request: web.Request) -> web.Response:
+        """
+        Route handler for renewing the configuration
+        """
+
+        logger.info("Renewing configuration event triggered...")
+        await self._renew_config()
+        return web.Response(text="Renewing configuration...")
+
     async def _handler_processes(self, request: web.Request) -> web.Response:
         """
         Route handler for listing PIDs of subprocesses
@@ -359,6 +376,7 @@ class Server:
                 web.patch(r"/v1/config{path:.*}", self._handler_config_query),
                 web.post("/stop", self._handler_stop),
                 web.post("/reload", self._handler_reload),
+                web.post("/renew", self._handler_renew),
                 web.get("/schema", self._handler_schema),
                 web.get("/schema/ui", self._handle_view_schema),
                 web.get("/metrics", self._handler_metrics),
diff --git a/tests/packaging/interactive/rpz_watchdog.sh b/tests/packaging/interactive/rpz_watchdog.sh
new file mode 100755
index 000000000..910178fab
--- /dev/null
+++ b/tests/packaging/interactive/rpz_watchdog.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+
+set -e
+
+gitroot=$(git rev-parse --show-toplevel)
+rpz_file=$gitroot/example.rpz
+
+rpz_example=$(cat <<EOF
+\$ORIGIN RPZ.EXAMPLE.ORG.
+ok.example.com                    CNAME rpz-passthru.
+EOF
+)
+# create example RPZ
+echo "$rpz_example" >> $rpz_file
+
+rpz_conf=$(cat <<EOF
+local-data:
+  rpz:
+    - file: $rpz_file
+      watchdog: false
+EOF
+)
+# add RPZ to config
+echo "$rpz_conf" >> /etc/knot-resolver/config.yaml
+
+function count_errors(){
+    echo "$(journalctl -u knot-resolver.service | grep -c error)"
+}
+
+function count_reloads(){
+    echo "$(journalctl -u knot-resolver.service | grep -c "Reloading policy rules has finished")"
+}
+
+# test that RPZ watchdog
+# {{
+
+err_count=$(count_errors)
+rel_count=$(count_reloads)
+
+# reload config with RPZ configured without watchdog turned on
+kresctl reload
+sleep 1
+if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -ne $rel_count ]; then
+    echo "RPZ file watchdog is running (should not) or other errors occurred."
+    exit 1
+fi
+
+# configure RPZ file and turn on watchdog
+kresctl config set -p /local-data/rpz/0/watchdog true
+sleep 1
+if [ "$?" -ne "0" ]; then
+    echo "Could not turn on RPZ file watchdog."
+    exit 1
+fi
+
+# }}
+
+# test RPZ modification
+# {{
+
+# modify RPZ file, it will trigger reload
+rel_count=$(count_reloads)
+echo "32.1.2.0.192.rpz-client-ip        CNAME rpz-passthru." >> $rpz_file
+
+# wait for files reload to finish
+sleep 10
+
+if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
+    echo "Could not reload modified RPZ file."
+    exit 1
+fi
+
+# }}
+
+# test replacement
+# {{
+
+rel_count=$(count_reloads)
+
+# copy RPZ file
+cp $rpz_file $rpz_file.new
+
+# edit new files
+echo "48.zz.101.db8.2001.rpz-client-ip  CNAME rpz-passthru." >> $rpz_file.new
+
+# replace files
+cp -f $rpz_file.new $rpz_file
+
+# wait for files reload to finish
+sleep 10
+
+if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
+    echo "Could not reload replaced RPZ file."
+    exit 1
+fi
+
+# }}
+
+# test recovery from deletion and creation
+# {{
+
+rel_count=$(count_reloads)
+
+# backup rpz file
+cp $rpz_file $rpz_file.backup
+
+# delete RPZ file
+rm $rpz_file
+
+# create cert files
+cp -f $rpz_file.backup $rpz_file
+
+# wait for files reload to finish
+sleep 10
+
+if [ $(count_errors) -ne $err_count ] || [ $(count_reloads) -eq $rel_count ]; then
+    echo "Could not reload created RPZ file."
+    exit 1
+fi
+
+# }}
diff --git a/tests/packaging/interactive/watchdog.sh b/tests/packaging/interactive/watchdog.sh
index ffc76e921..104bbdd64 100755
--- a/tests/packaging/interactive/watchdog.sh
+++ b/tests/packaging/interactive/watchdog.sh
@@ -26,7 +26,7 @@ function count_errors(){
 }
 
 function count_reloads(){
-    echo "$(journalctl -u knot-resolver.service | grep -c "Reloading of TLS certificate files has finished")"
+    echo "$(journalctl -u knot-resolver.service | grep -c "to reload watched files has finished")"
 }
 
 # test that files watchdog is turned off
-- 
GitLab


From d56ba63b8002b67dd87ea15c6548bb6210a1d345 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= <ales.mrazek@nic.cz>
Date: Tue, 1 Apr 2025 11:27:51 +0200
Subject: [PATCH 4/5] tests/packaging/interactive: watchdog.sh renamed to
 tls_cert_watchdog.sh

---
 tests/packaging/interactive/{watchdog.sh => tls_cert_watchdog.sh} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename tests/packaging/interactive/{watchdog.sh => tls_cert_watchdog.sh} (100%)

diff --git a/tests/packaging/interactive/watchdog.sh b/tests/packaging/interactive/tls_cert_watchdog.sh
similarity index 100%
rename from tests/packaging/interactive/watchdog.sh
rename to tests/packaging/interactive/tls_cert_watchdog.sh
-- 
GitLab


From bb1523d807258478397003e3f7d87d9592dca184 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ale=C5=A1=20Mr=C3=A1zek?= <ales.mrazek@nic.cz>
Date: Tue, 1 Apr 2025 11:51:28 +0200
Subject: [PATCH 5/5] manager: server: have/has typo

---
 python/knot_resolver/manager/server.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/python/knot_resolver/manager/server.py b/python/knot_resolver/manager/server.py
index c9b2e6fa1..41078a7ad 100644
--- a/python/knot_resolver/manager/server.py
+++ b/python/knot_resolver/manager/server.py
@@ -130,13 +130,13 @@ class Server:
                     f"Configuration file was not found at '{self._config_path}'."
                     " Something must have happened to it while we were running."
                 )
-                logger.error("Configuration have NOT been changed.")
+                logger.error("Configuration has NOT been changed.")
             except (DataParsingError, DataValidationError) as e:
                 logger.error(f"Failed to parse the updated configuration file: {e}")
-                logger.error("Configuration have NOT been changed.")
+                logger.error("Configuration has NOT been changed.")
             except KresManagerException as e:
                 logger.error(f"Reloading of the configuration file failed: {e}")
-                logger.error("Configuration have NOT been changed.")
+                logger.error("Configuration has NOT been changed.")
 
     async def _renew_config(self) -> None:
         try:
@@ -144,7 +144,7 @@ class Server:
             logger.info("Configuration successfully renewed")
         except KresManagerException as e:
             logger.error(f"Renewing the configuration failed: {e}")
-            logger.error("Configuration have NOT been renewed.")
+            logger.error("Configuration has NOT been renewed.")
 
     async def sigint_handler(self) -> None:
         logger.info("Received SIGINT, triggering graceful shutdown")
-- 
GitLab