112 lines
2.2 KiB
Go
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
|
|
}
|