From 7e6291c21c480f797bce6ee1611ad45702032ce8 Mon Sep 17 00:00:00 2001
From: Kristoffer Dalby <kradalby@kradalby.no>
Date: Sat, 11 Jun 2022 12:53:02 +0100
Subject: [PATCH 1/2] Change Set state change function to filter instead of
 single namespace

This commit makes the setLastStateChangeToNow function take a list of
namespaces instead of a single namespace. If no namespaces is passed,
all namespaces will be updated. This means that the argument acts like a
filter.
---
 app.go        | 22 +++++++++++++++++-----
 namespaces.go | 15 +++++++++++++++
 2 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/app.go b/app.go
index 01528fb..062860d 100644
--- a/app.go
+++ b/app.go
@@ -756,13 +756,25 @@ func (h *Headscale) getTLSSettings() (*tls.Config, error) {
 	}
 }
 
-func (h *Headscale) setLastStateChangeToNow(namespace string) {
+func (h *Headscale) setLastStateChangeToNow(namespaces ...string) {
+	var err error
+
 	now := time.Now().UTC()
-	lastStateUpdate.WithLabelValues("", "headscale").Set(float64(now.Unix()))
-	if h.lastStateChange == nil {
-		h.lastStateChange = xsync.NewMapOf[time.Time]()
+
+	if len(namespaces) == 0 {
+		namespaces, err = h.ListNamespacesStr()
+		if err != nil {
+			log.Error().Caller().Err(err).Msg("failed to fetch all namespaces, failing to update last changed state.")
+		}
+	}
+
+	for _, namespace := range namespaces {
+		lastStateUpdate.WithLabelValues(namespace, "headscale").Set(float64(now.Unix()))
+		if h.lastStateChange == nil {
+			h.lastStateChange = xsync.NewMapOf[time.Time]()
+		}
+		h.lastStateChange.Store(namespace, now)
 	}
-	h.lastStateChange.Store(namespace, now)
 }
 
 func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
diff --git a/namespaces.go b/namespaces.go
index 19407b8..0add03f 100644
--- a/namespaces.go
+++ b/namespaces.go
@@ -148,6 +148,21 @@ func (h *Headscale) ListNamespaces() ([]Namespace, error) {
 	return namespaces, nil
 }
 
+func (h *Headscale) ListNamespacesStr() ([]string, error) {
+	namespaces, err := h.ListNamespaces()
+	if err != nil {
+		return []string{}, err
+	}
+
+	namespaceStrs := make([]string, len(namespaces))
+
+	for index, namespace := range namespaces {
+		namespaceStrs[index] = namespace.Name
+	}
+
+	return namespaceStrs, nil
+}
+
 // ListMachinesInNamespace gets all the nodes in a given namespace.
 func (h *Headscale) ListMachinesInNamespace(name string) ([]Machine, error) {
 	err := CheckForFQDNRules(name)

From 0c2648c1889c332bca61e82006793ea40695faa1 Mon Sep 17 00:00:00 2001
From: Kristoffer Dalby <kradalby@kradalby.no>
Date: Sat, 11 Jun 2022 12:54:44 +0100
Subject: [PATCH 2/2] Update the nodes after we have reloaded the ACL policy
 with sighup

---
 app.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app.go b/app.go
index 062860d..ee4732f 100644
--- a/app.go
+++ b/app.go
@@ -657,7 +657,9 @@ func (h *Headscale) Serve() error {
 					}
 					log.Info().
 						Str("path", aclPath).
-						Msg("ACL policy successfully reloaded")
+						Msg("ACL policy successfully reloaded, notifying nodes of change")
+
+					h.setLastStateChangeToNow()
 				}
 
 			default: