evcc-io/server/updater/gokrazy.go
Michael Stapelberg b9e7fb126a
Some checks failed
Release / call-build-workflow (push) Has been cancelled
Release / Publish Docker :release (push) Has been cancelled
Release / Github & APT (push) Has been cancelled
Release / Demo (push) Has been cancelled
Release / Hassio Addon (push) Has been cancelled
server/updater: update to latest gokrazy/updater (needs a context) (#21564)
2025-06-01 10:17:12 +02:00

112 lines
2.2 KiB
Go

//go:build gokrazy
package updater
import (
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"net/http"
"sync/atomic"
"github.com/gokrazy/updater"
)
// update constants
const (
mb = 1024 * 1024
rootOffset = 8192*512 + 100*mb
rootSize = 500 * mb
)
var (
Host = "localhost"
Port = 8080
Password = "SECRET"
)
// unzipReader transparently unpacks zip files
func unzipReader(file io.ReadCloser) (io.ReadCloser, error) {
if unzipped, err := gzip.NewReader(file); err == nil {
return unzipped, nil
}
return file, nil
}
type countingWriter struct {
count int
C chan int
}
func (cw *countingWriter) Write(p []byte) (n int, err error) {
cw.count += len(p)
cw.C <- cw.count
return len(p), nil
}
var mutex int32
// Update request handler
func (u *watch) execute(assetID int64, size int) error {
if !atomic.CompareAndSwapInt32(&mutex, 0, 1) {
return errors.New("upgrade already running")
}
rootFS, err := u.repo.StreamAsset(assetID)
if err != nil {
return err
}
if rootFS, err = unzipReader(rootFS); err != nil {
rootFS.Close()
return err
}
uri := fmt.Sprintf("http://gokrazy:%s@%s:%d/", Password, Host, Port)
target, err := updater.NewTarget(context.TODO(), uri, http.DefaultClient)
if err != nil {
return err
}
// stream async to device
go u.executeAsync(target, rootFS, size)
return nil
}
func (u *watch) executeAsync(target *updater.Target, rootFS io.ReadCloser, size int) error {
defer func() {
atomic.StoreInt32(&mutex, 0)
rootFS.Close()
}()
cw := &countingWriter{C: make(chan int)}
go func() {
for v := range cw.C {
u.Send("uploadProgress", 100*v/size)
}
u.Send("uploadProgress", 100)
}()
u.Send("uploadMessage", "uploading")
if err := target.StreamTo(context.TODO(), "root", io.TeeReader(rootFS, cw)); err != nil {
return fmt.Errorf("updating root file system: %w", err)
}
close(cw.C) // upload finished
u.Send("uploadMessage", "switching to non-active partition")
if err := target.Switch(context.TODO()); err != nil {
return fmt.Errorf("switching to non-active partition: %w", err)
}
u.Send("uploadMessage", "rebooting")
if err := target.Reboot(context.TODO()); err != nil {
return fmt.Errorf("reboot: %w", err)
}
return nil
}