refactor: migrate lint config to v2, remove daemon auto-upgrade, add trust badges
This commit is contained in:
parent
a13104bdb7
commit
efa4562acd
18 changed files with 188 additions and 268 deletions
|
|
@ -73,17 +73,6 @@ func (c *Client) Deregister(ctx context.Context, agentID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ReportUpgradeResult reports the outcome of a self-upgrade attempt.
|
||||
func (c *Client) ReportUpgradeResult(ctx context.Context, result UpgradeResult) error {
|
||||
var resp struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
if err := c.doPost(ctx, "/api/internal/agent/upgrade-result", result, &resp); err != nil {
|
||||
return fmt.Errorf("report upgrade: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReportStatus reports download progress. Returns server-side flags the CLI must act on.
|
||||
func (c *Client) ReportStatus(ctx context.Context, update StatusUpdate) (*StatusResponse, error) {
|
||||
var resp StatusResponse
|
||||
|
|
@ -93,6 +82,15 @@ func (c *Client) ReportStatus(ctx context.Context, update StatusUpdate) (*Status
|
|||
return &resp, nil
|
||||
}
|
||||
|
||||
// BatchReportStatus sends multiple status updates in a single request.
|
||||
func (c *Client) BatchReportStatus(ctx context.Context, updates []StatusUpdate) (*BatchStatusResponse, error) {
|
||||
var resp BatchStatusResponse
|
||||
if err := c.doPost(ctx, "/api/internal/agent/status", BatchStatusRequest{Updates: updates}, &resp); err != nil {
|
||||
return nil, fmt.Errorf("batch report status: %w", err)
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Usenet endpoints
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -324,62 +324,3 @@ func TestHeartbeatWithoutUpgradeSignal(t *testing.T) {
|
|||
t.Errorf("expected no upgrade signal, got %+v", resp.Upgrade)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportUpgradeResult(t *testing.T) {
|
||||
var received UpgradeResult
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/internal/agent/upgrade-result" {
|
||||
t.Errorf("path = %s, want /api/internal/agent/upgrade-result", r.URL.Path)
|
||||
}
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("method = %s, want POST", r.Method)
|
||||
}
|
||||
json.NewDecoder(r.Body).Decode(&received)
|
||||
json.NewEncoder(w).Encode(struct{ Success bool }{Success: true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL, "test-key", "unarr-test")
|
||||
err := c.ReportUpgradeResult(context.Background(), UpgradeResult{
|
||||
AgentID: "agent-1",
|
||||
Success: true,
|
||||
Version: "2.0.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ReportUpgradeResult failed: %v", err)
|
||||
}
|
||||
if received.AgentID != "agent-1" {
|
||||
t.Errorf("agentId = %q, want agent-1", received.AgentID)
|
||||
}
|
||||
if !received.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
if received.Version != "2.0.0" {
|
||||
t.Errorf("version = %q, want 2.0.0", received.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportUpgradeResultFailure(t *testing.T) {
|
||||
var received UpgradeResult
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewDecoder(r.Body).Decode(&received)
|
||||
json.NewEncoder(w).Encode(struct{ Success bool }{Success: true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL, "test-key", "unarr-test")
|
||||
err := c.ReportUpgradeResult(context.Background(), UpgradeResult{
|
||||
AgentID: "agent-1",
|
||||
Success: false,
|
||||
Error: "checksum mismatch",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ReportUpgradeResult failed: %v", err)
|
||||
}
|
||||
if received.Success {
|
||||
t.Error("expected success=false")
|
||||
}
|
||||
if received.Error != "checksum mismatch" {
|
||||
t.Errorf("error = %q, want 'checksum mismatch'", received.Error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,23 +25,26 @@ type Daemon struct {
|
|||
transport Transport
|
||||
|
||||
// Callbacks
|
||||
OnTasksClaimed func(tasks []Task)
|
||||
OnStreamRequested func(req StreamRequest)
|
||||
OnUpgradeRequested func(version string)
|
||||
OnControlAction func(action, taskID string)
|
||||
OnTasksClaimed func(tasks []Task)
|
||||
OnStreamRequested func(req StreamRequest)
|
||||
OnControlAction func(action, taskID string)
|
||||
|
||||
// State
|
||||
User UserInfo
|
||||
Features FeatureFlags
|
||||
Info AgentInfo
|
||||
State DaemonState
|
||||
upgradeInProgress bool
|
||||
heartbeatFailures int
|
||||
User UserInfo
|
||||
Features FeatureFlags
|
||||
Info AgentInfo
|
||||
State DaemonState
|
||||
heartbeatFailures int
|
||||
lastNotifiedVersion string
|
||||
|
||||
// Callbacks for state tracking (set by cmd/daemon.go)
|
||||
GetActiveCount func() int
|
||||
GetCleanableBytes func() int64
|
||||
|
||||
// Watching tracks whether a user is viewing download progress in the web UI.
|
||||
// When false, the progress reporter skips detailed updates (only sends final states).
|
||||
Watching bool
|
||||
|
||||
// Exposed tickers for hot-reload
|
||||
PollTicker *time.Ticker
|
||||
HeartbeatTicker *time.Ticker
|
||||
|
|
@ -191,20 +194,18 @@ func (d *Daemon) heartbeat(ctx context.Context) {
|
|||
d.heartbeatFailures = 0
|
||||
}
|
||||
|
||||
// Update state file
|
||||
// Update watching flag and state file
|
||||
d.Watching = resp.Watching
|
||||
d.State.LastHeartbeat = time.Now()
|
||||
if d.GetActiveCount != nil {
|
||||
d.State.ActiveTasks = d.GetActiveCount()
|
||||
}
|
||||
WriteState(&d.State)
|
||||
|
||||
// Check for upgrade signal from server
|
||||
if resp.Upgrade != nil && resp.Upgrade.Version != "" && !d.upgradeInProgress {
|
||||
d.upgradeInProgress = true
|
||||
log.Printf("Upgrade requested by server: %s → %s", d.cfg.Version, resp.Upgrade.Version)
|
||||
if d.OnUpgradeRequested != nil {
|
||||
go d.OnUpgradeRequested(resp.Upgrade.Version)
|
||||
}
|
||||
// Log once per version when server suggests an upgrade
|
||||
if resp.Upgrade != nil && resp.Upgrade.Version != "" && resp.Upgrade.Version != d.lastNotifiedVersion {
|
||||
d.lastNotifiedVersion = resp.Upgrade.Version
|
||||
log.Printf("New version available: %s (run `unarr self-update` to upgrade)", resp.Upgrade.Version)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,12 +226,9 @@ func (d *Daemon) handleEvent(event ServerEvent) {
|
|||
}
|
||||
|
||||
case "upgrade":
|
||||
if event.Upgrade != nil && event.Upgrade.Version != "" && !d.upgradeInProgress {
|
||||
d.upgradeInProgress = true
|
||||
log.Printf("Upgrade requested via WebSocket: %s → %s", d.cfg.Version, event.Upgrade.Version)
|
||||
if d.OnUpgradeRequested != nil {
|
||||
go d.OnUpgradeRequested(event.Upgrade.Version)
|
||||
}
|
||||
if event.Upgrade != nil && event.Upgrade.Version != "" && event.Upgrade.Version != d.lastNotifiedVersion {
|
||||
d.lastNotifiedVersion = event.Upgrade.Version
|
||||
log.Printf("New version available: %s (run `unarr self-update` to upgrade)", event.Upgrade.Version)
|
||||
}
|
||||
|
||||
case "control":
|
||||
|
|
@ -253,11 +251,6 @@ func (d *Daemon) TriggerPoll() {
|
|||
}
|
||||
}
|
||||
|
||||
// ClearUpgradeInProgress resets the upgrade flag so a retry can be attempted.
|
||||
func (d *Daemon) ClearUpgradeInProgress() {
|
||||
d.upgradeInProgress = false
|
||||
}
|
||||
|
||||
func (d *Daemon) deregister() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -29,9 +29,6 @@ type Transport interface {
|
|||
// Deregister notifies the server of graceful shutdown.
|
||||
Deregister(ctx context.Context, agentID string) error
|
||||
|
||||
// ReportUpgradeResult reports upgrade outcome.
|
||||
ReportUpgradeResult(ctx context.Context, result UpgradeResult) error
|
||||
|
||||
// Events returns a channel that emits server-initiated events.
|
||||
// In HTTP mode this channel is never written to (polling handles it).
|
||||
// In WS mode, tasks/upgrade/control arrive here.
|
||||
|
|
|
|||
|
|
@ -134,23 +134,13 @@ func TestE2EFullLifecycle(t *testing.T) {
|
|||
t.Fatal("timeout waiting for cancel control")
|
||||
}
|
||||
|
||||
// 6. Send upgrade result
|
||||
err = tr.ReportUpgradeResult(ctx, UpgradeResult{
|
||||
AgentID: "e2e-agent",
|
||||
Success: true,
|
||||
Version: "2.0.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ReportUpgradeResult: %v", err)
|
||||
}
|
||||
|
||||
// Verify server received all messages
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if len(receivedMessages) < 4 {
|
||||
t.Fatalf("expected at least 4 messages, got %d", len(receivedMessages))
|
||||
if len(receivedMessages) < 3 {
|
||||
t.Fatalf("expected at least 3 messages, got %d", len(receivedMessages))
|
||||
}
|
||||
|
||||
types := make([]string, len(receivedMessages))
|
||||
|
|
@ -158,7 +148,7 @@ func TestE2EFullLifecycle(t *testing.T) {
|
|||
types[i], _ = m["type"].(string)
|
||||
}
|
||||
|
||||
expected := []string{"auth", "heartbeat", "progress", "upgrade-result"}
|
||||
expected := []string{"auth", "heartbeat", "progress"}
|
||||
for _, exp := range expected {
|
||||
found := false
|
||||
for _, got := range types {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ func (t *HTTPTransport) SendProgress(ctx context.Context, update StatusUpdate) (
|
|||
return t.client.ReportStatus(ctx, update)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) BatchReportStatus(ctx context.Context, updates []StatusUpdate) (*BatchStatusResponse, error) {
|
||||
return t.client.BatchReportStatus(ctx, updates)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) ClaimTasks(ctx context.Context, agentID string) (*TasksResponse, error) {
|
||||
return t.client.ClaimTasks(ctx, agentID)
|
||||
}
|
||||
|
|
@ -42,9 +46,5 @@ func (t *HTTPTransport) Deregister(ctx context.Context, agentID string) error {
|
|||
return t.client.Deregister(ctx, agentID)
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) ReportUpgradeResult(ctx context.Context, result UpgradeResult) error {
|
||||
return t.client.ReportUpgradeResult(ctx, result)
|
||||
}
|
||||
|
||||
// Client returns the underlying HTTP client for direct use if needed.
|
||||
func (t *HTTPTransport) Client() *Client { return t.client }
|
||||
|
|
|
|||
|
|
@ -120,18 +120,6 @@ func (h *HybridTransport) Deregister(ctx context.Context, agentID string) error
|
|||
return h.http.Deregister(ctx, agentID)
|
||||
}
|
||||
|
||||
// ReportUpgradeResult delegates to the active transport.
|
||||
func (h *HybridTransport) ReportUpgradeResult(ctx context.Context, result UpgradeResult) error {
|
||||
if h.mode.Load() == "ws" {
|
||||
if err := h.ws.ReportUpgradeResult(ctx, result); err != nil {
|
||||
h.switchToHTTP()
|
||||
return h.http.ReportUpgradeResult(ctx, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return h.http.ReportUpgradeResult(ctx, result)
|
||||
}
|
||||
|
||||
// ── Internal ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *HybridTransport) switchToHTTP() {
|
||||
|
|
|
|||
|
|
@ -209,22 +209,6 @@ func (t *WSTransport) Deregister(_ context.Context, _ string) error {
|
|||
return t.Close()
|
||||
}
|
||||
|
||||
// ReportUpgradeResult sends upgrade result to the DO.
|
||||
func (t *WSTransport) ReportUpgradeResult(_ context.Context, result UpgradeResult) error {
|
||||
msg := struct {
|
||||
Type string `json:"type"`
|
||||
Success bool `json:"success"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}{
|
||||
Type: "upgrade-result",
|
||||
Success: result.Success,
|
||||
Version: result.Version,
|
||||
Error: result.Error,
|
||||
}
|
||||
return t.send(msg)
|
||||
}
|
||||
|
||||
// ── Internal ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func (t *WSTransport) send(msg any) error {
|
||||
|
|
|
|||
|
|
@ -111,10 +111,21 @@ type StatusResponse struct {
|
|||
StreamRequested bool `json:"streamRequested,omitempty"`
|
||||
}
|
||||
|
||||
// BatchStatusRequest wraps multiple status updates in a single request.
|
||||
type BatchStatusRequest struct {
|
||||
Updates []StatusUpdate `json:"updates"`
|
||||
}
|
||||
|
||||
// BatchStatusResponse wraps per-task results from the batch endpoint.
|
||||
type BatchStatusResponse struct {
|
||||
Results []StatusResponse `json:"results"`
|
||||
}
|
||||
|
||||
// HeartbeatResponse is returned by the server on heartbeat.
|
||||
type HeartbeatResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Upgrade *UpgradeSignal `json:"upgrade,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Upgrade *UpgradeSignal `json:"upgrade,omitempty"`
|
||||
Watching bool `json:"watching,omitempty"` // true when a user is viewing download progress in the web UI
|
||||
}
|
||||
|
||||
// UpgradeSignal tells the agent to upgrade to a specific version.
|
||||
|
|
@ -122,14 +133,6 @@ type UpgradeSignal struct {
|
|||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// UpgradeResult is sent by the agent after an upgrade attempt.
|
||||
type UpgradeResult struct {
|
||||
AgentID string `json:"agentId"`
|
||||
Success bool `json:"success"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ErrorResponse is returned on API errors.
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue