feat(ota): verify offline update checksums

This commit is contained in:
watermeko
2026-08-03 02:56:16 +00:00
committed by Guoguo
parent 7cda3b2920
commit 0f8ef20fb5
32 changed files with 286 additions and 51 deletions

View File

@@ -39,10 +39,16 @@ func (s *Service) Update(c *gin.Context) {
rsp.OkRsp(c)
log.Debugf("update application success")
// Sleep for a second before restarting the device
go restartServices()
}
func restartServices() {
// Let the HTTP response reach the client before stopping the server.
time.Sleep(1 * time.Second)
_ = exec.Command("sh", "-c", "/kvmapp/system/init.d/S95nanokvm restart").Run()
if err := exec.Command("/kvmapp/system/init.d/S95nanokvm", "restart").Run(); err != nil {
log.Errorf("failed to restart services after update: %v", err)
}
}
func update() error {
@@ -88,7 +94,7 @@ func download(url string, target string) (err error) {
}
var req *http.Request
req, err = http.NewRequest("GET", url, nil)
req, err = utils.NewAuthenticatedRequest("GET", url, nil)
if err != nil {
log.Errorf("new request err: %s", err)
continue

View File

@@ -1,15 +1,16 @@
package application
import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"fmt"
"io"
"mime/multipart"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"NanoKVM-Server/proto"
"github.com/gin-gonic/gin"
@@ -35,11 +36,15 @@ func (s *Service) OfflineUpdate(c *gin.Context) {
rsp.OkRsp(c)
log.Debugf("offline update application success")
time.Sleep(1 * time.Second)
_ = exec.Command("sh", "-c", "/kvmapp/system/init.d/S95nanokvm restart").Run()
go restartServices()
}
func offlineUpdate(c *gin.Context) error {
expectedSHA256, err := parseSHA256Checksum(c.GetHeader("X-SHA256-Checksum"))
if err != nil {
return err
}
_ = os.RemoveAll(CacheDir)
_ = os.MkdirAll(CacheDir, 0o755)
defer func() {
@@ -63,6 +68,11 @@ func offlineUpdate(c *gin.Context) error {
return err
}
if err := verifySHA256Checksum(target, expectedSHA256); err != nil {
log.Errorf("failed to verify install package: %v", err)
return err
}
if err := installPackage(target); err != nil {
log.Errorf("failed to install package: %v", err)
return err
@@ -71,6 +81,43 @@ func offlineUpdate(c *gin.Context) error {
return nil
}
func parseSHA256Checksum(value string) ([]byte, error) {
value = strings.TrimSpace(value)
if value == "" {
return nil, nil
}
checksum, err := hex.DecodeString(value)
if err != nil || len(checksum) != sha256.Size {
return nil, fmt.Errorf("invalid sha256 checksum")
}
return checksum, nil
}
func verifySHA256Checksum(filePath string, expected []byte) error {
if len(expected) == 0 {
return nil
}
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open uploaded file: %w", err)
}
defer file.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return fmt.Errorf("failed to calculate sha256 checksum: %w", err)
}
if subtle.ConstantTimeCompare(hasher.Sum(nil), expected) != 1 {
return fmt.Errorf("sha256 checksum mismatch")
}
return nil
}
func createSentinelFile() error {
file, err := os.OpenFile(
sentinelPath,

View File

@@ -14,6 +14,7 @@ import (
"time"
"NanoKVM-Server/proto"
"NanoKVM-Server/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -32,7 +33,7 @@ const (
)
var (
latestClient = &http.Client{Timeout: 15 * time.Second}
latestClient = utils.NewUpdateHTTPClient(15 * time.Second)
packageNamePattern = regexp.MustCompile(`^nanokvm_[0-9]+\.[0-9]+\.[0-9]+\.tar\.gz$`)
versionPattern = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$`)
)
@@ -84,7 +85,11 @@ func getLatest() (*Latest, error) {
query.Set("now", fmt.Sprintf("%d", time.Now().Unix()))
parsedManifestURL.RawQuery = query.Encode()
resp, err := latestClient.Get(parsedManifestURL.String())
request, err := utils.NewAuthenticatedRequest("GET", parsedManifestURL.String(), nil)
if err != nil {
return nil, err
}
resp, err := latestClient.Do(request)
if err != nil {
log.Debugf("failed to request version from %s", parsedManifestURL.Redacted())
return nil, errors.New("update server is inaccessible")