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:
2026-09-01 20:17:06 +03:00
parent 2318684e93
commit 1bba2c6b51
13 changed files with 338 additions and 16 deletions
+79
View File
@@ -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
}