package hammerhead import ( "errors" "fmt" "net" "os" "path/filepath" "strconv" "strings" "git.veillax.com/Veillax/reef/model" ) const selfMappingID = "hammerhead-dashboard" // ensureSelfEdge makes sure this server has an edge of its own, for a Blacktip on the same // machine, and writes that edge's token to /self-edge.env for the installer. The edge // also carries a built-in https route for the dashboard (see selfMapping), so no separate // reverse proxy is needed in front of Hammerhead. func (s *Server) ensureSelfEdge() error { o := s.opts if o.SelfDomain == "" { return nil } host, port, err := net.SplitHostPort(o.SelfTarget) if err != nil { return fmt.Errorf("HAMMERHEAD_SELF_DOMAIN needs a listen address like 127.0.0.1:8080: %w", err) } if n, _ := strconv.Atoi(port); n < 1 || host == "" { return errors.New("the dashboard's listen address needs a host and a port") } s.mu.Lock() defer s.mu.Unlock() e := s.findEdge(s.st.Settings.SelfEdgeID) if e == nil { e = &model.Edge{ID: newID(), Name: "Hammerhead server", Token: randHex(24), AuthKey: randHex(32), TunnelAddr: o.SelfTunnel, TunnelIP: s.allocIPLocked(), OrgIDs: []string{}, IPs: []string{}} s.st.Edges = append(s.st.Edges, e) s.st.Sites = append(s.st.Sites, &model.Site{ID: newID(), Name: e.Name + " (this machine)", Local: true, EdgeID: e.ID, Connected: []string{}}) s.st.Settings.SelfEdgeID = e.ID s.bump() } body := "# written by Hammerhead for the Blacktip on this machine; keep it private\nBLACKTIP_TOKEN=" + e.Token + "\n" return os.WriteFile(filepath.Join(s.dir, "self-edge.env"), []byte(body), 0o600) } // selfMapping is the dashboard's own route on the self edge: https on the configured name, // forwarded to Hammerhead's listen address through the edge's local site. It is added to that // edge's config and never stored, so it can't be edited or deleted by mistake in the UI. // Caller holds s.mu. func (s *Server) selfMapping(e *model.Edge) *model.Mapping { o := s.opts if o.SelfDomain == "" || e.ID != s.st.Settings.SelfEdgeID { return nil } host, port, err := net.SplitHostPort(o.SelfTarget) if err != nil { return nil } n, _ := strconv.Atoi(port) for _, st := range s.st.Sites { if st.Local && st.EdgeID == e.ID { return &model.Mapping{ID: selfMappingID, Name: "Hammerhead dashboard", Proto: model.ProtoHTTPS, EdgeID: e.ID, BindIP: "0.0.0.0", Port: 443, Hostname: strings.ToLower(o.SelfDomain), Enabled: true, RedirectHTTP: true, HealthSeconds: -1, Targets: []model.Target{{SiteID: st.ID, Host: host, Port: n}}} } } return nil }