diff --git a/README.md b/README.md index d346c16..0de8d74 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ In-VM exec agent for [Cocoon](https://github.com/cocoonstack/cocoon)-managed VMs ## Highlights - Linux and Windows guest exec with stdin, stdout, stderr, and exit-code forwarding. -- Host access through `cocoon vm exec`; direct AF_VSOCK client for smoke tests. +- Host access through `cocoon vm exec`. - Linux clone/restore reseed with optional machine-ID regeneration. PTY mode is planned; see @@ -16,7 +16,7 @@ PTY mode is planned; see ## Quick start ```bash -cocoon-agent client --cid 3 --port 1024 -- echo "hello from guest" +cocoon vm exec -- echo "hello from guest" ``` Full steps in [Usage](docs/usage.md). diff --git a/agent/exec.go b/agent/exec.go index 0bd7608..8e84279 100644 --- a/agent/exec.go +++ b/agent/exec.go @@ -101,10 +101,9 @@ func runExec(parentCtx context.Context, argv []string, env map[string]string, st } exitCode := 0 - var exitErr *exec.ExitError - switch { + switch exitErr, isExit := errors.AsType[*exec.ExitError](waitErr); { case waitErr == nil: - case errors.As(waitErr, &exitErr): + case isExit: exitCode = exitErr.ExitCode() case errors.Is(waitErr, exec.ErrWaitDelay): // Pipes were abandoned to a background child; the command itself exited. diff --git a/agent/sysproc_windows.go b/agent/sysproc_windows.go index 8a82cf4..e5c2c47 100644 --- a/agent/sysproc_windows.go +++ b/agent/sysproc_windows.go @@ -14,31 +14,6 @@ import ( var ntResumeProcess = windows.NewLazySystemDLL("ntdll.dll").NewProc("NtResumeProcess") -// windowsProcessController owns the Job Object of one runExec session; cmd.Cancel and runExec's deferred close both release it. -type windowsProcessController struct { - job windows.Handle - close func() -} - -func (c *windowsProcessController) assign(cmd *exec.Cmd) error { - pid := uint32(cmd.Process.Pid) //nolint:gosec // the OS hands out PIDs as DWORDs; the int round-trip can't overflow - proc, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_SUSPEND_RESUME, false, pid) - if err != nil { - return fmt.Errorf("open process: %w", err) - } - defer windows.CloseHandle(proc) //nolint:errcheck - - if err := windows.AssignProcessToJobObject(c.job, proc); err != nil { - return fmt.Errorf("assign process to job object: %w", err) - } - return resumeProcess(proc) -} - -func (c *windowsProcessController) cancel() error { - c.close() - return nil -} - // setupProcess creates a kill-on-close Job Object so the child's whole process tree dies with the session. func setupProcess(cmd *exec.Cmd) (processController, error) { job, err := windows.CreateJobObject(nil, nil) @@ -58,16 +33,30 @@ func setupProcess(cmd *exec.Cmd) (processController, error) { return processController{}, fmt.Errorf("set job object limits: %w", err) } - ctl := &windowsProcessController{job: job, close: sync.OnceFunc(func() { _ = windows.CloseHandle(job) })} + closeJob := sync.OnceFunc(func() { _ = windows.CloseHandle(job) }) cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: windows.CREATE_SUSPENDED} - cmd.Cancel = ctl.cancel + cmd.Cancel = func() error { closeJob(); return nil } return processController{ - afterStart: ctl.assign, - close: ctl.close, + afterStart: func(cmd *exec.Cmd) error { return assignToJob(job, cmd) }, + close: closeJob, }, nil } +func assignToJob(job windows.Handle, cmd *exec.Cmd) error { + pid := uint32(cmd.Process.Pid) //nolint:gosec // the OS hands out PIDs as DWORDs; the int round-trip can't overflow + proc, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE|windows.PROCESS_SUSPEND_RESUME, false, pid) + if err != nil { + return fmt.Errorf("open process: %w", err) + } + defer windows.CloseHandle(proc) //nolint:errcheck + + if err := windows.AssignProcessToJobObject(job, proc); err != nil { + return fmt.Errorf("assign process to job object: %w", err) + } + return resumeProcess(proc) +} + func resumeProcess(proc windows.Handle) error { if err := ntResumeProcess.Find(); err != nil { return err diff --git a/cmd/client.go b/cmd/client.go deleted file mode 100644 index f42feca..0000000 --- a/cmd/client.go +++ /dev/null @@ -1,49 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" - - "github.com/cocoonstack/cocoon-agent/agent" - "github.com/cocoonstack/cocoon-agent/client" -) - -// exitCodeError carries the child exit code through cobra's RunE so defers run before os.Exit. -type exitCodeError struct{ code int } - -func (e *exitCodeError) Error() string { return fmt.Sprintf("exit code %d", e.code) } - -func newClientCmd() *cobra.Command { - var ( - cid uint32 - port uint32 - ) - - cmd := &cobra.Command{ - Use: "client [flags] -- ...", - Short: "Run a command on a remote cocoon-agent (debug / smoke test)", - Args: cobra.MinimumNArgs(1), - RunE: func(c *cobra.Command, args []string) error { - ctx := c.Context() - conn, err := dialVsock(cid, port) - if err != nil { - return fmt.Errorf("dial vsock cid=%d port=%d: %w", cid, port, err) - } - // client.Run closes conn via its runCancel goroutine, so no defer Close here - exitCode, err := client.Run(ctx, conn, args, nil, os.Stdin, os.Stdout, os.Stderr) - if err != nil { - return err - } - if exitCode != 0 { - return &exitCodeError{code: exitCode} - } - return nil - }, - } - cmd.Flags().Uint32Var(&cid, "cid", 0, "vsock CID of the target VM (host-side; from hypervisor)") - cmd.Flags().Uint32Var(&port, "port", agent.DefaultPort, "vsock port the agent is listening on") - _ = cmd.MarkFlagRequired("cid") - return cmd -} diff --git a/cmd/root.go b/cmd/root.go index 0e55b49..a898cc6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,7 +3,6 @@ package cmd import ( "context" - "errors" "fmt" "os" "os/signal" @@ -21,12 +20,11 @@ func NewRootCmd() *cobra.Command { Use: "cocoon-agent", Short: "vsock-based command exec agent for Cocoon-managed VMs", Version: fmt.Sprintf("%s (rev=%s built=%s)", version.VERSION, version.REVISION, version.BUILTAT), - // run logs and maps exit codes itself, so cobra's Error/Usage dump would double up + // run logs the error itself, so cobra's Error/Usage dump would double up SilenceErrors: true, SilenceUsage: true, } rootCmd.AddCommand(newServeCmd()) - rootCmd.AddCommand(newClientCmd()) return rootCmd } @@ -53,9 +51,6 @@ func run() int { } if err := NewRootCmd().ExecuteContext(ctx); err != nil { - if ec, ok := errors.AsType[*exitCodeError](err); ok { - return ec.code - } log.WithFunc("cmd.run").Error(ctx, err, "command failed") return 1 } diff --git a/cmd/serve.go b/cmd/serve.go index a6428ca..71af8dc 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -30,7 +30,7 @@ func newServeCmd() *cobra.Command { return fmt.Errorf("listen vsock port %d: %w", port, err) } srv := agent.NewServer(listener) - log.WithFunc("cmd.newServeCmd").Infof(ctx, "cocoon-agent serving vsock port %d", port) + log.WithFunc("cmd.serve").Infof(ctx, "cocoon-agent serving vsock port %d", port) return srv.Serve(ctx) }, } diff --git a/cmd/transport_linux.go b/cmd/transport_linux.go index 0909bb5..451f075 100644 --- a/cmd/transport_linux.go +++ b/cmd/transport_linux.go @@ -5,33 +5,12 @@ package cmd import ( "context" "fmt" - "io" "net" "github.com/mdlayher/vsock" "github.com/projecteru2/core/log" ) -func listenVsock(ctx context.Context, port uint32) (net.Listener, error) { - l, err := vsock.Listen(port, nil) - if err != nil { - return nil, fmt.Errorf("vsock listen: %w", err) - } - return &hostOnlyListener{ - Listener: l, - ctx: ctx, - logger: log.WithFunc("cmd.hostOnlyListener.Accept"), - }, nil -} - -func dialVsock(cid, port uint32) (io.ReadWriteCloser, error) { - conn, err := vsock.Dial(cid, port, nil) - if err != nil { - return nil, fmt.Errorf("vsock dial: %w", err) - } - return conn, nil -} - var _ net.Listener = (*hostOnlyListener)(nil) // Peers other than VMADDR_CID_HOST are rejected: VMADDR_CID_LOCAL loopback would let an unprivileged guest process run commands as root. @@ -55,6 +34,18 @@ func (l *hostOnlyListener) Accept() (net.Conn, error) { } } +func listenVsock(ctx context.Context, port uint32) (net.Listener, error) { + l, err := vsock.Listen(port, nil) + if err != nil { + return nil, fmt.Errorf("vsock listen: %w", err) + } + return &hostOnlyListener{ + Listener: l, + ctx: ctx, + logger: log.WithFunc("cmd.hostOnlyListener.Accept"), + }, nil +} + func isHostPeer(conn net.Conn) bool { addr, ok := conn.RemoteAddr().(*vsock.Addr) if !ok { diff --git a/cmd/transport_linux_test.go b/cmd/transport_linux_test.go index e008edd..2721a3b 100644 --- a/cmd/transport_linux_test.go +++ b/cmd/transport_linux_test.go @@ -3,10 +3,8 @@ package cmd import ( - "io" "net" "testing" - "time" "github.com/mdlayher/vsock" ) @@ -45,14 +43,8 @@ func TestIsHostPeer(t *testing.T) { } type staticAddrConn struct { + net.Conn addr net.Addr } -func (c *staticAddrConn) Read(_ []byte) (int, error) { return 0, io.EOF } -func (c *staticAddrConn) Write(p []byte) (int, error) { return len(p), nil } -func (c *staticAddrConn) Close() error { return nil } -func (c *staticAddrConn) LocalAddr() net.Addr { return c.addr } -func (c *staticAddrConn) RemoteAddr() net.Addr { return c.addr } -func (c *staticAddrConn) SetDeadline(_ time.Time) error { return nil } -func (c *staticAddrConn) SetReadDeadline(_ time.Time) error { return nil } -func (c *staticAddrConn) SetWriteDeadline(_ time.Time) error { return nil } +func (c *staticAddrConn) RemoteAddr() net.Addr { return c.addr } diff --git a/cmd/transport_other.go b/cmd/transport_other.go index 12fb6a5..90fd106 100644 --- a/cmd/transport_other.go +++ b/cmd/transport_other.go @@ -5,7 +5,6 @@ package cmd import ( "context" "errors" - "io" "net" ) @@ -15,7 +14,3 @@ var errVsockUnsupported = errors.New("vsock is only supported on linux and windo func listenVsock(_ context.Context, _ uint32) (net.Listener, error) { return nil, errVsockUnsupported } - -func dialVsock(_, _ uint32) (io.ReadWriteCloser, error) { - return nil, errVsockUnsupported -} diff --git a/cmd/transport_windows.go b/cmd/transport_windows.go index d21d0c3..d4bc1af 100644 --- a/cmd/transport_windows.go +++ b/cmd/transport_windows.go @@ -37,7 +37,6 @@ var ( procBind = modws2_32.NewProc("bind") procListen = modws2_32.NewProc("listen") procAccept = modws2_32.NewProc("accept") - procConnect = modws2_32.NewProc("connect") procRecv = modws2_32.NewProc("recv") procSend = modws2_32.NewProc("send") procWSAStartup = modws2_32.NewProc("WSAStartup") @@ -192,21 +191,13 @@ type vsockAddr struct { func (a *vsockAddr) Network() string { return "vsock" } func (a *vsockAddr) String() string { return fmt.Sprintf("vsock://%d:%d", a.cid, a.port) } -func newVsockSocket() (windows.Handle, error) { +func listenVsock(ctx context.Context, port uint32) (net.Listener, error) { if err := wsaInit(); err != nil { - return 0, err + return nil, err } h, err := windows.Socket(afVsock, windows.SOCK_STREAM, 0) if err != nil { - return 0, fmt.Errorf("vsock socket: %w", err) - } - return h, nil -} - -func listenVsock(ctx context.Context, port uint32) (net.Listener, error) { - h, err := newVsockSocket() - if err != nil { - return nil, err + return nil, fmt.Errorf("vsock socket: %w", err) } sa := sockaddrVM{Family: afVsock, Port: port, CID: vmAddrCidAny} r, _, callErr := procBind.Call(uintptr(h), uintptr(unsafe.Pointer(&sa)), uintptr(sockaddrVMSize)) //nolint:gosec // winsock bind requires raw pointer @@ -226,21 +217,3 @@ func listenVsock(ctx context.Context, port uint32) (net.Listener, error) { logger: log.WithFunc("cmd.vsockListener.Accept"), }, nil } - -func dialVsock(cid, port uint32) (io.ReadWriteCloser, error) { - h, err := newVsockSocket() - if err != nil { - return nil, err - } - sa := sockaddrVM{Family: afVsock, Port: port, CID: cid} - r, _, callErr := procConnect.Call(uintptr(h), uintptr(unsafe.Pointer(&sa)), uintptr(sockaddrVMSize)) //nolint:gosec // winsock connect requires raw pointer - if r == socketError { - _ = windows.Closesocket(h) - return nil, fmt.Errorf("vsock connect %d:%d: %w", cid, port, callErr) - } - return &vsockConn{ - h: h, - peerCID: cid, - peerPort: port, - }, nil -} diff --git a/docs/architecture.md b/docs/architecture.md index c298eb2..f404a27 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,7 +9,7 @@ Cocoon's host components (vk-cocoon, cocoon CLI) and the VMs they manage always - requires per-VM credentials to be provisioned and rotated - comes with the full SSH protocol surface (handshake, key exchange, host key trust) -vsock is host↔guest only, has no IP layer, and the kernel `vhost_vsock` module is the auth boundary — anything that can connect from the host is already privileged on that host. cocoon-agent leverages this for a small focused daemon that gives kubectl-exec semantics (stdin / stdout / stderr / exit code) with none of those dependencies. +vsock is host↔guest only, has no IP layer, and access to the hypervisor's host-side vsock socket (for cocoon, the per-VM hybrid-vsock Unix socket) is the auth boundary — anything that can connect from the host is already privileged on that host. cocoon-agent leverages this for a small focused daemon that gives kubectl-exec semantics (stdin / stdout / stderr / exit code) with none of those dependencies. ## Status diff --git a/docs/configuration.md b/docs/configuration.md index 9fe4eba..d8da913 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,5 +9,3 @@ CLI flags: | Flag | Default | Notes | |---|---|---| | `serve --port` | `1024` | vsock port to bind on | -| `client --cid` | required | target VM CID (host-side) | -| `client --port` | `1024` | matches `serve --port` | diff --git a/docs/index.md b/docs/index.md index aa2bb40..71260a3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,8 +26,8 @@ host (cocoon node) guest VM status, the wire protocol, and the roadmap - [Installation](install.md) — baking the Linux binary into an image, the Windows service installer, and pipe-mode limitations -- [Usage](usage.md) — smoke-testing an agent from the host with the - `client` subcommand +- [Usage](usage.md) — smoke-testing an agent from the host with + `cocoon vm exec` - [Configuration](configuration.md) — environment variables and CLI flags - [Build](build.md) — cross-compiling for Linux and Windows guests diff --git a/docs/install.md b/docs/install.md index ede236f..52d2b7d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -37,7 +37,7 @@ Windows support requires the `viosock` driver shipped with **virtio-win >= 0.1.2 .\install-cocoon-agent.ps1 ``` -The script copies `cocoon-agent.exe` to `C:\Program Files\Cocoon\`, registers a Windows service (`cocoon-agent`, `LocalSystem`, auto-start, restart-on-crash), and starts it. See [`packaging/install-cocoon-agent.ps1`](../packaging/install-cocoon-agent.ps1). +The script copies `cocoon-agent.exe` to `C:\Program Files\Cocoon\`, registers a Windows service (`cocoon-agent`, `LocalSystem`, auto-start, restart on failure), and starts it. See [`packaging/install-cocoon-agent.ps1`](../packaging/install-cocoon-agent.ps1). Verify it's running: diff --git a/docs/usage.md b/docs/usage.md index 1688412..2314d94 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -2,11 +2,11 @@ ## Smoke test from the host -cocoon-agent ships a `client` subcommand for vsock smoke tests without needing to plumb through cocoon CLI / vk-cocoon. It dials AF_VSOCK by CID, so take the guest CID from the hypervisor's vsock config (cocoon itself reaches the agent over the hybrid-vsock Unix socket that `cocoon vm inspect` reports as `vsock_socket`, not by CID) and run: +The host reaches the agent through cocoon: `cocoon vm exec` dials the VM's hybrid-vsock Unix socket and speaks the agent protocol. ```bash -cocoon-agent client --cid 3 --port 1024 -- echo "hello from guest" -echo "world" | cocoon-agent client --cid 3 --port 1024 -- cat +cocoon vm exec -- echo "hello from guest" +echo "world" | cocoon vm exec -i -- cat ``` -Exit codes pass through. +Stdin is attached only with `-i`; exit codes pass through. diff --git a/go.mod b/go.mod index 779c032..6a4d343 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/cocoonstack/cocoon-agent go 1.27.0 require ( - github.com/mdlayher/vsock v1.2.1 + github.com/mdlayher/vsock v1.3.0 github.com/projecteru2/core v0.1.3 github.com/spf13/cobra v1.10.2 - golang.org/x/sys v0.47.0 + golang.org/x/sys v0.48.0 ) require ( @@ -21,7 +21,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect - github.com/mdlayher/socket v0.5.1 // indirect + github.com/mdlayher/socket v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rogpeppe/go-internal v1.16.0 // indirect github.com/rs/zerolog v1.35.1 // indirect @@ -30,7 +30,7 @@ require ( golang.org/x/sync v0.22.0 // indirect golang.org/x/text v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect - google.golang.org/grpc v1.83.1 // indirect + google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect ) diff --git a/go.sum b/go.sum index 4388a8b..7e8a935 100644 --- a/go.sum +++ b/go.sum @@ -28,10 +28,10 @@ github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= -github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= -github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= +github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18= +github.com/mdlayher/vsock v1.3.0 h1:bqQfZ1OznI03y6YiXp2sze05RVdzLn/zsfjnjd4+ivI= +github.com/mdlayher/vsock v1.3.0/go.mod h1:WsuksavOvwCnV5UqGHUkvAvCy+Dqy81y4goKQTzxxNY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -82,8 +82,8 @@ golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= @@ -98,8 +98,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc= google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= -google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/packaging/install-cocoon-agent.ps1 b/packaging/install-cocoon-agent.ps1 index dfed20b..594e4b7 100644 --- a/packaging/install-cocoon-agent.ps1 +++ b/packaging/install-cocoon-agent.ps1 @@ -83,11 +83,13 @@ if ($existing) { Write-Step "creating service $ServiceName" Invoke-Bat ('sc.exe create {0} binPath= "\"{1}\" serve --port {2}" start= auto DisplayName= "{3}"' -f $ServiceName, $BinaryDest, $Port, $DisplayName) Invoke-Bat ('sc.exe description {0} "{1}"' -f $ServiceName, $Description) - # 24h reset window mirrors Linux's Restart=always intent — a flapping - # bug self-heals instead of permanently bricking the agent. - Invoke-Bat ('sc.exe failure {0} reset= 86400 actions= restart/5000/restart/5000/restart/5000' -f $ServiceName) } +# 24h reset window mirrors Linux's Restart=always intent — a flapping +# bug self-heals instead of permanently bricking the agent. +Invoke-Bat ('sc.exe failure {0} reset= 86400 actions= restart/5000/restart/5000/restart/5000' -f $ServiceName) +Invoke-Bat ('sc.exe failureflag {0} 1' -f $ServiceName) + # Confirm registration before attempting Start-Service; otherwise an SCM hiccup # in step 3 would surface here as a misleading "service not found" error. $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue