Repository: caddyserver/caddy (language: go)

Issue:
Watch directory
Using [this `import` example](https://caddyserver.com/docs/caddyfile/directives/import#examples), loading a whole directory of config files, it would be nice if the --watch flag could monitor an entire directory as well. Being able to glob just like the import directive would be awesome as well.


Discussion / hints:
One way this could be resolved is to always load + adapt the config file without looking at the main config file's modification time. This would definitely be more expensive though because it'll always be doing IO operations. But it might be worth doing 🤔 
Hello, I've also stumbled across this.

I think such overhead will be negligible, moreover we can increase delay between reloads to, for example, 3 seconds instead of 1.
But there are info messages printed every time config is loaded. If those can be removed, this would be good way to go.

Other approach is to look for import directives in Caddyfile and if they're pointing to files watch them too.

What do you think?
Us maintainers don't really have a need for this feature, so we don't have much motivation to work on it.

If someone wants to take a crack at it though, we'd be glad to review a PR.
Hi, I'm new to this repo I would like to tackle this issue.
I am a little confused on how to approach this, I thought maybe identify the folder of the import and watch them as well with time modification (it is also affected when files inside are affected) but how can I get the folder path inside?  
That's part of the trouble. The caddyfile adapter would need to keep memory of all the locations it reads files from (including globs) and then set up file watching for all of them. File watching on all platforms Caddy supports is tricky though. Hence polling the Caddyfile adapter might be the simplest option even if inefficient.

Fix patch:
diff --git a/cmd/main.go b/cmd/main.go
index 96debdf01da..97326ba45c5 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -174,6 +174,8 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
 // blocks indefinitely; it only quits if the poller has errors for
 // long enough time. The filename passed in must be the actual
 // config file used, not one to be discovered.
+// Each second the config files is loaded and parsed into an object
+// and is compared to the last config object that was loaded
 func watchConfigFile(filename, adapterName string) {
 	defer func() {
 		if err := recover(); err != nil {
@@ -189,64 +191,37 @@ func watchConfigFile(filename, adapterName string) {
 			With(zap.String("config_file", filename))
 	}
 
-	// get the initial timestamp on the config file
-	info, err := os.Stat(filename)
+	// get current config
+	lastCfg, _, err := LoadConfig(filename, adapterName)
 	if err != nil {
-		logger().Error("cannot watch config file", zap.Error(err))
+		logger().Error("unable to load latest config", zap.Error(err))
 		return
 	}
-	lastModified := info.ModTime()
 
 	logger().Info("watching config file for changes")
 
-	// if the file disappears or something, we can
-	// stop polling if the error lasts long enough
-	var lastErr time.Time
-	finalError := func(err error) bool {
-		if lastErr.IsZero() {
-			lastErr = time.Now()
-			return false
-		}
-		if time.Since(lastErr) > 30*time.Second {
-			logger().Error("giving up watching config file; too many errors",
-				zap.Error(err))
-			return true
-		}
-		return false
-	}
-
 	// begin poller
 	//nolint:staticcheck
 	for range time.Tick(1 * time.Second) {
-		// get the file info
-		info, err := os.Stat(filename)
+
+		// get current config
+		newCfg, _, err := LoadConfig(filename, adapterName)
 		if err != nil {
-			if finalError(err) {
-				return
-			}
-			continue
+			logger().Error("unable to load latest config", zap.Error(err))
+			return
 		}
-		lastErr = time.Time{} // no error, so clear any memory of one
 
 		// if it hasn't changed, nothing to do
-		if !info.ModTime().After(lastModified) {
+		if bytes.Equal(lastCfg, newCfg) {
 			continue
 		}
-
 		logger().Info("config file changed; reloading")
 
-		// remember this timestamp
-		lastModified = info.ModTime()
-
-		// load the contents of the file
-		config, _, err := LoadConfig(filename, adapterName)
-		if err != nil {
-			logger().Error("unable to load latest config", zap.Error(err))
-			continue
-		}
+		// remember the current config
+		lastCfg = newCfg
 
 		// apply the updated config
-		err = caddy.Load(config, false)
+		err = caddy.Load(lastCfg, false)
 		if err != nil {
 			logger().Error("applying latest config", zap.Error(err))
 			continue


Test patch: