From 0c75d06c591e9b7abd7eafd049f2184c75d17de0 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 31 Jul 2026 11:23:15 +0200 Subject: [PATCH] remote: auto-upgrade ollie-remote on each tool call Before each CallTool, check if the local ollie-remote binary hash differs from what was deployed. If so, close the SSH connection, re-dial (triggering bootstrap to upload the new binary), and swap the connection transparently. Emits info events when upgrade is detected and when it completes. --- toolsrv/remote.go | 67 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/toolsrv/remote.go b/toolsrv/remote.go index 6434dd2..49bac0f 100644 --- a/toolsrv/remote.go +++ b/toolsrv/remote.go @@ -48,6 +48,11 @@ type RemoteServer struct { dec *json.Decoder nextID atomic.Int64 Info HostInfo + + // For auto-upgrade on hash mismatch. + cfg RemoteConfig + ctx context.Context + deployedHash string } // Config holds the parameters for connecting to a remote host. @@ -198,11 +203,14 @@ func RemoteDial(ctx context.Context, cfg RemoteConfig) (*RemoteServer, error) { // Bootstrap complete — stdin/stdout are now JSON-RPC. s := &RemoteServer{ - stdin: stdin, - stdout: stdout, - cmd: cmd, - enc: json.NewEncoder(stdin), - dec: json.NewDecoder(bufio.NewReader(stdout)), + stdin: stdin, + stdout: stdout, + cmd: cmd, + enc: json.NewEncoder(stdin), + dec: json.NewDecoder(bufio.NewReader(stdout)), + cfg: cfg, + ctx: ctx, + deployedHash: hashStr, } // Verify connectivity with a ping. @@ -277,6 +285,50 @@ func (s *RemoteServer) Close() error { return nil } +// checkUpgrade compares the local ollie-remote binary hash to the deployed +// hash. If they differ, it closes the current connection and re-dials, +// which triggers the bootstrap to upload the new binary. +func (s *RemoteServer) checkUpgrade() error { + localBin, err := findLocalBinary() + if err != nil { + return nil // can't find binary, skip check + } + binData, err := os.ReadFile(localBin) + if err != nil { + return nil + } + hash := sha256.Sum256(binData) + hashStr := hex.EncodeToString(hash[:]) + + if hashStr == s.deployedHash { + return nil + } + + // Hash changed — reconnect. + StreamOutput(s.ctx, "[info] ollie-remote binary changed, upgrading remote...\n") + + s.Close() + + fresh, err := RemoteDial(s.ctx, s.cfg) + if err != nil { + return err + } + + // Swap internals from the fresh connection. + s.mu.Lock() + s.stdin = fresh.stdin + s.stdout = fresh.stdout + s.cmd = fresh.cmd + s.enc = fresh.enc + s.dec = fresh.dec + s.deployedHash = fresh.deployedHash + s.Info = fresh.Info + s.mu.Unlock() + + StreamOutput(s.ctx, "[info] remote binary upgraded successfully\n") + return nil +} + // --- Server interface --- func (s *RemoteServer) ListTools() ([]ToolInfo, error) { @@ -311,6 +363,11 @@ func (s *RemoteServer) ListTools() ([]ToolInfo, error) { } func (s *RemoteServer) CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) { + // Check if local binary has been updated; reconnect if so. + if err := s.checkUpgrade(); err != nil { + StreamOutput(ctx, "[info] remote upgrade failed: "+err.Error()+"\n") + } + // Forward the call with the tool name as the RPC method. id := s.nextID.Add(1)