Compare commits
2 Commits
9043cf9bc0
...
c116b035bd
| Author | SHA1 | Date | |
|---|---|---|---|
| c116b035bd | |||
| 1115954827 |
8
Makefile
8
Makefile
@ -1,5 +1,11 @@
|
||||
export DEBUG = routewatch
|
||||
|
||||
# Git revision for version embedding
|
||||
GIT_REVISION := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_REVISION_SHORT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
VERSION_PKG := git.eeqj.de/sneak/routewatch/internal/version
|
||||
LDFLAGS := -X $(VERSION_PKG).GitRevision=$(GIT_REVISION) -X $(VERSION_PKG).GitRevisionShort=$(GIT_REVISION_SHORT)
|
||||
|
||||
.PHONY: test fmt lint build clean run asupdate
|
||||
|
||||
all: test
|
||||
@ -15,7 +21,7 @@ lint:
|
||||
golangci-lint run
|
||||
|
||||
build:
|
||||
CGO_ENABLED=1 go build -o bin/routewatch cmd/routewatch/main.go
|
||||
CGO_ENABLED=1 go build -ldflags "$(LDFLAGS)" -o bin/routewatch cmd/routewatch/main.go
|
||||
|
||||
clean:
|
||||
rm -rf bin/
|
||||
|
||||
@ -30,6 +30,14 @@ type Tracker struct {
|
||||
// Route update metrics
|
||||
ipv4UpdateRate metrics.Meter
|
||||
ipv6UpdateRate metrics.Meter
|
||||
|
||||
// Announcement/withdrawal metrics
|
||||
announcementCounter metrics.Counter
|
||||
withdrawalCounter metrics.Counter
|
||||
churnRate metrics.Meter // combined announcements + withdrawals per second
|
||||
|
||||
// BGP peer tracking
|
||||
bgpPeerCount atomic.Int32
|
||||
}
|
||||
|
||||
// New creates a new metrics tracker
|
||||
@ -37,15 +45,18 @@ func New() *Tracker {
|
||||
registry := metrics.NewRegistry()
|
||||
|
||||
return &Tracker{
|
||||
registry: registry,
|
||||
messageCounter: metrics.NewCounter(),
|
||||
byteCounter: metrics.NewCounter(),
|
||||
messageRate: metrics.NewMeter(),
|
||||
byteRate: metrics.NewMeter(),
|
||||
wireByteCounter: metrics.NewCounter(),
|
||||
wireByteRate: metrics.NewMeter(),
|
||||
ipv4UpdateRate: metrics.NewMeter(),
|
||||
ipv6UpdateRate: metrics.NewMeter(),
|
||||
registry: registry,
|
||||
messageCounter: metrics.NewCounter(),
|
||||
byteCounter: metrics.NewCounter(),
|
||||
messageRate: metrics.NewMeter(),
|
||||
byteRate: metrics.NewMeter(),
|
||||
wireByteCounter: metrics.NewCounter(),
|
||||
wireByteRate: metrics.NewMeter(),
|
||||
ipv4UpdateRate: metrics.NewMeter(),
|
||||
ipv6UpdateRate: metrics.NewMeter(),
|
||||
announcementCounter: metrics.NewCounter(),
|
||||
withdrawalCounter: metrics.NewCounter(),
|
||||
churnRate: metrics.NewMeter(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,6 +145,56 @@ func (t *Tracker) RecordIPv6Update() {
|
||||
t.ipv6UpdateRate.Mark(1)
|
||||
}
|
||||
|
||||
// RecordAnnouncement records a route announcement
|
||||
func (t *Tracker) RecordAnnouncement() {
|
||||
t.announcementCounter.Inc(1)
|
||||
t.churnRate.Mark(1)
|
||||
}
|
||||
|
||||
// RecordWithdrawal records a route withdrawal
|
||||
func (t *Tracker) RecordWithdrawal() {
|
||||
t.withdrawalCounter.Inc(1)
|
||||
t.churnRate.Mark(1)
|
||||
}
|
||||
|
||||
// SetBGPPeerCount updates the current BGP peer count
|
||||
func (t *Tracker) SetBGPPeerCount(count int) {
|
||||
// BGP peer count is always small (< 1000), so int32 is safe
|
||||
if count > 0 && count < 1<<31 {
|
||||
t.bgpPeerCount.Store(int32(count)) //nolint:gosec // count is validated
|
||||
}
|
||||
}
|
||||
|
||||
// GetBGPPeerCount returns the current BGP peer count
|
||||
func (t *Tracker) GetBGPPeerCount() int {
|
||||
return int(t.bgpPeerCount.Load())
|
||||
}
|
||||
|
||||
// GetAnnouncementCount returns the total announcement count
|
||||
func (t *Tracker) GetAnnouncementCount() uint64 {
|
||||
count := t.announcementCounter.Count()
|
||||
if count < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uint64(count)
|
||||
}
|
||||
|
||||
// GetWithdrawalCount returns the total withdrawal count
|
||||
func (t *Tracker) GetWithdrawalCount() uint64 {
|
||||
count := t.withdrawalCounter.Count()
|
||||
if count < 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uint64(count)
|
||||
}
|
||||
|
||||
// GetChurnRate returns the route churn rate per second
|
||||
func (t *Tracker) GetChurnRate() float64 {
|
||||
return t.churnRate.Rate1()
|
||||
}
|
||||
|
||||
// GetRouteMetrics returns current route update metrics
|
||||
func (t *Tracker) GetRouteMetrics() RouteMetrics {
|
||||
return RouteMetrics{
|
||||
|
||||
@ -113,6 +113,10 @@ func (h *PrefixHandler) HandleMessage(msg *ristypes.RISMessage) {
|
||||
timestamp: timestamp,
|
||||
path: msg.Path,
|
||||
})
|
||||
// Record announcement in metrics
|
||||
if h.metrics != nil {
|
||||
h.metrics.RecordAnnouncement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +130,10 @@ func (h *PrefixHandler) HandleMessage(msg *ristypes.RISMessage) {
|
||||
timestamp: timestamp,
|
||||
path: msg.Path,
|
||||
})
|
||||
// Record withdrawal in metrics
|
||||
if h.metrics != nil {
|
||||
h.metrics.RecordWithdrawal()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to flush
|
||||
|
||||
@ -320,6 +320,23 @@ func (s *Server) handleStats() http.HandlerFunc {
|
||||
MaxProcessTimeMs float64 `json:"max_process_time_ms"`
|
||||
}
|
||||
|
||||
// GCStats represents garbage collection statistics
|
||||
type GCStats struct {
|
||||
NumGC uint32 `json:"num_gc"`
|
||||
TotalPauseMs uint64 `json:"total_pause_ms"`
|
||||
LastPauseMs float64 `json:"last_pause_ms"`
|
||||
HeapAllocBytes uint64 `json:"heap_alloc_bytes"`
|
||||
HeapSysBytes uint64 `json:"heap_sys_bytes"`
|
||||
}
|
||||
|
||||
// StreamStats represents stream statistics including announcements/withdrawals
|
||||
type StreamStats struct {
|
||||
Announcements uint64 `json:"announcements"`
|
||||
Withdrawals uint64 `json:"withdrawals"`
|
||||
RouteChurnPerSec float64 `json:"route_churn_per_sec"`
|
||||
BGPPeerCount int `json:"bgp_peer_count"`
|
||||
}
|
||||
|
||||
// StatsResponse represents the API statistics response
|
||||
type StatsResponse struct {
|
||||
Uptime string `json:"uptime"`
|
||||
@ -335,6 +352,8 @@ func (s *Server) handleStats() http.HandlerFunc {
|
||||
GoVersion string `json:"go_version"`
|
||||
Goroutines int `json:"goroutines"`
|
||||
MemoryUsage string `json:"memory_usage"`
|
||||
GC GCStats `json:"gc"`
|
||||
Stream StreamStats `json:"stream"`
|
||||
ASNs int `json:"asns"`
|
||||
Prefixes int `json:"prefixes"`
|
||||
IPv4Prefixes int `json:"ipv4_prefixes"`
|
||||
@ -450,6 +469,25 @@ func (s *Server) handleStats() http.HandlerFunc {
|
||||
connectionDuration = time.Since(metrics.ConnectedSince).Truncate(time.Second).String()
|
||||
}
|
||||
|
||||
// Get announcement/withdrawal stats from metrics tracker
|
||||
metricsTracker := s.streamer.GetMetricsTracker()
|
||||
announcements := metricsTracker.GetAnnouncementCount()
|
||||
withdrawals := metricsTracker.GetWithdrawalCount()
|
||||
churnRate := metricsTracker.GetChurnRate()
|
||||
bgpPeerCount := metricsTracker.GetBGPPeerCount()
|
||||
|
||||
// Calculate last GC pause
|
||||
const (
|
||||
nanosecondsPerMillisecond = 1e6
|
||||
gcPauseHistorySize = 256 // Size of runtime.MemStats.PauseNs circular buffer
|
||||
)
|
||||
var lastPauseMs float64
|
||||
if memStats.NumGC > 0 {
|
||||
// PauseNs is a circular buffer, get the most recent pause
|
||||
lastPauseIdx := (memStats.NumGC + gcPauseHistorySize - 1) % gcPauseHistorySize
|
||||
lastPauseMs = float64(memStats.PauseNs[lastPauseIdx]) / nanosecondsPerMillisecond
|
||||
}
|
||||
|
||||
stats := StatsResponse{
|
||||
Uptime: uptime,
|
||||
TotalMessages: metrics.TotalMessages,
|
||||
@ -464,6 +502,19 @@ func (s *Server) handleStats() http.HandlerFunc {
|
||||
GoVersion: runtime.Version(),
|
||||
Goroutines: runtime.NumGoroutine(),
|
||||
MemoryUsage: humanize.Bytes(memStats.Alloc),
|
||||
GC: GCStats{
|
||||
NumGC: memStats.NumGC,
|
||||
TotalPauseMs: memStats.PauseTotalNs / uint64(nanosecondsPerMillisecond),
|
||||
LastPauseMs: lastPauseMs,
|
||||
HeapAllocBytes: memStats.HeapAlloc,
|
||||
HeapSysBytes: memStats.HeapSys,
|
||||
},
|
||||
Stream: StreamStats{
|
||||
Announcements: announcements,
|
||||
Withdrawals: withdrawals,
|
||||
RouteChurnPerSec: churnRate,
|
||||
BGPPeerCount: bgpPeerCount,
|
||||
},
|
||||
ASNs: dbStats.ASNs,
|
||||
Prefixes: dbStats.Prefixes,
|
||||
IPv4Prefixes: dbStats.IPv4Prefixes,
|
||||
@ -685,7 +736,8 @@ func (s *Server) handleASDetailJSON() http.HandlerFunc {
|
||||
// handlePrefixDetailJSON returns prefix details as JSON
|
||||
func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
prefixParam := chi.URLParam(r, "prefix")
|
||||
// Get wildcard parameter (everything after /prefix/)
|
||||
prefixParam := chi.URLParam(r, "*")
|
||||
if prefixParam == "" {
|
||||
writeJSONError(w, http.StatusBadRequest, "Prefix parameter is required")
|
||||
|
||||
@ -851,7 +903,8 @@ func (s *Server) handleASDetail() http.HandlerFunc {
|
||||
// handlePrefixDetail returns a handler that serves the prefix detail HTML page
|
||||
func (s *Server) handlePrefixDetail() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
prefixParam := chi.URLParam(r, "prefix")
|
||||
// Get wildcard parameter (everything after /prefix/)
|
||||
prefixParam := chi.URLParam(r, "*")
|
||||
if prefixParam == "" {
|
||||
http.Error(w, "Prefix parameter is required", http.StatusBadRequest)
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ func (s *Server) setupRoutes() {
|
||||
|
||||
// AS and prefix detail pages
|
||||
r.Get("/as/{asn}", s.handleASDetail())
|
||||
r.Get("/prefix/{prefix}", s.handlePrefixDetail())
|
||||
r.Get("/prefix/*", s.handlePrefixDetail())
|
||||
r.Get("/prefixlength/{length}", s.handlePrefixLength())
|
||||
r.Get("/prefixlength6/{length}", s.handlePrefixLength6())
|
||||
|
||||
@ -45,7 +45,7 @@ func (s *Server) setupRoutes() {
|
||||
r.Get("/stats", s.handleStats())
|
||||
r.Get("/ip/{ip}", s.handleIPLookup())
|
||||
r.Get("/as/{asn}", s.handleASDetailJSON())
|
||||
r.Get("/prefix/{prefix}", s.handlePrefixDetailJSON())
|
||||
r.Get("/prefix/*", s.handlePrefixDetailJSON())
|
||||
})
|
||||
|
||||
s.router = r
|
||||
|
||||
@ -114,6 +114,8 @@ type Streamer struct {
|
||||
metrics *metrics.Tracker
|
||||
totalDropped uint64 // Total dropped messages across all handlers
|
||||
random *rand.Rand // Random number generator for backpressure drops
|
||||
bgpPeers map[string]bool // Track active BGP peers by peer IP
|
||||
bgpPeersMu sync.RWMutex // Protects bgpPeers map
|
||||
}
|
||||
|
||||
// New creates a new Streamer instance configured to connect to the RIS Live API.
|
||||
@ -132,7 +134,8 @@ func New(logger *logger.Logger, metrics *metrics.Tracker) *Streamer {
|
||||
handlers: make([]*handlerInfo, 0),
|
||||
metrics: metrics,
|
||||
//nolint:gosec // Non-cryptographic randomness is fine for backpressure
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
bgpPeers: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
@ -608,18 +611,32 @@ func (s *Streamer) stream(ctx context.Context) error {
|
||||
// BGP keepalive messages - silently process
|
||||
continue
|
||||
case "OPEN":
|
||||
// BGP open messages
|
||||
// BGP open messages - track peer as active
|
||||
s.bgpPeersMu.Lock()
|
||||
s.bgpPeers[msg.Peer] = true
|
||||
peerCount := len(s.bgpPeers)
|
||||
s.bgpPeersMu.Unlock()
|
||||
s.metrics.SetBGPPeerCount(peerCount)
|
||||
|
||||
s.logger.Info("BGP session opened",
|
||||
"peer", msg.Peer,
|
||||
"peer_asn", msg.PeerASN,
|
||||
"total_peers", peerCount,
|
||||
)
|
||||
|
||||
continue
|
||||
case "NOTIFICATION":
|
||||
// BGP notification messages (errors)
|
||||
// BGP notification messages (session closed)
|
||||
s.bgpPeersMu.Lock()
|
||||
delete(s.bgpPeers, msg.Peer)
|
||||
peerCount := len(s.bgpPeers)
|
||||
s.bgpPeersMu.Unlock()
|
||||
s.metrics.SetBGPPeerCount(peerCount)
|
||||
|
||||
s.logger.Warn("BGP notification",
|
||||
"peer", msg.Peer,
|
||||
"peer_asn", msg.PeerASN,
|
||||
"total_peers", peerCount,
|
||||
)
|
||||
|
||||
continue
|
||||
|
||||
@ -72,6 +72,27 @@
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.footer a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.footer .separator {
|
||||
margin: 0 10px;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -112,6 +133,10 @@
|
||||
<span class="metric-label">Reconnections</span>
|
||||
<span class="metric-value" id="reconnect_count">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">BGP Peers</span>
|
||||
<span class="metric-value" id="bgp_peer_count">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Total Messages</span>
|
||||
<span class="metric-value" id="total_messages">-</span>
|
||||
@ -120,6 +145,18 @@
|
||||
<span class="metric-label">Messages/sec</span>
|
||||
<span class="metric-value" id="messages_per_sec">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Announcements</span>
|
||||
<span class="metric-value" id="announcements">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Withdrawals</span>
|
||||
<span class="metric-value" id="withdrawals">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Route Churn/sec</span>
|
||||
<span class="metric-value" id="route_churn_per_sec">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Total Data</span>
|
||||
<span class="metric-value" id="total_wire_bytes">-</span>
|
||||
@ -130,6 +167,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-card">
|
||||
<h2>GC Statistics</h2>
|
||||
<div class="metric">
|
||||
<span class="metric-label">GC Runs</span>
|
||||
<span class="metric-value" id="gc_num">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Total Pause</span>
|
||||
<span class="metric-value" id="gc_total_pause">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Last Pause</span>
|
||||
<span class="metric-value" id="gc_last_pause">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Heap Alloc</span>
|
||||
<span class="metric-value" id="gc_heap_alloc">-</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">Heap Sys</span>
|
||||
<span class="metric-value" id="gc_heap_sys">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-card">
|
||||
<h2>Database Statistics</h2>
|
||||
<div class="metric">
|
||||
@ -344,10 +405,19 @@
|
||||
document.getElementById('memory_usage').textContent = '-';
|
||||
document.getElementById('connection_duration').textContent = '-';
|
||||
document.getElementById('reconnect_count').textContent = '-';
|
||||
document.getElementById('bgp_peer_count').textContent = '-';
|
||||
document.getElementById('total_messages').textContent = '-';
|
||||
document.getElementById('messages_per_sec').textContent = '-';
|
||||
document.getElementById('announcements').textContent = '-';
|
||||
document.getElementById('withdrawals').textContent = '-';
|
||||
document.getElementById('route_churn_per_sec').textContent = '-';
|
||||
document.getElementById('total_wire_bytes').textContent = '-';
|
||||
document.getElementById('wire_mbits_per_sec').textContent = '-';
|
||||
document.getElementById('gc_num').textContent = '-';
|
||||
document.getElementById('gc_total_pause').textContent = '-';
|
||||
document.getElementById('gc_last_pause').textContent = '-';
|
||||
document.getElementById('gc_heap_alloc').textContent = '-';
|
||||
document.getElementById('gc_heap_sys').textContent = '-';
|
||||
document.getElementById('asns').textContent = '-';
|
||||
document.getElementById('prefixes').textContent = '-';
|
||||
document.getElementById('ipv4_prefixes').textContent = '-';
|
||||
@ -421,6 +491,23 @@
|
||||
document.getElementById('ipv4_updates_per_sec').textContent = data.ipv4_updates_per_sec.toFixed(1);
|
||||
document.getElementById('ipv6_updates_per_sec').textContent = data.ipv6_updates_per_sec.toFixed(1);
|
||||
|
||||
// Update stream stats
|
||||
if (data.stream) {
|
||||
document.getElementById('bgp_peer_count').textContent = formatNumber(data.stream.bgp_peer_count);
|
||||
document.getElementById('announcements').textContent = formatNumber(data.stream.announcements);
|
||||
document.getElementById('withdrawals').textContent = formatNumber(data.stream.withdrawals);
|
||||
document.getElementById('route_churn_per_sec').textContent = data.stream.route_churn_per_sec.toFixed(1);
|
||||
}
|
||||
|
||||
// Update GC stats
|
||||
if (data.gc) {
|
||||
document.getElementById('gc_num').textContent = formatNumber(data.gc.num_gc);
|
||||
document.getElementById('gc_total_pause').textContent = data.gc.total_pause_ms + ' ms';
|
||||
document.getElementById('gc_last_pause').textContent = data.gc.last_pause_ms.toFixed(3) + ' ms';
|
||||
document.getElementById('gc_heap_alloc').textContent = formatBytes(data.gc.heap_alloc_bytes);
|
||||
document.getElementById('gc_heap_sys').textContent = formatBytes(data.gc.heap_sys_bytes);
|
||||
}
|
||||
|
||||
// Update WHOIS stats
|
||||
if (data.whois_stats) {
|
||||
document.getElementById('whois_fresh').textContent = formatNumber(data.whois_stats.fresh_asns);
|
||||
@ -455,5 +542,13 @@
|
||||
updateStatus();
|
||||
setInterval(updateStatus, 2000);
|
||||
</script>
|
||||
|
||||
<footer class="footer">
|
||||
<span><a href="{{appRepoURL}}">{{appName}}</a> by <a href="{{appAuthorURL}}">{{appAuthor}}</a></span>
|
||||
<span class="separator">|</span>
|
||||
<span>{{appLicense}}</span>
|
||||
<span class="separator">|</span>
|
||||
<span><a href="{{appGitCommitURL}}">{{appGitRevision}}</a></span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/routewatch/internal/version"
|
||||
)
|
||||
|
||||
//go:embed status.html
|
||||
@ -86,12 +88,19 @@ func initTemplates() {
|
||||
|
||||
// Create common template functions
|
||||
funcs := template.FuncMap{
|
||||
"timeSince": timeSince,
|
||||
"urlEncode": url.QueryEscape,
|
||||
"timeSince": timeSince,
|
||||
"urlEncode": url.QueryEscape,
|
||||
"appName": func() string { return version.Name },
|
||||
"appAuthor": func() string { return version.Author },
|
||||
"appAuthorURL": func() string { return version.AuthorURL },
|
||||
"appLicense": func() string { return version.License },
|
||||
"appRepoURL": func() string { return version.RepoURL },
|
||||
"appGitRevision": func() string { return version.GitRevisionShort },
|
||||
"appGitCommitURL": func() string { return version.CommitURL() },
|
||||
}
|
||||
|
||||
// Parse status template
|
||||
defaultTemplates.Status, err = template.New("status").Parse(statusHTML)
|
||||
defaultTemplates.Status, err = template.New("status").Funcs(funcs).Parse(statusHTML)
|
||||
if err != nil {
|
||||
panic("failed to parse status template: " + err.Error())
|
||||
}
|
||||
|
||||
34
internal/version/version.go
Normal file
34
internal/version/version.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Package version provides build version information
|
||||
package version
|
||||
|
||||
// Build-time variables set via ldflags
|
||||
//
|
||||
//nolint:gochecknoglobals // These must be variables to allow ldflags injection at build time
|
||||
var (
|
||||
// GitRevision is the git commit hash
|
||||
GitRevision = "unknown"
|
||||
// GitRevisionShort is the short git commit hash (7 chars)
|
||||
GitRevisionShort = "unknown"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name is the program name
|
||||
Name = "routewatch"
|
||||
// Author is the program author
|
||||
Author = "@sneak"
|
||||
// AuthorURL is the author's website
|
||||
AuthorURL = "https://sneak.berlin"
|
||||
// License is the program license
|
||||
License = "WTFPL"
|
||||
// RepoURL is the git repository URL
|
||||
RepoURL = "https://git.eeqj.de/sneak/routewatch"
|
||||
)
|
||||
|
||||
// CommitURL returns the URL to view the current commit
|
||||
func CommitURL() string {
|
||||
if GitRevision == "unknown" {
|
||||
return RepoURL
|
||||
}
|
||||
|
||||
return RepoURL + "/commit/" + GitRevision
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user