watchd Monitoring Daemon

A compact version of the capstone: it polls a directory on a ticker and counts changes, serves live stats over a Unix domain socket, reloads on SIGHUP, and shuts down cleanly on SIGTERM. The demo creates a file, queries the socket, reloads, and stops itself.

package main

import (
	"bufio"
	"context"
	"fmt"
	"net"
	"os"
	"os/signal"
	"path/filepath"
	"sync/atomic"
	"syscall"
	"time"
)

func main() {
	dir, _ := os.MkdirTemp("", "watchd")
	defer os.RemoveAll(dir)
	sock := filepath.Join(dir, "watchd.sock")

	var changes atomic.Int64
	start := time.Now()

	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	// Watcher: poll the directory, count added or resized files against a snapshot.
	go func() {
		seen := map[string]int64{}
		t := time.NewTicker(100 * time.Millisecond)
		defer t.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-t.C:
				now := map[string]int64{}
				entries, _ := os.ReadDir(dir)
				for _, e := range entries {
					info, err := e.Info()
					if err != nil {
						continue
					}
					now[e.Name()] = info.Size()
					if seen[e.Name()] != info.Size() {
						changes.Add(1)
					}
				}
				seen = now
			}
		}
	}()

	// Stats server: each connection gets one line of live stats.
	ln, err := net.Listen("unix", sock)
	if err != nil {
		fmt.Println("listen:", err)
		return
	}
	defer ln.Close()
	go func() {
		<-ctx.Done()
		ln.Close() // unblock Accept on shutdown
	}()
	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				return
			}
			fmt.Fprintf(conn, "uptime=%s changes=%d\n",
				time.Since(start).Round(time.Millisecond), changes.Load())
			conn.Close()
		}
	}()

	// SIGHUP: reload config without restarting.
	hup := make(chan os.Signal, 1)
	signal.Notify(hup, syscall.SIGHUP)
	go func() {
		for range hup {
			fmt.Println("SIGHUP: config reloaded")
		}
	}()

	fmt.Println("watchd running on", sock)

	// Demo driver: create a file, read the stats, reload, then shut down.
	go func() {
		time.Sleep(250 * time.Millisecond)
		os.WriteFile(filepath.Join(dir, "new.txt"), []byte("hi"), 0644)
		time.Sleep(250 * time.Millisecond)

		if c, err := net.Dial("unix", sock); err == nil {
			line, _ := bufio.NewReader(c).ReadString('\n')
			fmt.Print("stats: ", line) // stats: uptime=... changes=1
			c.Close()
		}
		syscall.Kill(os.Getpid(), syscall.SIGHUP)
		time.Sleep(100 * time.Millisecond)
		syscall.Kill(os.Getpid(), syscall.SIGTERM)
	}()

	<-ctx.Done()
	fmt.Println("watchd shutting down")
}

Save the code as main.go and run it:

go run main.go

Expected output:

watchd running on /tmp/watchd2959957259/watchd.sock
stats: uptime=503ms changes=1
SIGHUP: config reloaded
watchd shutting down

The socket path and uptime vary, and the whole demo finishes in under a second. To run it as a real daemon, delete the demo-driver goroutine; then drop files into the printed directory, query it with nc -U <socket-path>, reload with kill -HUP <pid>, and stop with kill -TERM <pid>.

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy