From f3cb7701f23c8c1d13364737e857fb483f748bda Mon Sep 17 00:00:00 2001 From: iRedTea Date: Tue, 13 Oct 2026 16:30:53 +0300 Subject: [PATCH] fix: remove an agent's forward rules with the agent (0.0.4) Deleting a machine left its forward rules behind: the host ports stayed bound and "busy" with no page to remove them from. The rules, their listeners and ufw rules now go with the agent, and rules orphaned by older versions are dropped on start. A host port equal to the direct tunnel port gets its own message. --- VERSION.txt | 2 +- internal/server/handlers_panel.go | 6 ++++++ internal/server/handlers_panel_fwd.go | 22 ++++++++++++++++------ internal/server/server.go | 6 ++++++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index bcab45a..81340c7 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.0.3 +0.0.4 diff --git a/internal/server/handlers_panel.go b/internal/server/handlers_panel.go index 94a8906..7619c39 100644 --- a/internal/server/handlers_panel.go +++ b/internal/server/handlers_panel.go @@ -146,6 +146,12 @@ func (s *Server) handleAgentDelete(w http.ResponseWriter, r *http.Request) { return } id := r.PathValue("id") + // Its forward rules go with it: left behind they would keep their host + // ports bound and busy with no page to remove them from. + rules, _ := s.db.ListForwardRulesByAgent(id) + for _, rule := range rules { + s.removeForward(rule.ID) + } _ = s.db.DeleteAgent(id) http.Redirect(w, r, "/", http.StatusSeeOther) } diff --git a/internal/server/handlers_panel_fwd.go b/internal/server/handlers_panel_fwd.go index 3f7a5f1..9c159d2 100644 --- a/internal/server/handlers_panel_fwd.go +++ b/internal/server/handlers_panel_fwd.go @@ -18,6 +18,7 @@ func (s *Server) handleForwardsPage(w http.ResponseWriter, r *http.Request) { rules, _ := s.db.ListForwardRulesByAgent(id) errMsg := map[string]string{ "busy": "Этот порт хоста уже занят другим правилом.", + "tunnel": "Этот порт занят прямым туннелем панели (см. Настройки).", "port": "Некорректный номер порта.", "listen": "Не удалось занять порт на хосте (возможно, уже используется).", "db": "Ошибка базы данных.", @@ -76,7 +77,11 @@ func (s *Server) handleForwardAdd(w http.ResponseWriter, r *http.Request) { return } // Reject a host port already bound by another forward rule or the tunnel. - if s.db.HostPortUsed(hostPort) || hostPort == s.TunnelPort() { + if hostPort == s.TunnelPort() { + http.Redirect(w, r, "/agents/"+id+"/forwards?err=tunnel", http.StatusSeeOther) + return + } + if s.db.HostPortUsed(hostPort) { http.Redirect(w, r, "/agents/"+id+"/forwards?err=busy", http.StatusSeeOther) return } @@ -107,14 +112,19 @@ func (s *Server) handleForwardDelete(w http.ResponseWriter, r *http.Request) { } rid, _ := strconv.ParseInt(r.PathValue("rid"), 10, 64) rule, _ := s.db.GetForwardRule(rid) - s.fwd.close(rid) - _ = s.db.DeleteForwardRule(rid) - // Remove the ufw rule bound to this forward rule (the shared tunnel port - // stays while another direct forward needs it). - s.ufw.revokeForRule(rid) + s.removeForward(rid) dest := "/" if rule != nil { dest = "/agents/" + rule.AgentID + "/forwards" } http.Redirect(w, r, dest, http.StatusSeeOther) } + +// removeForward closes a forward rule's listener and deletes the rule, along +// with the ufw rule bound to it (the shared tunnel port stays while another +// direct forward needs it). +func (s *Server) removeForward(rid int64) { + s.fwd.close(rid) + _ = s.db.DeleteForwardRule(rid) + s.ufw.revokeForRule(rid) +} diff --git a/internal/server/server.go b/internal/server/server.go index bf6f213..0018527 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -161,6 +161,12 @@ func (s *Server) restoreForwards() { return } for _, r := range rules { + // Rules of an agent deleted by an older version: drop them. + if a, err := s.db.GetAgent(r.AgentID); err == nil && a == nil { + log.Printf("forward rule %d: agent %s is gone, removing", r.ID, r.AgentID) + s.removeForward(r.ID) + continue + } if err := s.fwd.open(r); err != nil { log.Printf("restore forward rule %d (%s:%d): %v", r.ID, r.ListenAddr, r.HostPort, err) }