summaryrefslogtreecommitdiffstats
path: root/modules/updatechecker/update_checker.go
blob: 0c93f08d218220603694f7c6d656f2d3ee401648 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package updatechecker

import (
	"context"
	"errors"
	"io"
	"net"
	"net/http"
	"strings"

	"code.gitea.io/gitea/modules/json"
	"code.gitea.io/gitea/modules/proxy"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/system"

	"github.com/hashicorp/go-version"
)

// CheckerState stores the remote version from the JSON endpoint
type CheckerState struct {
	LatestVersion string
}

// Name returns the name of the state item for update checker
func (r *CheckerState) Name() string {
	return "update-checker"
}

// GiteaUpdateChecker returns error when new version of Gitea is available
func GiteaUpdateChecker(httpEndpoint, domainEndpoint string) error {
	var version string
	var err error
	if domainEndpoint != "" {
		version, err = getVersionDNS(domainEndpoint)
	} else {
		version, err = getVersionHTTP(httpEndpoint)
	}

	if err != nil {
		return err
	}

	return UpdateRemoteVersion(context.Background(), version)
}

// getVersionDNS will request the TXT records for the domain. If a record starts
// with "forgejo_versions=" everything after that will be used as the latest
// version available.
func getVersionDNS(domainEndpoint string) (version string, err error) {
	records, err := net.LookupTXT(domainEndpoint)
	if err != nil {
		return "", err
	}

	if len(records) == 0 {
		return "", errors.New("no TXT records were found")
	}

	for _, record := range records {
		if strings.HasPrefix(record, "forgejo_versions=") {
			// Get all supported versions, separated by a comma.
			supportedVersions := strings.Split(strings.TrimPrefix(record, "forgejo_versions="), ",")
			// For now always return the latest supported version.
			return supportedVersions[len(supportedVersions)-1], nil
		}
	}

	return "", errors.New("there is no TXT record with a valid value")
}

// getVersionHTTP will make an HTTP request to the endpoint, and the returned
// content is JSON. The "latest.version" path's value will be used as the latest
// version available.
func getVersionHTTP(httpEndpoint string) (version string, err error) {
	httpClient := &http.Client{
		Transport: &http.Transport{
			Proxy: proxy.Proxy(),
		},
	}

	req, err := http.NewRequest("GET", httpEndpoint, nil)
	if err != nil {
		return "", err
	}
	resp, err := httpClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	type respType struct {
		Latest struct {
			Version string `json:"version"`
		} `json:"latest"`
	}
	respData := respType{}
	err = json.Unmarshal(body, &respData)
	if err != nil {
		return "", err
	}
	return respData.Latest.Version, nil
}

// UpdateRemoteVersion updates the latest available version of Gitea
func UpdateRemoteVersion(ctx context.Context, version string) (err error) {
	return system.AppState.Set(ctx, &CheckerState{LatestVersion: version})
}

// GetRemoteVersion returns the current remote version (or currently installed version if fail to fetch from DB)
func GetRemoteVersion(ctx context.Context) string {
	item := new(CheckerState)
	if err := system.AppState.Get(ctx, item); err != nil {
		return ""
	}
	return item.LatestVersion
}

// GetNeedUpdate returns true whether a newer version of Gitea is available
func GetNeedUpdate(ctx context.Context) bool {
	curVer, err := version.NewVersion(setting.AppVer)
	if err != nil {
		// return false to fail silently
		return false
	}
	remoteVerStr := GetRemoteVersion(ctx)
	if remoteVerStr == "" {
		// no remote version is known
		return false
	}
	remoteVer, err := version.NewVersion(remoteVerStr)
	if err != nil {
		// return false to fail silently
		return false
	}
	return curVer.LessThan(remoteVer)
}