I wrote a daemon at the weekend and, against the run of play, finished it. It watches a directory with fsnotify, debounces the events for a second so a burst of writes becomes one, and posts a small JSON payload to a webhook. About 120 lines. The point wasn't the feature, it was getting the dull bits right.
The dull bits are signals and lifecycle. A daemon that doesn't handle SIGTERM cleanly is a daemon that loses work when systemd restarts it. So a context, a signal.NotifyContext, and every goroutine selecting on ctx.Done():
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
Then a systemd unit with Type=simple, Restart=on-failure, and a RestartSec so a crash loop doesn't hammer the box. No PID files, no double-forking, none of the old daemonising rituals. systemd holds the process, journald holds the logs, and I get to write plain code that logs to stdout.
That's the bit I keep relearning. The Go is trivial. The thing that makes it a daemon rather than a script is behaving well when something tells it to stop.