Some checks are pending
CI / build-and-test (push) Waiting to run
Release / build (amd64, linux) (push) Successful in 1m12s
Release / build (amd64, darwin) (push) Successful in 1m16s
Release / build (arm64, darwin) (push) Successful in 1m0s
Release / build (amd64, windows) (push) Successful in 1m13s
Release / build (arm64, linux) (push) Successful in 45s
Release / release (push) Successful in 50s
- Rename binary from act_runner to gitcaddy-runner - Update all user-facing strings (Gitea → GitCaddy) - Add gitcaddy-upload helper with automatic retry for large files - Add upload helper package (internal/pkg/artifact) - Update Docker image name to marketally/gitcaddy-runner 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
210 lines
4.4 KiB
Go
210 lines
4.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package envcheck
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// BandwidthInfo holds network bandwidth test results
|
|
type BandwidthInfo struct {
|
|
DownloadMbps float64 `json:"download_mbps"`
|
|
UploadMbps float64 `json:"upload_mbps,omitempty"`
|
|
Latency float64 `json:"latency_ms,omitempty"`
|
|
TestedAt time.Time `json:"tested_at"`
|
|
}
|
|
|
|
// BandwidthManager handles periodic bandwidth testing
|
|
type BandwidthManager struct {
|
|
serverURL string
|
|
lastResult *BandwidthInfo
|
|
mu sync.RWMutex
|
|
testInterval time.Duration
|
|
stopChan chan struct{}
|
|
}
|
|
|
|
// NewBandwidthManager creates a new bandwidth manager
|
|
func NewBandwidthManager(serverURL string, testInterval time.Duration) *BandwidthManager {
|
|
return &BandwidthManager{
|
|
serverURL: serverURL,
|
|
testInterval: testInterval,
|
|
stopChan: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Start begins periodic bandwidth testing
|
|
func (bm *BandwidthManager) Start(ctx context.Context) {
|
|
// Run initial test
|
|
bm.RunTest(ctx)
|
|
|
|
// Start periodic testing
|
|
go func() {
|
|
ticker := time.NewTicker(bm.testInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
bm.RunTest(ctx)
|
|
case <-bm.stopChan:
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Stop stops the periodic testing
|
|
func (bm *BandwidthManager) Stop() {
|
|
close(bm.stopChan)
|
|
}
|
|
|
|
// RunTest runs a bandwidth test and stores the result
|
|
func (bm *BandwidthManager) RunTest(ctx context.Context) *BandwidthInfo {
|
|
result := TestBandwidth(ctx, bm.serverURL)
|
|
|
|
bm.mu.Lock()
|
|
bm.lastResult = result
|
|
bm.mu.Unlock()
|
|
|
|
return result
|
|
}
|
|
|
|
// GetLastResult returns the most recent bandwidth test result
|
|
func (bm *BandwidthManager) GetLastResult() *BandwidthInfo {
|
|
bm.mu.RLock()
|
|
defer bm.mu.RUnlock()
|
|
return bm.lastResult
|
|
}
|
|
|
|
// TestBandwidth tests network bandwidth to the GitCaddy server
|
|
func TestBandwidth(ctx context.Context, serverURL string) *BandwidthInfo {
|
|
if serverURL == "" {
|
|
return nil
|
|
}
|
|
|
|
info := &BandwidthInfo{
|
|
TestedAt: time.Now(),
|
|
}
|
|
|
|
// Test latency first
|
|
info.Latency = testLatency(ctx, serverURL)
|
|
|
|
// Test download speed
|
|
info.DownloadMbps = testDownloadSpeed(ctx, serverURL)
|
|
|
|
return info
|
|
}
|
|
|
|
func testLatency(ctx context.Context, serverURL string) float64 {
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
|
|
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(reqCtx, "HEAD", serverURL, nil)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
start := time.Now()
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
resp.Body.Close()
|
|
|
|
latency := time.Since(start).Seconds() * 1000 // Convert to ms
|
|
return float64(int(latency*100)) / 100 // Round to 2 decimals
|
|
}
|
|
|
|
func testDownloadSpeed(ctx context.Context, serverURL string) float64 {
|
|
// Try multiple endpoints to accumulate ~1MB of data
|
|
endpoints := []string{
|
|
"/assets/css/index.css",
|
|
"/assets/js/index.js",
|
|
"/assets/img/logo.svg",
|
|
"/assets/img/logo.png",
|
|
"/",
|
|
}
|
|
|
|
client := &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
var totalBytes int64
|
|
var totalDuration time.Duration
|
|
targetBytes := int64(1024 * 1024) // 1MB target
|
|
maxAttempts := 10 // Limit iterations
|
|
|
|
for attempt := 0; attempt < maxAttempts && totalBytes < targetBytes; attempt++ {
|
|
for _, endpoint := range endpoints {
|
|
if totalBytes >= targetBytes {
|
|
break
|
|
}
|
|
|
|
url := serverURL + endpoint
|
|
|
|
reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
req, err := http.NewRequestWithContext(reqCtx, "GET", url, nil)
|
|
if err != nil {
|
|
cancel()
|
|
continue
|
|
}
|
|
|
|
start := time.Now()
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
cancel()
|
|
continue
|
|
}
|
|
|
|
n, _ := io.Copy(io.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
cancel()
|
|
|
|
duration := time.Since(start)
|
|
|
|
if n > 0 {
|
|
totalBytes += n
|
|
totalDuration += duration
|
|
}
|
|
}
|
|
}
|
|
|
|
if totalBytes == 0 || totalDuration == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Calculate speed in Mbps
|
|
seconds := totalDuration.Seconds()
|
|
if seconds == 0 {
|
|
return 0
|
|
}
|
|
|
|
bytesPerSecond := float64(totalBytes) / seconds
|
|
mbps := (bytesPerSecond * 8) / (1024 * 1024)
|
|
|
|
return float64(int(mbps*100)) / 100
|
|
}
|
|
|
|
// FormatBandwidth formats bandwidth for display
|
|
func FormatBandwidth(mbps float64) string {
|
|
if mbps == 0 {
|
|
return "Unknown"
|
|
}
|
|
if mbps >= 1000 {
|
|
return fmt.Sprintf("%.1f Gbps", mbps/1000)
|
|
}
|
|
return fmt.Sprintf("%.1f Mbps", mbps)
|
|
}
|