Files
VeillaxandClaude Sonnet 5.5 536284ec75
Release / release (push) Failing after 13m6s
CI / test (push) Failing after 18m11s
Give Hammerhead its own edge with a built-in dashboard route; setup.sh installs it with Blacktip instead of Caddy
HAMMERHEAD_SELF_DOMAIN creates an edge for this machine, writes its token to <data>/self-edge.env
and adds an https route for the dashboard to that edge's config, so no reverse proxy is needed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 15:38:16 -06:00

72 lines
2.5 KiB
Go

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 <data>/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
}