evcc-io/server/updater/gokrazy.go

111 lines
2.1 KiB
Go

//go:build gokrazy
package updater
import (
"compress/gzip"
"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(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("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(); err != nil {
return fmt.Errorf("switching to non-active partition: %w", err)
}
u.Send("uploadMessage", "rebooting")
if err := target.Reboot(); err != nil {
return fmt.Errorf("reboot: %w", err)
}
return nil
}