A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more lint fixes. enable autofix

+178 -38
+9 -2
.golangci.yml
··· 1 1 # golangci-lint configuration for ATCR 2 2 # See: https://golangci-lint.run/usage/configuration/ 3 3 version: "2" 4 + 5 + issues: 6 + fix: true 7 + 4 8 linters: 5 9 settings: 6 10 staticcheck: ··· 25 29 linters: 26 30 - errcheck 27 31 28 - # TODO: fix issues and remove these paths one by one 29 - 30 32 formatters: 31 33 enable: 32 34 - gofmt 33 35 - goimports 36 + settings: 37 + gofmt: 38 + rewrite-rules: 39 + - pattern: 'interface{}' 40 + replacement: 'any'
+24 -7
cmd/credential-helper/main.go
··· 11 11 "os/exec" 12 12 "path/filepath" 13 13 "runtime" 14 + "strconv" 14 15 "strings" 15 16 "time" 16 17 ) ··· 385 386 } 386 387 387 388 var tokenResult DeviceTokenResponse 388 - json.NewDecoder(tokenResp.Body).Decode(&tokenResult) 389 + if err := json.NewDecoder(tokenResp.Body).Decode(&tokenResult); err != nil { 390 + fmt.Fprintf(os.Stderr, "\nFailed to decode response: %v\n", err) 391 + tokenResp.Body.Close() 392 + continue 393 + } 389 394 tokenResp.Body.Close() 390 395 391 396 if tokenResult.Error == "authorization_pending" { ··· 767 772 // Compare each part 768 773 for i := range min(len(newParts), len(curParts)) { 769 774 newNum := 0 775 + if parsed, err := strconv.Atoi(newParts[i]); err == nil { 776 + newNum = parsed 777 + } 770 778 curNum := 0 771 - fmt.Sscanf(newParts[i], "%d", &newNum) 772 - fmt.Sscanf(curParts[i], "%d", &curNum) 779 + if parsed, err := strconv.Atoi(curParts[i]); err == nil { 780 + curNum = parsed 781 + } 773 782 774 783 if newNum > curNum { 775 784 return true ··· 881 890 // Install new binary 882 891 if err := copyFile(binaryPath, currentPath); err != nil { 883 892 // Try to restore backup 884 - os.Rename(backupPath, currentPath) 893 + if renameErr := os.Rename(backupPath, currentPath); renameErr != nil { 894 + fmt.Fprintf(os.Stderr, "Warning: failed to restore backup: %v\n", renameErr) 895 + } 885 896 return fmt.Errorf("failed to install new binary: %w", err) 886 897 } 887 898 ··· 889 900 if err := os.Chmod(currentPath, 0755); err != nil { 890 901 // Try to restore backup 891 902 os.Remove(currentPath) 892 - os.Rename(backupPath, currentPath) 903 + if renameErr := os.Rename(backupPath, currentPath); renameErr != nil { 904 + fmt.Fprintf(os.Stderr, "Warning: failed to restore backup: %v\n", renameErr) 905 + } 893 906 return fmt.Errorf("failed to set permissions: %w", err) 894 907 } 895 908 ··· 1047 1060 1048 1061 // Ensure directory exists 1049 1062 dir := filepath.Dir(path) 1050 - os.MkdirAll(dir, 0700) 1063 + if err := os.MkdirAll(dir, 0700); err != nil { 1064 + return 1065 + } 1051 1066 1052 - os.WriteFile(path, data, 0600) 1067 + if err := os.WriteFile(path, data, 0600); err != nil { 1068 + return // Cache write failed, non-critical 1069 + } 1053 1070 }
+15 -13
pkg/appview/handlers/manifest_health.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "html/template" 6 + "log/slog" 5 7 "net/http" 6 8 "net/url" 7 9 "time" ··· 12 14 // ManifestHealthHandler handles HTMX polling for manifest health status 13 15 type ManifestHealthHandler struct { 14 16 HealthChecker *holdhealth.Checker 17 + Templates *template.Template 15 18 } 16 19 17 20 func (h *ManifestHealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ··· 61 64 func (h *ManifestHealthHandler) renderBadge(w http.ResponseWriter, endpoint string, reachable, pending bool) { 62 65 w.Header().Set("Content-Type", "text/html") 63 66 64 - if pending { 65 - // Still checking - render badge with HTMX retry after 3 seconds 66 - retryURL := "/api/manifest-health?endpoint=" + url.QueryEscape(endpoint) 67 - w.Write([]byte(`<span class="checking-badge" 68 - hx-get="` + retryURL + `" 69 - hx-trigger="load delay:3s" 70 - hx-swap="outerHTML"><i data-lucide="refresh-ccw"></i> Checking...</span>`)) 71 - } else if !reachable { 72 - // Unreachable - render offline badge 73 - w.Write([]byte(`<span class="offline-badge"><i data-lucide="triangle-alert"></i> Offline</span>`)) 74 - } else { 75 - // Reachable - no badge (empty response) 76 - w.Write([]byte(``)) 67 + data := struct { 68 + Pending bool 69 + Reachable bool 70 + RetryURL string 71 + }{ 72 + Pending: pending, 73 + Reachable: reachable, 74 + RetryURL: url.QueryEscape(endpoint), 75 + } 76 + 77 + if err := h.Templates.ExecuteTemplate(w, "health-badge", data); err != nil { 78 + slog.Warn("Failed to render health badge", "error", err) 77 79 } 78 80 }
+1 -3
pkg/appview/handlers/opengraph.go
··· 211 211 } 212 212 213 213 // Draw package icon with description-sized text 214 - if err := card.DrawIcon("package", int(layout.TextX), int(textY)-int(ogcard.FontDescription), int(ogcard.FontDescription), ogcard.ColorMuted); err != nil { 215 - slog.Warn("Failed to draw package icon", "error", err) 216 - } 214 + card.DrawIcon("package", int(layout.TextX), int(textY)-int(ogcard.FontDescription), int(ogcard.FontDescription), ogcard.ColorMuted) 217 215 card.DrawText(repoText, layout.TextX+42, textY, ogcard.FontDescription, ogcard.ColorMuted, ogcard.AlignLeft, false) 218 216 219 217 // ATCR branding (bottom right)
+8 -1
pkg/appview/handlers/settings.go
··· 73 73 // UpdateDefaultHoldHandler handles updating the default hold 74 74 type UpdateDefaultHoldHandler struct { 75 75 Refresher *oauth.Refresher 76 + Templates *template.Template 76 77 } 77 78 78 79 func (h *UpdateDefaultHoldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ··· 105 106 } 106 107 107 108 w.Header().Set("Content-Type", "text/html") 108 - w.Write([]byte(`<div class="success"><i data-lucide="check"></i> Default hold updated successfully!</div>`)) 109 + if err := h.Templates.ExecuteTemplate(w, "alert", map[string]string{ 110 + "Class": "success", 111 + "Icon": "check", 112 + "Message": "Default hold updated successfully!", 113 + }); err != nil { 114 + slog.Warn("Failed to render alert", "error", err) 115 + } 109 116 }
+7 -5
pkg/appview/ogcard/card.go
··· 9 9 _ "image/jpeg" // Register JPEG decoder for image.Decode 10 10 "image/png" 11 11 "io" 12 + "log/slog" 12 13 "net/http" 13 14 "time" 14 15 ··· 118 119 draw.Draw(c.img, rect, &image.Uniform{col}, image.Point{}, draw.Over) 119 120 } 120 121 121 - // DrawText draws text at the specified position 122 - func (c *Card) DrawText(text string, x, y float64, size float64, col color.Color, align int, bold bool) error { 122 + // DrawText draws text at the specified position. 123 + func (c *Card) DrawText(text string, x, y float64, size float64, col color.Color, align int, bold bool) { 123 124 f := regularFont 124 125 if bold { 125 126 f = boldFont 126 127 } 127 128 if f == nil { 128 - return nil // No font loaded 129 + return // No font loaded 129 130 } 130 131 131 132 ctx := freetype.NewContext() ··· 152 153 } 153 154 154 155 pt := freetype.Pt(int(x), int(y)) 155 - _, err := ctx.DrawString(text, pt) 156 - return err 156 + if _, err := ctx.DrawString(text, pt); err != nil { 157 + slog.Warn("Failed to draw text", "text", text, "error", err) 158 + } 157 159 } 158 160 159 161 // MeasureText returns the width of text in pixels
+7 -6
pkg/appview/ogcard/icons.go
··· 6 6 "image" 7 7 "image/color" 8 8 "image/draw" 9 + "log/slog" 9 10 "strings" 10 11 11 12 "github.com/srwiley/oksvg" ··· 28 29 "package": `<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`, 29 30 } 30 31 31 - // DrawIcon draws a Lucide icon at the specified position with the given size and color 32 - func (c *Card) DrawIcon(name string, x, y, size int, col color.Color) error { 32 + // DrawIcon draws a Lucide icon at the specified position with the given size and color. 33 + func (c *Card) DrawIcon(name string, x, y, size int, col color.Color) { 33 34 path, ok := iconPaths[name] 34 35 if !ok { 35 - return fmt.Errorf("unknown icon: %s", name) 36 + slog.Warn("Unknown icon", "name", name) 37 + return 36 38 } 37 39 38 40 // Build full SVG with color ··· 45 47 // Parse SVG 46 48 icon, err := oksvg.ReadIconStream(bytes.NewReader([]byte(svg))) 47 49 if err != nil { 48 - return fmt.Errorf("failed to parse icon SVG: %w", err) 50 + slog.Warn("Failed to parse icon SVG", "name", name, "error", err) 51 + return 49 52 } 50 53 51 54 // Create target image for the icon ··· 63 66 // Draw icon onto card 64 67 rect := image.Rect(x, y, x+size, y+size) 65 68 draw.Draw(c.img, rect, iconImg, image.Point{}, draw.Over) 66 - 67 - return nil 68 69 }
+2
pkg/appview/routes/routes.go
··· 146 146 // Manifest health check API endpoint (HTMX polling) 147 147 router.Get("/api/manifest-health", (&uihandlers.ManifestHealthHandler{ 148 148 HealthChecker: deps.HealthChecker, 149 + Templates: deps.Templates, 149 150 }).ServeHTTP) 150 151 151 152 router.Get("/u/{handle}", middleware.OptionalAuth(deps.SessionStore, deps.Database)( ··· 196 197 197 198 r.Post("/api/profile/default-hold", (&uihandlers.UpdateDefaultHoldHandler{ 198 199 Refresher: deps.Refresher, 200 + Templates: deps.Templates, 199 201 }).ServeHTTP) 200 202 201 203 r.Delete("/api/images/{repository}/tags/{tag}", (&uihandlers.DeleteTagHandler{
+3
pkg/appview/templates/partials/alert.html
··· 1 + {{ define "alert" }} 2 + <div class="{{ .Class }}"><i data-lucide="{{ .Icon }}"></i> {{ .Message }}</div> 3 + {{ end }}
+10
pkg/appview/templates/partials/health-badge.html
··· 1 + {{ define "health-badge" }} 2 + {{ if .Pending }} 3 + <span class="checking-badge" 4 + hx-get="/api/manifest-health?endpoint={{ .RetryURL }}" 5 + hx-trigger="load delay:3s" 6 + hx-swap="outerHTML"><i data-lucide="refresh-ccw"></i> Checking...</span> 7 + {{ else if not .Reachable }} 8 + <span class="offline-badge"><i data-lucide="triangle-alert"></i> Offline</span> 9 + {{ end }} 10 + {{ end }}
+91
pkg/appview/ui_test.go
··· 551 551 "install.html", 552 552 "manifest-modal", 553 553 "push-list.html", 554 + "health-badge", 555 + "alert", 554 556 } 555 557 556 558 for _, name := range expectedTemplates { ··· 680 682 t.Errorf("Template output %q does not contain expected %q", output, tt.expectInOutput) 681 683 } 682 684 }) 685 + } 686 + } 687 + 688 + func TestTemplateExecution_HealthBadge(t *testing.T) { 689 + tmpl, err := Templates() 690 + if err != nil { 691 + t.Fatalf("Templates() error = %v", err) 692 + } 693 + 694 + tests := []struct { 695 + name string 696 + data map[string]any 697 + expectInOutput string 698 + expectMissing string 699 + }{ 700 + { 701 + name: "pending state", 702 + data: map[string]any{ 703 + "Pending": true, 704 + "Reachable": false, 705 + "RetryURL": "http%3A%2F%2Fexample.com", 706 + }, 707 + expectInOutput: "checking-badge", 708 + expectMissing: "offline-badge", 709 + }, 710 + { 711 + name: "offline state", 712 + data: map[string]any{ 713 + "Pending": false, 714 + "Reachable": false, 715 + "RetryURL": "", 716 + }, 717 + expectInOutput: "offline-badge", 718 + expectMissing: "checking-badge", 719 + }, 720 + { 721 + name: "online state - empty output", 722 + data: map[string]any{ 723 + "Pending": false, 724 + "Reachable": true, 725 + "RetryURL": "", 726 + }, 727 + expectMissing: "badge", 728 + }, 729 + } 730 + 731 + for _, tt := range tests { 732 + t.Run(tt.name, func(t *testing.T) { 733 + buf := new(bytes.Buffer) 734 + err := tmpl.ExecuteTemplate(buf, "health-badge", tt.data) 735 + if err != nil { 736 + t.Fatalf("Failed to execute template: %v", err) 737 + } 738 + 739 + output := buf.String() 740 + if tt.expectInOutput != "" && !strings.Contains(output, tt.expectInOutput) { 741 + t.Errorf("Template output %q does not contain expected %q", output, tt.expectInOutput) 742 + } 743 + if tt.expectMissing != "" && strings.Contains(output, tt.expectMissing) { 744 + t.Errorf("Template output %q should not contain %q", output, tt.expectMissing) 745 + } 746 + }) 747 + } 748 + } 749 + 750 + func TestTemplateExecution_Alert(t *testing.T) { 751 + tmpl, err := Templates() 752 + if err != nil { 753 + t.Fatalf("Templates() error = %v", err) 754 + } 755 + 756 + data := map[string]string{ 757 + "Class": "success", 758 + "Icon": "check", 759 + "Message": "Operation completed!", 760 + } 761 + 762 + buf := new(bytes.Buffer) 763 + err = tmpl.ExecuteTemplate(buf, "alert", data) 764 + if err != nil { 765 + t.Fatalf("Failed to execute template: %v", err) 766 + } 767 + 768 + output := buf.String() 769 + expectedParts := []string{"success", "check", "Operation completed!"} 770 + for _, expected := range expectedParts { 771 + if !strings.Contains(output, expected) { 772 + t.Errorf("Template output %q does not contain expected %q", output, expected) 773 + } 683 774 } 684 775 } 685 776
+1 -1
pkg/hold/admin/auth.go
··· 87 87 } 88 88 89 89 // renderTemplate renders a template with the given data 90 - func (ui *AdminUI) renderTemplate(w http.ResponseWriter, name string, data interface{}) { 90 + func (ui *AdminUI) renderTemplate(w http.ResponseWriter, name string, data any) { 91 91 w.Header().Set("Content-Type", "text/html; charset=utf-8") 92 92 93 93 if err := ui.templates.ExecuteTemplate(w, name, data); err != nil {