A shepherd for your Appimages.
0

Configure Feed

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

code cleanup

Aly Raffauf (Jun 17, 2026, 10:33 PM EDT) ced51215 8e6d3e6e

+140 -140
+27 -27
cmd/appherder/appimage.go
··· 14 14 15 15 // openAppImage exposes a type-2 AppImage's squashfs payload as an fs.FS without 16 16 // executing it. The caller must invoke the returned closer to release the file. 17 - func openAppImage(file string) (fs.FS, func(), error) { 18 - f, err := os.Open(file) 17 + func openAppImage(path string) (fs.FS, func(), error) { 18 + file, err := os.Open(path) 19 19 if err != nil { 20 - return nil, nil, fmt.Errorf("open AppImage %s: %w", file, err) 20 + return nil, nil, fmt.Errorf("open AppImage %s: %w", path, err) 21 21 } 22 22 23 - offset, err := appImageSquashfsOffset(f) 23 + offset, err := appImageSquashfsOffset(file) 24 24 if err != nil { 25 - f.Close() 26 - return nil, nil, fmt.Errorf("read AppImage %s: %w", file, err) 25 + file.Close() 26 + return nil, nil, fmt.Errorf("read AppImage %s: %w", path, err) 27 27 } 28 28 29 - reader, err := squashfs.NewReaderAtOffset(f, offset) 29 + reader, err := squashfs.NewReaderAtOffset(file, offset) 30 30 if err != nil { 31 - f.Close() 32 - return nil, nil, fmt.Errorf("read AppImage filesystem %s: %w", file, err) 31 + file.Close() 32 + return nil, nil, fmt.Errorf("read AppImage filesystem %s: %w", path, err) 33 33 } 34 34 35 - return squashFS{root: &reader.FS}, func() { f.Close() }, nil 35 + return squashFS{root: &reader.FS}, func() { file.Close() }, nil 36 36 } 37 37 38 38 // squashFS adapts a squashfs reader to fs.FS, resolving symlinks on Open since ··· 68 68 69 69 // appImageSquashfsOffset returns the byte offset of the squashfs image appended 70 70 // to the AppImage's ELF runtime, computed from the ELF section header table. 71 - func appImageSquashfsOffset(r io.ReaderAt) (int64, error) { 72 - var h [64]byte 73 - if _, err := r.ReadAt(h[:], 0); err != nil { 71 + func appImageSquashfsOffset(reader io.ReaderAt) (int64, error) { 72 + var header [64]byte 73 + if _, err := reader.ReadAt(header[:], 0); err != nil { 74 74 return 0, fmt.Errorf("read ELF header: %w", err) 75 75 } 76 - if h[0] != 0x7f || h[1] != 'E' || h[2] != 'L' || h[3] != 'F' { 76 + if header[0] != 0x7f || header[1] != 'E' || header[2] != 'L' || header[3] != 'F' { 77 77 return 0, errors.New("not an AppImage (missing ELF header)") 78 78 } 79 79 // AppImages record their type in the otherwise-unused ELF padding bytes. 80 - if h[8] == 'A' && h[9] == 'I' && h[10] == 1 { 80 + if header[8] == 'A' && header[9] == 'I' && header[10] == 1 { 81 81 return 0, errors.New("type-1 AppImages are not supported") 82 82 } 83 83 84 - bo := binary.ByteOrder(binary.LittleEndian) 85 - if h[5] == 2 { 86 - bo = binary.BigEndian 84 + byteOrder := binary.ByteOrder(binary.LittleEndian) 85 + if header[5] == 2 { 86 + byteOrder = binary.BigEndian 87 87 } 88 88 // The squashfs starts where the ELF ends: e_shoff + e_shnum*e_shentsize. 89 - switch h[4] { 89 + switch header[4] { 90 90 case 1: // 32-bit ELF: e_shoff@32, e_shentsize@46, e_shnum@48 91 - return int64(bo.Uint32(h[32:36])) + int64(bo.Uint16(h[46:48]))*int64(bo.Uint16(h[48:50])), nil 91 + return int64(byteOrder.Uint32(header[32:36])) + int64(byteOrder.Uint16(header[46:48]))*int64(byteOrder.Uint16(header[48:50])), nil 92 92 case 2: // 64-bit ELF: e_shoff@40, e_shentsize@58, e_shnum@60 93 - return int64(bo.Uint64(h[40:48])) + int64(bo.Uint16(h[58:60]))*int64(bo.Uint16(h[60:62])), nil 93 + return int64(byteOrder.Uint64(header[40:48])) + int64(byteOrder.Uint16(header[58:60]))*int64(byteOrder.Uint16(header[60:62])), nil 94 94 default: 95 95 return 0, errors.New("unknown ELF class") 96 96 } ··· 132 132 } 133 133 default: 134 134 // Source lives elsewhere: copy it in. 135 - if err := writeAtomic(dest, 0o755, func(w io.Writer) error { 136 - return copyTo(file, w) 135 + if err := writeAtomic(dest, 0o755, func(writer io.Writer) error { 136 + return copyTo(file, writer) 137 137 }); err != nil { 138 138 return "", fmt.Errorf("install AppImage %s to %s: %w", file, dest, err) 139 139 } ··· 148 148 149 149 // samePath reports whether a and b resolve to the same file, accounting for 150 150 // symlinks and mounts (e.g. /home -> /var/home). 151 - func samePath(a, b string) bool { 152 - ai, err := os.Stat(a) 151 + func samePath(pathA, pathB string) bool { 152 + infoA, err := os.Stat(pathA) 153 153 if err != nil { 154 154 return false 155 155 } 156 - bi, err := os.Stat(b) 156 + infoB, err := os.Stat(pathB) 157 157 if err != nil { 158 158 return false 159 159 } 160 - return os.SameFile(ai, bi) 160 + return os.SameFile(infoA, infoB) 161 161 } 162 162 163 163 func copyTo(src string, dest io.Writer) error {
+7 -7
cmd/appherder/exec.go
··· 64 64 if err != nil { 65 65 return "" 66 66 } 67 - i := 0 68 - if i < len(tokens) && tokens[i] == "env" { 69 - i++ 67 + idx := 0 68 + if idx < len(tokens) && tokens[idx] == "env" { 69 + idx++ 70 70 } 71 - for i < len(tokens) && isEnvVar(tokens[i]) { 72 - i++ 71 + for idx < len(tokens) && isEnvVar(tokens[idx]) { 72 + idx++ 73 73 } 74 - if i < len(tokens) { 75 - return tokens[i] 74 + if idx < len(tokens) { 75 + return tokens[idx] 76 76 } 77 77 return "" 78 78 }
+12 -12
cmd/appherder/files.go
··· 56 56 57 57 // sameContent reports whether a and b are byte-identical, comparing size first 58 58 // to avoid hashing files that obviously differ. A missing b reports false. 59 - func sameContent(a, b string) (bool, error) { 60 - ia, err := os.Stat(a) 59 + func sameContent(pathA, pathB string) (bool, error) { 60 + infoA, err := os.Stat(pathA) 61 61 if err != nil { 62 62 return false, err 63 63 } 64 - ib, err := os.Stat(b) 64 + infoB, err := os.Stat(pathB) 65 65 if err != nil { 66 66 if errors.Is(err, os.ErrNotExist) { 67 67 return false, nil 68 68 } 69 69 return false, err 70 70 } 71 - if ia.Size() != ib.Size() { 71 + if infoA.Size() != infoB.Size() { 72 72 return false, nil 73 73 } 74 74 75 - ha, err := fileHash(a) 75 + hashA, err := fileHash(pathA) 76 76 if err != nil { 77 77 return false, err 78 78 } 79 - hb, err := fileHash(b) 79 + hashB, err := fileHash(pathB) 80 80 if err != nil { 81 81 return false, err 82 82 } 83 - return bytes.Equal(ha, hb), nil 83 + return bytes.Equal(hashA, hashB), nil 84 84 } 85 85 86 86 func fileHash(path string) ([]byte, error) { ··· 88 88 } 89 89 90 90 // fileSum returns h's checksum over the file's contents. 91 - func fileSum(path string, h hash.Hash) ([]byte, error) { 92 - f, err := os.Open(path) 91 + func fileSum(path string, hasher hash.Hash) ([]byte, error) { 92 + file, err := os.Open(path) 93 93 if err != nil { 94 94 return nil, err 95 95 } 96 - defer f.Close() 96 + defer file.Close() 97 97 98 - if _, err := io.Copy(h, f); err != nil { 98 + if _, err := io.Copy(hasher, file); err != nil { 99 99 return nil, err 100 100 } 101 - return h.Sum(nil), nil 101 + return hasher.Sum(nil), nil 102 102 } 103 103 104 104 func copyFromFS(fsys fs.FS, name string, dest string) error {
+5 -5
cmd/appherder/http.go
··· 36 36 // idleTimeoutReader cancels via cancel() when a single Read stalls longer than 37 37 // timeout, guarding a download against a connection that goes quiet. 38 38 type idleTimeoutReader struct { 39 - r io.Reader 39 + reader io.Reader 40 40 timer *time.Timer 41 41 timeout time.Duration 42 42 } 43 43 44 - func newIdleTimeoutReader(r io.Reader, timeout time.Duration, cancel context.CancelFunc) *idleTimeoutReader { 44 + func newIdleTimeoutReader(reader io.Reader, timeout time.Duration, cancel context.CancelFunc) *idleTimeoutReader { 45 45 timer := time.AfterFunc(timeout, cancel) 46 46 timer.Stop() 47 - return &idleTimeoutReader{r: r, timer: timer, timeout: timeout} 47 + return &idleTimeoutReader{reader: reader, timer: timer, timeout: timeout} 48 48 } 49 49 50 - func (t *idleTimeoutReader) Read(p []byte) (int, error) { 50 + func (t *idleTimeoutReader) Read(buf []byte) (int, error) { 51 51 t.timer.Reset(t.timeout) 52 - n, err := t.r.Read(p) 52 + n, err := t.reader.Read(buf) 53 53 t.timer.Stop() 54 54 return n, err 55 55 }
+6 -6
cmd/appherder/icons.go
··· 27 27 if err != nil { 28 28 return "" 29 29 } 30 - var p iconPicker 30 + var picker iconPicker 31 31 for _, entry := range entries { 32 32 if !entry.IsDir() { 33 - p.consider(entry.Name(), entry) 33 + picker.consider(entry.Name(), entry) 34 34 } 35 35 } 36 - return p.best 36 + return picker.best 37 37 } 38 38 39 39 func bestThemedIcon(fsys fs.FS) string { 40 - var p iconPicker 40 + var picker iconPicker 41 41 for _, dir := range []string{"usr/share/icons", "usr/share/pixmaps"} { 42 42 _ = fs.WalkDir(fsys, dir, func(name string, entry fs.DirEntry, err error) error { 43 43 if err != nil || entry.IsDir() { 44 44 return nil 45 45 } 46 - p.consider(name, entry) 46 + picker.consider(name, entry) 47 47 return nil 48 48 }) 49 49 } 50 - return p.best 50 + return picker.best 51 51 } 52 52 53 53 // iconPicker keeps the best icon seen, preferring format rank then larger size.
+11 -11
cmd/appherder/install.go
··· 21 21 22 22 // The squashfs reader parses untrusted input; turn any panic into an error. 23 23 defer func() { 24 - if r := recover(); r != nil { 25 - err = fmt.Errorf("read AppImage %s: %v", appimage, r) 24 + if recovered := recover(); recovered != nil { 25 + err = fmt.Errorf("read AppImage %s: %v", appimage, recovered) 26 26 } 27 27 }() 28 28 ··· 96 96 // sanitizeAppName lowercases s, turns spaces into underscores, and drops any 97 97 // character that isn't alphanumeric, underscore, or dot — GearLever's naming 98 98 // rule. 99 - func sanitizeAppName(s string) string { 100 - s = strings.ToLower(s) 101 - s = strings.ReplaceAll(s, " ", "_") 102 - var b strings.Builder 103 - b.Grow(len(s)) 104 - for _, r := range s { 105 - if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '.' { 106 - b.WriteRune(r) 99 + func sanitizeAppName(name string) string { 100 + name = strings.ToLower(name) 101 + name = strings.ReplaceAll(name, " ", "_") 102 + var builder strings.Builder 103 + builder.Grow(len(name)) 104 + for _, char := range name { 105 + if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '_' || char == '.' { 106 + builder.WriteRune(char) 107 107 } 108 108 } 109 - return b.String() 109 + return builder.String() 110 110 } 111 111 112 112 func appNameFromPath(path string) string {
+11 -11
cmd/appherder/source.go
··· 25 25 26 26 // checksum returns the strongest hash the release carries with a hasher to 27 27 // match; ok is false when the source provided no cryptographic hash. 28 - func (r release) checksum() (want string, h hash.Hash, ok bool) { 28 + func (r release) checksum() (want string, hasher hash.Hash, ok bool) { 29 29 switch { 30 30 case r.sha256 != "": 31 31 return r.sha256, sha256.New(), true ··· 38 38 // localMatches reports whether file already equals this release, by the 39 39 // strongest available signal: checksum, then mtime, then size. 40 40 func (r release) localMatches(file string) (bool, error) { 41 - if want, h, ok := r.checksum(); ok { 42 - sum, err := fileSum(file, h) 41 + if want, hasher, ok := r.checksum(); ok { 42 + sum, err := fileSum(file, hasher) 43 43 if err != nil { 44 44 return false, err 45 45 } ··· 64 64 // verifyDownload checks a freshly downloaded file against the release's 65 65 // checksum. With no checksum there is nothing to verify and it returns nil. 66 66 func (r release) verifyDownload(file string) error { 67 - want, h, ok := r.checksum() 67 + want, hasher, ok := r.checksum() 68 68 if !ok { 69 69 return nil 70 70 } 71 - sum, err := fileSum(file, h) 71 + sum, err := fileSum(file, hasher) 72 72 if err != nil { 73 73 return err 74 74 } ··· 85 85 86 86 // readUpdateInfo returns the AppImage's embedded update-information string from 87 87 // its .upd_info ELF section, or "" when absent or empty. 88 - func readUpdateInfo(file string) (string, error) { 89 - f, err := elf.Open(file) 88 + func readUpdateInfo(path string) (string, error) { 89 + file, err := elf.Open(path) 90 90 if err != nil { 91 - return "", fmt.Errorf("open AppImage %s: %w", file, err) 91 + return "", fmt.Errorf("open AppImage %s: %w", path, err) 92 92 } 93 - defer f.Close() 93 + defer file.Close() 94 94 95 - section := f.Section(".upd_info") 95 + section := file.Section(".upd_info") 96 96 if section == nil { 97 97 return "", nil 98 98 } 99 99 data, err := section.Data() 100 100 if err != nil { 101 - return "", fmt.Errorf("read .upd_info from %s: %w", file, err) 101 + return "", fmt.Errorf("read .upd_info from %s: %w", path, err) 102 102 } 103 103 return strings.TrimRight(string(data), "\x00"), nil 104 104 }
+5 -5
cmd/appherder/source_github.go
··· 82 82 // multiple matches it takes the first by name, for determinism. 83 83 func matchAsset(assets []ghAsset, pattern string) (ghAsset, error) { 84 84 var matches []ghAsset 85 - for _, a := range assets { 86 - if ok, _ := path.Match(pattern, a.Name); ok { 87 - matches = append(matches, a) 85 + for _, asset := range assets { 86 + if ok, _ := path.Match(pattern, asset.Name); ok { 87 + matches = append(matches, asset) 88 88 } 89 89 } 90 90 if len(matches) == 0 { ··· 98 98 // raise the API rate limit. It is never sent to asset download URLs. 99 99 func githubToken() string { 100 100 for _, key := range []string{"GH_TOKEN", "GITHUB_TOKEN"} { 101 - if v := os.Getenv(key); v != "" { 102 - return v 101 + if token := os.Getenv(key); token != "" { 102 + return token 103 103 } 104 104 } 105 105 return ""
+16 -16
cmd/appherder/source_gitlab.go
··· 79 79 80 80 // No digest from the API: take the checksum from the sibling .zsync asset 81 81 // when present, else comparison falls back to size. 82 - if z, ok := findLink(rel.Assets.Links, asset.Name+".zsync"); ok { 83 - header, err := fetchZsyncHeader(ctx, linkURL(z)) 82 + if zsyncLink, ok := findLink(rel.Assets.Links, asset.Name+".zsync"); ok { 83 + header, err := fetchZsyncHeader(ctx, linkURL(zsyncLink)) 84 84 if err != nil { 85 85 return release{}, err 86 86 } 87 87 out.sha1 = header["sha-1"] 88 - if n, err := strconv.ParseInt(header["length"], 10, 64); err == nil { 89 - out.size = n 88 + if size, err := strconv.ParseInt(header["length"], 10, 64); err == nil { 89 + out.size = size 90 90 } 91 91 } 92 92 return out, nil ··· 96 96 // the first by name when several match, for determinism. 97 97 func matchLink(links []glLink, pattern string) (glLink, error) { 98 98 var matches []glLink 99 - for _, l := range links { 100 - if ok, _ := path.Match(pattern, l.Name); ok { 101 - matches = append(matches, l) 99 + for _, link := range links { 100 + if ok, _ := path.Match(pattern, link.Name); ok { 101 + matches = append(matches, link) 102 102 } 103 103 } 104 104 if len(matches) == 0 { ··· 109 109 } 110 110 111 111 func findLink(links []glLink, name string) (glLink, bool) { 112 - for _, l := range links { 113 - if l.Name == name { 114 - return l, true 112 + for _, link := range links { 113 + if link.Name == name { 114 + return link, true 115 115 } 116 116 } 117 117 return glLink{}, false 118 118 } 119 119 120 120 // linkURL prefers the permalinked direct_asset_url, falling back to the raw url. 121 - func linkURL(l glLink) string { 122 - if l.DirectAssetURL != "" { 123 - return l.DirectAssetURL 121 + func linkURL(link glLink) string { 122 + if link.DirectAssetURL != "" { 123 + return link.DirectAssetURL 124 124 } 125 - return l.URL 125 + return link.URL 126 126 } 127 127 128 128 // gitlabToken returns a token from the environment, used for private projects 129 129 // and to raise rate limits. It is never sent to asset download URLs. 130 130 func gitlabToken() string { 131 131 for _, key := range []string{"GL_TOKEN", "GITLAB_TOKEN"} { 132 - if v := os.Getenv(key); v != "" { 133 - return v 132 + if token := os.Getenv(key); token != "" { 133 + return token 134 134 } 135 135 } 136 136 return ""
+6 -6
cmd/appherder/source_static.go
··· 25 25 defer resp.Body.Close() 26 26 27 27 rel := release{url: s.url, version: "latest"} 28 - if lm := resp.Header.Get("Last-Modified"); lm != "" { 29 - rel.version = lm 30 - if t, err := http.ParseTime(lm); err == nil { 31 - rel.modTime = t 28 + if lastModified := resp.Header.Get("Last-Modified"); lastModified != "" { 29 + rel.version = lastModified 30 + if modTime, err := http.ParseTime(lastModified); err == nil { 31 + rel.modTime = modTime 32 32 } 33 33 } 34 - if n, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); err == nil { 35 - rel.size = n 34 + if size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); err == nil { 35 + rel.size = size 36 36 } 37 37 return rel, nil 38 38 }
+8 -8
cmd/appherder/source_zsync.go
··· 35 35 sha1: header["sha-1"], 36 36 version: zsyncVersion(header), 37 37 } 38 - if n, err := strconv.ParseInt(header["length"], 10, 64); err == nil { 39 - rel.size = n 38 + if size, err := strconv.ParseInt(header["length"], 10, 64); err == nil { 39 + rel.size = size 40 40 } 41 41 return rel, nil 42 42 } ··· 70 70 // parseZsyncHeader reads a .zsync file's text header: "Key: Value" lines ending 71 71 // at the blank line that separates them from the binary checksum block. Keys 72 72 // are lowercased. It stops at the blank line so the body isn't consumed. 73 - func parseZsyncHeader(r io.Reader) (map[string]string, error) { 73 + func parseZsyncHeader(reader io.Reader) (map[string]string, error) { 74 74 header := make(map[string]string) 75 - scanner := bufio.NewScanner(r) 75 + scanner := bufio.NewScanner(reader) 76 76 for scanner.Scan() { 77 77 line := scanner.Text() 78 78 if line == "" { // header ends; binary data follows ··· 110 110 // zsyncVersion picks a human label for the build. zsync carries no version, so 111 111 // MTime (which changes per build) is the most useful, then the filename. 112 112 func zsyncVersion(header map[string]string) string { 113 - if v := header["mtime"]; v != "" { 114 - return v 113 + if value := header["mtime"]; value != "" { 114 + return value 115 115 } 116 - if v := header["filename"]; v != "" { 117 - return v 116 + if value := header["filename"]; value != "" { 117 + return value 118 118 } 119 119 return "latest" 120 120 }
+11 -11
cmd/appherder/sync.go
··· 40 40 results := parallelMap(ctx, files, installConcurrency, func(_ context.Context, f string) syncResult { 41 41 return syncResult{file: f, err: a.install(f)} 42 42 }) 43 - for _, r := range results { 44 - if r.err != nil { 45 - fmt.Fprintf(out, "skip %s: %v\n", filepath.Base(r.file), r.err) 43 + for _, result := range results { 44 + if result.err != nil { 45 + fmt.Fprintf(out, "skip %s: %v\n", filepath.Base(result.file), result.err) 46 46 } 47 47 } 48 48 ··· 85 85 return nil, err 86 86 } 87 87 var files []string 88 - for _, e := range entries { 89 - if e.IsDir() || strings.HasPrefix(e.Name(), ".") { 88 + for _, entry := range entries { 89 + if entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { 90 90 continue 91 91 } 92 - if !strings.EqualFold(filepath.Ext(e.Name()), ".appimage") { 92 + if !strings.EqualFold(filepath.Ext(entry.Name()), ".appimage") { 93 93 continue 94 94 } 95 - files = append(files, filepath.Join(dir, e.Name())) 95 + files = append(files, filepath.Join(dir, entry.Name())) 96 96 } 97 97 sort.Strings(files) 98 98 return files, nil ··· 108 108 } 109 109 return false, err 110 110 } 111 - for _, e := range entries { 112 - if e.IsDir() { 111 + for _, entry := range entries { 112 + if entry.IsDir() { 113 113 continue 114 114 } 115 - if strings.EqualFold(e.Name(), appid+".appimage") { 115 + if strings.EqualFold(entry.Name(), appid+".appimage") { 116 116 return true, nil 117 117 } 118 118 } ··· 135 135 if err != nil { 136 136 return nil, fmt.Errorf("read desktop file %s: %w", path, err) 137 137 } 138 - if v, ok := desktop.get(desktopOwnerKey, desktopEntrySection); ok && v == "true" { 138 + if value, ok := desktop.get(desktopOwnerKey, desktopEntrySection); ok && value == "true" { 139 139 continue 140 140 } 141 141 target := desktopTarget(desktop)
+5 -5
cmd/appherder/sync_test.go
··· 216 216 got := out.String() 217 217 want := []string{"alpha.appimage", "bravo.appimage", "charlie.appimage"} 218 218 pos := 0 219 - for _, w := range want { 220 - i := strings.Index(got[pos:], w) 221 - if i < 0 { 222 - t.Fatalf("output missing %s in order:\n%s", w, got) 219 + for _, wantName := range want { 220 + idx := strings.Index(got[pos:], wantName) 221 + if idx < 0 { 222 + t.Fatalf("output missing %s in order:\n%s", wantName, got) 223 223 } 224 - pos += i + len(w) 224 + pos += idx + len(wantName) 225 225 } 226 226 }
+10 -10
cmd/appherder/upgrade.go
··· 44 44 45 45 // Apply sequentially: bandwidth/disk-bound, and keeps output ordered. 46 46 available := 0 47 - for _, c := range checks { 47 + for _, check := range checks { 48 48 switch { 49 - case c.err != nil: 50 - fmt.Fprintf(out, "skip %s: %v\n", c.name, c.err) 49 + case check.err != nil: 50 + fmt.Fprintf(out, "skip %s: %v\n", check.name, check.err) 51 51 continue 52 - case c.noSource || !c.available: 52 + case check.noSource || !check.available: 53 53 continue 54 54 } 55 55 56 56 available++ 57 57 if checkOnly { 58 - fmt.Fprintf(out, "%s: update available (%s)\n", c.name, c.release.version) 58 + fmt.Fprintf(out, "%s: update available (%s)\n", check.name, check.release.version) 59 59 continue 60 60 } 61 61 62 - if err := a.applyUpgrade(ctx, c.release); err != nil { 63 - fmt.Fprintf(out, "skip %s: %v\n", c.name, err) 62 + if err := a.applyUpgrade(ctx, check.release); err != nil { 63 + fmt.Fprintf(out, "skip %s: %v\n", check.name, err) 64 64 continue 65 65 } 66 - fmt.Fprintf(out, "upgraded %s to %s\n", c.name, c.release.version) 66 + fmt.Fprintf(out, "upgraded %s to %s\n", check.name, check.release.version) 67 67 } 68 68 69 69 if available == 0 { ··· 141 141 return a.install(tmpName) 142 142 } 143 143 144 - func download(ctx context.Context, url string, w io.Writer) error { 144 + func download(ctx context.Context, url string, writer io.Writer) error { 145 145 ctx, cancel := context.WithCancel(ctx) 146 146 defer cancel() 147 147 ··· 157 157 if resp.StatusCode != http.StatusOK { 158 158 return fmt.Errorf("download %s: %s", url, resp.Status) 159 159 } 160 - _, err = io.Copy(w, newIdleTimeoutReader(resp.Body, downloadIdleTimeout, cancel)) 160 + _, err = io.Copy(writer, newIdleTimeoutReader(resp.Body, downloadIdleTimeout, cancel)) 161 161 return err 162 162 }