1
0
forked from wrenn/wrenn

Add background process execution API

Start long-running processes (web servers, daemons) without blocking the
HTTP request. Leverages envd's existing background process support
(context.Background(), List, Connect, SendSignal RPCs) and wires it
through the host agent and control plane layers.

New API surface:
- POST /v1/capsules/{id}/exec with background:true → 202 {pid, tag}
- GET /v1/capsules/{id}/processes → list running processes
- DELETE /v1/capsules/{id}/processes/{selector} → kill by PID or tag
- WS /v1/capsules/{id}/processes/{selector}/stream → reconnect to output

The {selector} param auto-detects: numeric = PID, string = tag.
Tags are auto-generated as "proc-" + 8 hex chars if not provided.
This commit is contained in:
2026-04-14 03:57:01 +06:00
parent 962860ba74
commit 516890c49a
10 changed files with 1876 additions and 73 deletions

View File

@ -29,9 +29,13 @@ func newExecHandler(db *db.Queries, pool *lifecycle.HostClientPool) *execHandler
} }
type execRequest struct { type execRequest struct {
Cmd string `json:"cmd"` Cmd string `json:"cmd"`
Args []string `json:"args"` Args []string `json:"args"`
TimeoutSec int32 `json:"timeout_sec"` TimeoutSec int32 `json:"timeout_sec"`
Background bool `json:"background"`
Tag string `json:"tag"`
Envs map[string]string `json:"envs"`
Cwd string `json:"cwd"`
} }
type execResponse struct { type execResponse struct {
@ -45,6 +49,13 @@ type execResponse struct {
Encoding string `json:"encoding"` Encoding string `json:"encoding"`
} }
type backgroundExecResponse struct {
SandboxID string `json:"sandbox_id"`
Cmd string `json:"cmd"`
PID uint32 `json:"pid"`
Tag string `json:"tag"`
}
// Exec handles POST /v1/capsules/{id}/exec. // Exec handles POST /v1/capsules/{id}/exec.
func (h *execHandler) Exec(w http.ResponseWriter, r *http.Request) { func (h *execHandler) Exec(w http.ResponseWriter, r *http.Request) {
sandboxIDStr := chi.URLParam(r, "id") sandboxIDStr := chi.URLParam(r, "id")
@ -78,14 +89,54 @@ func (h *execHandler) Exec(w http.ResponseWriter, r *http.Request) {
return return
} }
start := time.Now()
agent, err := agentForHost(ctx, h.db, h.pool, sb.HostID) agent, err := agentForHost(ctx, h.db, h.pool, sb.HostID)
if err != nil { if err != nil {
writeError(w, http.StatusServiceUnavailable, "host_unavailable", "sandbox host is not reachable") writeError(w, http.StatusServiceUnavailable, "host_unavailable", "sandbox host is not reachable")
return return
} }
// Background mode: start process and return immediately.
if req.Background {
tag := req.Tag
if tag == "" {
tag = "proc-" + id.NewPtyTag()
}
bgResp, err := agent.StartBackground(ctx, connect.NewRequest(&pb.StartBackgroundRequest{
SandboxId: sandboxIDStr,
Tag: tag,
Cmd: req.Cmd,
Args: req.Args,
Envs: req.Envs,
Cwd: req.Cwd,
}))
if err != nil {
status, code, msg := agentErrToHTTP(err)
writeError(w, status, code, msg)
return
}
if err := h.db.UpdateLastActive(ctx, db.UpdateLastActiveParams{
ID: sandboxID,
LastActiveAt: pgtype.Timestamptz{
Time: time.Now(),
Valid: true,
},
}); err != nil {
slog.Warn("failed to update last_active_at", "id", sandboxIDStr, "error", err)
}
writeJSON(w, http.StatusAccepted, backgroundExecResponse{
SandboxID: sandboxIDStr,
Cmd: req.Cmd,
PID: bgResp.Msg.Pid,
Tag: bgResp.Msg.Tag,
})
return
}
start := time.Now()
resp, err := agent.Exec(ctx, connect.NewRequest(&pb.ExecRequest{ resp, err := agent.Exec(ctx, connect.NewRequest(&pb.ExecRequest{
SandboxId: sandboxIDStr, SandboxId: sandboxIDStr,
Cmd: req.Cmd, Cmd: req.Cmd,

View File

@ -0,0 +1,266 @@
package api
import (
"context"
"log/slog"
"net/http"
"strconv"
"time"
"connectrpc.com/connect"
"github.com/go-chi/chi/v5"
"github.com/gorilla/websocket"
"github.com/jackc/pgx/v5/pgtype"
"git.omukk.dev/wrenn/wrenn/internal/auth"
"git.omukk.dev/wrenn/wrenn/internal/db"
"git.omukk.dev/wrenn/wrenn/internal/id"
"git.omukk.dev/wrenn/wrenn/internal/lifecycle"
pb "git.omukk.dev/wrenn/wrenn/proto/hostagent/gen"
)
type processHandler struct {
db *db.Queries
pool *lifecycle.HostClientPool
}
func newProcessHandler(db *db.Queries, pool *lifecycle.HostClientPool) *processHandler {
return &processHandler{db: db, pool: pool}
}
// processResponse is a single entry in the process list.
type processResponse struct {
PID uint32 `json:"pid"`
Tag string `json:"tag,omitempty"`
Cmd string `json:"cmd"`
Args []string `json:"args,omitempty"`
}
// processListResponse wraps the list of processes.
type processListResponse struct {
Processes []processResponse `json:"processes"`
}
// ListProcesses handles GET /v1/capsules/{id}/processes.
func (h *processHandler) ListProcesses(w http.ResponseWriter, r *http.Request) {
sandboxIDStr := chi.URLParam(r, "id")
ctx := r.Context()
ac := auth.MustFromContext(ctx)
sandboxID, err := id.ParseSandboxID(sandboxIDStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request", "invalid sandbox ID")
return
}
sb, err := h.db.GetSandboxByTeam(ctx, db.GetSandboxByTeamParams{ID: sandboxID, TeamID: ac.TeamID})
if err != nil {
writeError(w, http.StatusNotFound, "not_found", "sandbox not found")
return
}
if sb.Status != "running" {
writeError(w, http.StatusConflict, "invalid_state", "sandbox is not running (status: "+sb.Status+")")
return
}
agent, err := agentForHost(ctx, h.db, h.pool, sb.HostID)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "host_unavailable", "sandbox host is not reachable")
return
}
resp, err := agent.ListProcesses(ctx, connect.NewRequest(&pb.ListProcessesRequest{
SandboxId: sandboxIDStr,
}))
if err != nil {
status, code, msg := agentErrToHTTP(err)
writeError(w, status, code, msg)
return
}
procs := make([]processResponse, 0, len(resp.Msg.Processes))
for _, p := range resp.Msg.Processes {
procs = append(procs, processResponse{
PID: p.Pid,
Tag: p.Tag,
Cmd: p.Cmd,
Args: p.Args,
})
}
writeJSON(w, http.StatusOK, processListResponse{Processes: procs})
}
// KillProcess handles DELETE /v1/capsules/{id}/processes/{selector}.
// The selector can be a numeric PID or a string tag.
func (h *processHandler) KillProcess(w http.ResponseWriter, r *http.Request) {
sandboxIDStr := chi.URLParam(r, "id")
selectorStr := chi.URLParam(r, "selector")
ctx := r.Context()
ac := auth.MustFromContext(ctx)
sandboxID, err := id.ParseSandboxID(sandboxIDStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request", "invalid sandbox ID")
return
}
sb, err := h.db.GetSandboxByTeam(ctx, db.GetSandboxByTeamParams{ID: sandboxID, TeamID: ac.TeamID})
if err != nil {
writeError(w, http.StatusNotFound, "not_found", "sandbox not found")
return
}
if sb.Status != "running" {
writeError(w, http.StatusConflict, "invalid_state", "sandbox is not running (status: "+sb.Status+")")
return
}
agent, err := agentForHost(ctx, h.db, h.pool, sb.HostID)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "host_unavailable", "sandbox host is not reachable")
return
}
// Build the kill request with PID or tag selector.
killReq := &pb.KillProcessRequest{
SandboxId: sandboxIDStr,
Signal: "SIGKILL",
}
if sig := r.URL.Query().Get("signal"); sig == "SIGTERM" {
killReq.Signal = "SIGTERM"
}
if pid, err := strconv.ParseUint(selectorStr, 10, 32); err == nil {
killReq.Selector = &pb.KillProcessRequest_Pid{Pid: uint32(pid)}
} else {
killReq.Selector = &pb.KillProcessRequest_Tag{Tag: selectorStr}
}
if _, err := agent.KillProcess(ctx, connect.NewRequest(killReq)); err != nil {
status, code, msg := agentErrToHTTP(err)
writeError(w, status, code, msg)
return
}
w.WriteHeader(http.StatusNoContent)
}
// wsProcessOut is the JSON message sent to the WebSocket client.
type wsProcessOut struct {
Type string `json:"type"` // "start", "stdout", "stderr", "exit", "error"
PID uint32 `json:"pid,omitempty"` // only for "start"
Data string `json:"data,omitempty"` // only for "stdout", "stderr", "error"
ExitCode *int32 `json:"exit_code,omitempty"` // only for "exit"
}
// ConnectProcess handles WS /v1/capsules/{id}/processes/{selector}/stream.
func (h *processHandler) ConnectProcess(w http.ResponseWriter, r *http.Request) {
sandboxIDStr := chi.URLParam(r, "id")
selectorStr := chi.URLParam(r, "selector")
ctx := r.Context()
ac := auth.MustFromContext(ctx)
sandboxID, err := id.ParseSandboxID(sandboxIDStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request", "invalid sandbox ID")
return
}
sb, err := h.db.GetSandboxByTeam(ctx, db.GetSandboxByTeamParams{ID: sandboxID, TeamID: ac.TeamID})
if err != nil {
writeError(w, http.StatusNotFound, "not_found", "sandbox not found")
return
}
if sb.Status != "running" {
writeError(w, http.StatusConflict, "invalid_state", "sandbox is not running (status: "+sb.Status+")")
return
}
agent, err := agentForHost(ctx, h.db, h.pool, sb.HostID)
if err != nil {
writeError(w, http.StatusServiceUnavailable, "host_unavailable", "sandbox host is not reachable")
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
slog.Error("process stream websocket upgrade failed", "error", err)
return
}
defer conn.Close()
// Build the connect request with PID or tag selector.
connectReq := &pb.ConnectProcessRequest{
SandboxId: sandboxIDStr,
}
if pid, err := strconv.ParseUint(selectorStr, 10, 32); err == nil {
connectReq.Selector = &pb.ConnectProcessRequest_Pid{Pid: uint32(pid)}
} else {
connectReq.Selector = &pb.ConnectProcessRequest_Tag{Tag: selectorStr}
}
streamCtx, cancel := context.WithCancel(ctx)
defer cancel()
stream, err := agent.ConnectProcess(streamCtx, connect.NewRequest(connectReq))
if err != nil {
sendProcessWSError(conn, "failed to connect to process: "+err.Error())
return
}
defer stream.Close()
// Listen for client disconnect in a goroutine.
go func() {
for {
_, _, err := conn.ReadMessage()
if err != nil {
cancel()
return
}
}
}()
// Forward stream events to WebSocket.
for stream.Receive() {
resp := stream.Msg()
switch ev := resp.Event.(type) {
case *pb.ConnectProcessResponse_Start:
writeWSJSON(conn, wsProcessOut{Type: "start", PID: ev.Start.Pid})
case *pb.ConnectProcessResponse_Data:
switch o := ev.Data.Output.(type) {
case *pb.ExecStreamData_Stdout:
writeWSJSON(conn, wsProcessOut{Type: "stdout", Data: string(o.Stdout)})
case *pb.ExecStreamData_Stderr:
writeWSJSON(conn, wsProcessOut{Type: "stderr", Data: string(o.Stderr)})
}
case *pb.ConnectProcessResponse_End:
exitCode := ev.End.ExitCode
writeWSJSON(conn, wsProcessOut{Type: "exit", ExitCode: &exitCode})
}
}
if err := stream.Err(); err != nil {
if streamCtx.Err() == nil {
sendProcessWSError(conn, err.Error())
}
}
// Update last active using a fresh context.
updateCtx, updateCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer updateCancel()
if err := h.db.UpdateLastActive(updateCtx, db.UpdateLastActiveParams{
ID: sandboxID,
LastActiveAt: pgtype.Timestamptz{
Time: time.Now(),
Valid: true,
},
}); err != nil {
slog.Warn("failed to update last active after process stream", "sandbox_id", sandboxIDStr, "error", err)
}
}
func sendProcessWSError(conn *websocket.Conn, msg string) {
writeWSJSON(conn, wsProcessOut{Type: "error", Data: msg})
}

View File

@ -699,11 +699,17 @@ paths:
$ref: "#/components/schemas/ExecRequest" $ref: "#/components/schemas/ExecRequest"
responses: responses:
"200": "200":
description: Command output description: Command output (foreground exec)
content: content:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/ExecResponse" $ref: "#/components/schemas/ExecResponse"
"202":
description: Background process started
content:
application/json:
schema:
$ref: "#/components/schemas/BackgroundExecResponse"
"404": "404":
description: Capsule not found description: Capsule not found
content: content:
@ -717,6 +723,122 @@ paths:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes:
parameters:
- name: id
in: path
required: true
schema:
type: string
get:
summary: List running processes
operationId: listProcesses
tags: [capsules]
security:
- apiKeyAuth: []
description: |
Returns all running processes inside the capsule, including background
processes and any processes started by templates or init scripts.
responses:
"200":
description: Process list
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessListResponse"
"404":
description: Capsule not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes/{selector}:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: selector
in: path
required: true
description: Process PID (numeric) or tag (string)
schema:
type: string
delete:
summary: Kill a process
operationId: killProcess
tags: [capsules]
security:
- apiKeyAuth: []
parameters:
- name: signal
in: query
required: false
description: Signal to send (SIGKILL or SIGTERM, default SIGKILL)
schema:
type: string
enum: [SIGKILL, SIGTERM]
default: SIGKILL
responses:
"204":
description: Process killed
"404":
description: Capsule or process not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"409":
description: Capsule not running
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/v1/capsules/{id}/processes/{selector}/stream:
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: selector
in: path
required: true
description: Process PID (numeric) or tag (string)
schema:
type: string
get:
summary: Stream process output via WebSocket
operationId: connectProcess
tags: [capsules]
security:
- apiKeyAuth: []
description: |
Opens a WebSocket connection to stream stdout/stderr from a running
background process. The selector can be a numeric PID or a string tag.
Server sends JSON messages:
- `{"type": "start", "pid": 42}` — connected to process
- `{"type": "stdout", "data": "..."}` — stdout output
- `{"type": "stderr", "data": "..."}` — stderr output
- `{"type": "exit", "exit_code": 0}` — process exited
- `{"type": "error", "data": "..."}` — error message
responses:
"101":
description: WebSocket upgrade
/v1/capsules/{id}/ping: /v1/capsules/{id}/ping:
parameters: parameters:
- name: id - name: id
@ -2153,6 +2275,56 @@ components:
timeout_sec: timeout_sec:
type: integer type: integer
default: 30 default: 30
description: Timeout in seconds (foreground exec only, default 30)
background:
type: boolean
default: false
description: If true, starts the process in the background and returns immediately with a PID and tag (HTTP 202)
tag:
type: string
description: Optional user-chosen tag for the background process. Auto-generated if omitted. Only used when background is true.
envs:
type: object
additionalProperties:
type: string
description: Environment variables for the process (background exec only)
cwd:
type: string
description: Working directory for the process (background exec only)
BackgroundExecResponse:
type: object
properties:
sandbox_id:
type: string
cmd:
type: string
pid:
type: integer
tag:
type: string
ProcessEntry:
type: object
properties:
pid:
type: integer
tag:
type: string
cmd:
type: string
args:
type: array
items:
type: string
ProcessListResponse:
type: object
properties:
processes:
type: array
items:
$ref: "#/components/schemas/ProcessEntry"
ExecResponse: ExecResponse:
type: object type: object

View File

@ -74,6 +74,7 @@ func New(
buildH := newBuildHandler(buildSvc, queries, pool) buildH := newBuildHandler(buildSvc, queries, pool)
channelH := newChannelHandler(channelSvc, al) channelH := newChannelHandler(channelSvc, al)
ptyH := newPtyHandler(queries, pool) ptyH := newPtyHandler(queries, pool)
processH := newProcessHandler(queries, pool)
adminCapsules := newAdminCapsuleHandler(sandboxSvc, queries, pool, al) adminCapsules := newAdminCapsuleHandler(sandboxSvc, queries, pool, al)
// OpenAPI spec and docs. // OpenAPI spec and docs.
@ -141,6 +142,9 @@ func New(
r.Post("/files/remove", fsH.Remove) r.Post("/files/remove", fsH.Remove)
r.Get("/metrics", metricsH.GetMetrics) r.Get("/metrics", metricsH.GetMetrics)
r.Get("/pty", ptyH.PtySession) r.Get("/pty", ptyH.PtySession)
r.Get("/processes", processH.ListProcesses)
r.Delete("/processes/{selector}", processH.KillProcess)
r.Get("/processes/{selector}/stream", processH.ConnectProcess)
}) })
}) })
@ -224,6 +228,9 @@ func New(
r.Post("/files/remove", fsH.Remove) r.Post("/files/remove", fsH.Remove)
r.Get("/metrics", metricsH.GetMetrics) r.Get("/metrics", metricsH.GetMetrics)
r.Get("/pty", ptyH.PtySession) r.Get("/pty", ptyH.PtySession)
r.Get("/processes", processH.ListProcesses)
r.Delete("/processes/{selector}", processH.KillProcess)
r.Get("/processes/{selector}/stream", processH.ConnectProcess)
}) })
}) })

View File

@ -0,0 +1,187 @@
package envdclient
import (
"context"
"fmt"
"io"
"log/slog"
"connectrpc.com/connect"
envdpb "git.omukk.dev/wrenn/wrenn/proto/envd/gen"
)
// ProcessInfo holds metadata about a running process inside the sandbox.
type ProcessInfo struct {
PID uint32
Tag string
Cmd string
Args []string
}
// StartBackground starts a process that runs independently of the RPC stream.
// It opens a Start stream, reads the first StartEvent to obtain the PID,
// then closes the stream. The process continues running inside the VM because
// envd binds it to context.Background().
func (c *Client) StartBackground(ctx context.Context, tag, cmd string, args []string, envs map[string]string, cwd string) (uint32, error) {
stdin := false
cfg := &envdpb.ProcessConfig{
Cmd: cmd,
Args: args,
Envs: envs,
}
if cwd != "" {
cfg.Cwd = &cwd
}
req := connect.NewRequest(&envdpb.StartRequest{
Process: cfg,
Tag: &tag,
Stdin: &stdin,
})
stream, err := c.process.Start(ctx, req)
if err != nil {
return 0, fmt.Errorf("start background process: %w", err)
}
defer stream.Close()
// Read events until we get the StartEvent with the PID.
for stream.Receive() {
msg := stream.Msg()
if msg.Event == nil {
continue
}
if start, ok := msg.Event.GetEvent().(*envdpb.ProcessEvent_Start); ok {
return start.Start.GetPid(), nil
}
}
if err := stream.Err(); err != nil && err != io.EOF {
return 0, fmt.Errorf("start background process stream: %w", err)
}
return 0, fmt.Errorf("start background process: no start event received")
}
// ConnectProcess re-attaches to a running process by PID or tag and returns
// a channel of streaming events. The channel is closed when the process ends
// or the context is cancelled.
func (c *Client) ConnectProcess(ctx context.Context, pid uint32, tag string) (<-chan ExecStreamEvent, error) {
var selector *envdpb.ProcessSelector
if tag != "" {
selector = &envdpb.ProcessSelector{
Selector: &envdpb.ProcessSelector_Tag{Tag: tag},
}
} else {
selector = &envdpb.ProcessSelector{
Selector: &envdpb.ProcessSelector_Pid{Pid: pid},
}
}
stream, err := c.process.Connect(ctx, connect.NewRequest(&envdpb.ConnectRequest{
Process: selector,
}))
if err != nil {
return nil, fmt.Errorf("connect process: %w", err)
}
ch := make(chan ExecStreamEvent, 16)
go func() {
defer close(ch)
defer stream.Close()
for stream.Receive() {
msg := stream.Msg()
if msg.Event == nil {
continue
}
var ev ExecStreamEvent
switch e := msg.Event.GetEvent().(type) {
case *envdpb.ProcessEvent_Start:
ev = ExecStreamEvent{Type: "start", PID: e.Start.GetPid()}
case *envdpb.ProcessEvent_Data:
switch o := e.Data.GetOutput().(type) {
case *envdpb.ProcessEvent_DataEvent_Stdout:
ev = ExecStreamEvent{Type: "stdout", Data: o.Stdout}
case *envdpb.ProcessEvent_DataEvent_Stderr:
ev = ExecStreamEvent{Type: "stderr", Data: o.Stderr}
default:
continue
}
case *envdpb.ProcessEvent_End:
ev = ExecStreamEvent{Type: "end", ExitCode: e.End.GetExitCode()}
if e.End.Error != nil {
ev.Error = e.End.GetError()
}
case *envdpb.ProcessEvent_Keepalive:
continue
}
select {
case ch <- ev:
case <-ctx.Done():
return
}
}
if err := stream.Err(); err != nil && err != io.EOF {
slog.Debug("connect process stream error", "error", err)
}
}()
return ch, nil
}
// ListProcesses returns all running processes inside the sandbox.
func (c *Client) ListProcesses(ctx context.Context) ([]ProcessInfo, error) {
resp, err := c.process.List(ctx, connect.NewRequest(&envdpb.ListRequest{}))
if err != nil {
return nil, fmt.Errorf("list processes: %w", err)
}
procs := make([]ProcessInfo, 0, len(resp.Msg.Processes))
for _, p := range resp.Msg.Processes {
info := ProcessInfo{
PID: p.Pid,
}
if p.Tag != nil {
info.Tag = *p.Tag
}
if p.Config != nil {
info.Cmd = p.Config.Cmd
info.Args = p.Config.Args
}
procs = append(procs, info)
}
return procs, nil
}
// KillProcess sends a signal to a process identified by PID or tag.
func (c *Client) KillProcess(ctx context.Context, pid uint32, tag string, signal envdpb.Signal) error {
var selector *envdpb.ProcessSelector
if tag != "" {
selector = &envdpb.ProcessSelector{
Selector: &envdpb.ProcessSelector_Tag{Tag: tag},
}
} else {
selector = &envdpb.ProcessSelector{
Selector: &envdpb.ProcessSelector_Pid{Pid: pid},
}
}
_, err := c.process.SendSignal(ctx, connect.NewRequest(&envdpb.SendSignalRequest{
Process: selector,
Signal: signal,
}))
if err != nil {
return fmt.Errorf("kill process: %w", err)
}
return nil
}

View File

@ -752,3 +752,152 @@ func entryInfoToPB(e *envdpb.EntryInfo) *pb.FileEntry {
return entry return entry
} }
// ── Background Processes ────────────────────────────────────────────
func (s *Server) StartBackground(
ctx context.Context,
req *connect.Request[pb.StartBackgroundRequest],
) (*connect.Response[pb.StartBackgroundResponse], error) {
msg := req.Msg
pid, err := s.mgr.StartBackground(ctx, msg.SandboxId, msg.Tag, msg.Cmd, msg.Args, msg.Envs, msg.Cwd)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil, connect.NewError(connect.CodeNotFound, err)
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("start background: %w", err))
}
return connect.NewResponse(&pb.StartBackgroundResponse{
Pid: pid,
Tag: msg.Tag,
}), nil
}
func (s *Server) ListProcesses(
ctx context.Context,
req *connect.Request[pb.ListProcessesRequest],
) (*connect.Response[pb.ListProcessesResponse], error) {
procs, err := s.mgr.ListProcesses(ctx, req.Msg.SandboxId)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil, connect.NewError(connect.CodeNotFound, err)
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("list processes: %w", err))
}
entries := make([]*pb.ProcessEntry, 0, len(procs))
for _, p := range procs {
entries = append(entries, &pb.ProcessEntry{
Pid: p.PID,
Tag: p.Tag,
Cmd: p.Cmd,
Args: p.Args,
})
}
return connect.NewResponse(&pb.ListProcessesResponse{
Processes: entries,
}), nil
}
func (s *Server) KillProcess(
ctx context.Context,
req *connect.Request[pb.KillProcessRequest],
) (*connect.Response[pb.KillProcessResponse], error) {
msg := req.Msg
// Resolve PID/tag selector.
var pid uint32
var tag string
switch sel := msg.Selector.(type) {
case *pb.KillProcessRequest_Pid:
pid = sel.Pid
case *pb.KillProcessRequest_Tag:
tag = sel.Tag
default:
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("pid or tag is required"))
}
// Map signal string to envd enum.
var signal envdpb.Signal
switch msg.Signal {
case "", "SIGKILL":
signal = envdpb.Signal_SIGNAL_SIGKILL
case "SIGTERM":
signal = envdpb.Signal_SIGNAL_SIGTERM
default:
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("unsupported signal: %s (use SIGKILL or SIGTERM)", msg.Signal))
}
if err := s.mgr.KillProcess(ctx, msg.SandboxId, pid, tag, signal); err != nil {
if strings.Contains(err.Error(), "not found") {
return nil, connect.NewError(connect.CodeNotFound, err)
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("kill process: %w", err))
}
return connect.NewResponse(&pb.KillProcessResponse{}), nil
}
func (s *Server) ConnectProcess(
ctx context.Context,
req *connect.Request[pb.ConnectProcessRequest],
stream *connect.ServerStream[pb.ConnectProcessResponse],
) error {
msg := req.Msg
var pid uint32
var tag string
switch sel := msg.Selector.(type) {
case *pb.ConnectProcessRequest_Pid:
pid = sel.Pid
case *pb.ConnectProcessRequest_Tag:
tag = sel.Tag
default:
return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("pid or tag is required"))
}
events, err := s.mgr.ConnectProcess(ctx, msg.SandboxId, pid, tag)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return connect.NewError(connect.CodeNotFound, err)
}
return connect.NewError(connect.CodeInternal, fmt.Errorf("connect process: %w", err))
}
for ev := range events {
var resp pb.ConnectProcessResponse
switch ev.Type {
case "start":
resp.Event = &pb.ConnectProcessResponse_Start{
Start: &pb.ExecStreamStart{Pid: ev.PID},
}
case "stdout":
resp.Event = &pb.ConnectProcessResponse_Data{
Data: &pb.ExecStreamData{
Output: &pb.ExecStreamData_Stdout{Stdout: ev.Data},
},
}
case "stderr":
resp.Event = &pb.ConnectProcessResponse_Data{
Data: &pb.ExecStreamData{
Output: &pb.ExecStreamData_Stderr{Stderr: ev.Data},
},
}
case "end":
resp.Event = &pb.ConnectProcessResponse_End{
End: &pb.ExecStreamEnd{
ExitCode: ev.ExitCode,
Error: ev.Error,
},
}
}
if err := stream.Send(&resp); err != nil {
return err
}
}
return nil
}

View File

@ -24,12 +24,13 @@ import (
"git.omukk.dev/wrenn/wrenn/internal/snapshot" "git.omukk.dev/wrenn/wrenn/internal/snapshot"
"git.omukk.dev/wrenn/wrenn/internal/uffd" "git.omukk.dev/wrenn/wrenn/internal/uffd"
"git.omukk.dev/wrenn/wrenn/internal/vm" "git.omukk.dev/wrenn/wrenn/internal/vm"
envdpb "git.omukk.dev/wrenn/wrenn/proto/envd/gen"
) )
// Config holds the paths and defaults for the sandbox manager. // Config holds the paths and defaults for the sandbox manager.
type Config struct { type Config struct {
WrennDir string // root directory (e.g. /var/lib/wrenn); all sub-paths derived via layout package WrennDir string // root directory (e.g. /var/lib/wrenn); all sub-paths derived via layout package
EnvdTimeout time.Duration EnvdTimeout time.Duration
DefaultRootfsSizeMB int // target size for template rootfs images; 0 → DefaultDiskSizeMB DefaultRootfsSizeMB int // target size for template rootfs images; 0 → DefaultDiskSizeMB
} }
@ -1328,6 +1329,74 @@ func (m *Manager) PtyKill(ctx context.Context, sandboxID, tag string) error {
return sb.client.PtyKill(ctx, tag) return sb.client.PtyKill(ctx, tag)
} }
// StartBackground starts a background process inside a sandbox.
func (m *Manager) StartBackground(ctx context.Context, sandboxID, tag, cmd string, args []string, envs map[string]string, cwd string) (uint32, error) {
sb, err := m.get(sandboxID)
if err != nil {
return 0, err
}
if sb.Status != models.StatusRunning {
return 0, fmt.Errorf("sandbox %s is not running (status: %s)", sandboxID, sb.Status)
}
m.mu.Lock()
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.StartBackground(ctx, tag, cmd, args, envs, cwd)
}
// ConnectProcess re-attaches to a running process inside a sandbox.
func (m *Manager) ConnectProcess(ctx context.Context, sandboxID string, pid uint32, tag string) (<-chan envdclient.ExecStreamEvent, error) {
sb, err := m.get(sandboxID)
if err != nil {
return nil, err
}
if sb.Status != models.StatusRunning {
return nil, fmt.Errorf("sandbox %s is not running (status: %s)", sandboxID, sb.Status)
}
m.mu.Lock()
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.ConnectProcess(ctx, pid, tag)
}
// ListProcesses returns all running processes inside a sandbox.
func (m *Manager) ListProcesses(ctx context.Context, sandboxID string) ([]envdclient.ProcessInfo, error) {
sb, err := m.get(sandboxID)
if err != nil {
return nil, err
}
if sb.Status != models.StatusRunning {
return nil, fmt.Errorf("sandbox %s is not running (status: %s)", sandboxID, sb.Status)
}
m.mu.Lock()
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.ListProcesses(ctx)
}
// KillProcess sends a signal to a process inside a sandbox.
func (m *Manager) KillProcess(ctx context.Context, sandboxID string, pid uint32, tag string, signal envdpb.Signal) error {
sb, err := m.get(sandboxID)
if err != nil {
return err
}
if sb.Status != models.StatusRunning {
return fmt.Errorf("sandbox %s is not running (status: %s)", sandboxID, sb.Status)
}
m.mu.Lock()
sb.LastActiveAt = time.Now()
m.mu.Unlock()
return sb.client.KillProcess(ctx, pid, tag, signal)
}
// AcquireProxyConn atomically looks up a sandbox by ID and registers an // AcquireProxyConn atomically looks up a sandbox by ID and registers an
// in-flight proxy connection. Returns the sandbox's host-reachable IP, the // in-flight proxy connection. Returns the sandbox's host-reachable IP, the
// connection tracker, and true on success. The caller must call // connection tracker, and true on success. The caller must call

View File

@ -3461,6 +3461,623 @@ func (*PtyKillResponse) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{59} return file_hostagent_proto_rawDescGZIP(), []int{59}
} }
type StartBackgroundRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"`
Cmd string `protobuf:"bytes,2,opt,name=cmd,proto3" json:"cmd,omitempty"`
Args []string `protobuf:"bytes,3,rep,name=args,proto3" json:"args,omitempty"`
// User-chosen tag for the process. If empty, the host agent generates one.
Tag string `protobuf:"bytes,4,opt,name=tag,proto3" json:"tag,omitempty"`
Envs map[string]string `protobuf:"bytes,5,rep,name=envs,proto3" json:"envs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Cwd string `protobuf:"bytes,6,opt,name=cwd,proto3" json:"cwd,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StartBackgroundRequest) Reset() {
*x = StartBackgroundRequest{}
mi := &file_hostagent_proto_msgTypes[60]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StartBackgroundRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StartBackgroundRequest) ProtoMessage() {}
func (x *StartBackgroundRequest) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[60]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StartBackgroundRequest.ProtoReflect.Descriptor instead.
func (*StartBackgroundRequest) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{60}
}
func (x *StartBackgroundRequest) GetSandboxId() string {
if x != nil {
return x.SandboxId
}
return ""
}
func (x *StartBackgroundRequest) GetCmd() string {
if x != nil {
return x.Cmd
}
return ""
}
func (x *StartBackgroundRequest) GetArgs() []string {
if x != nil {
return x.Args
}
return nil
}
func (x *StartBackgroundRequest) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *StartBackgroundRequest) GetEnvs() map[string]string {
if x != nil {
return x.Envs
}
return nil
}
func (x *StartBackgroundRequest) GetCwd() string {
if x != nil {
return x.Cwd
}
return ""
}
type StartBackgroundResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"`
Tag string `protobuf:"bytes,2,opt,name=tag,proto3" json:"tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StartBackgroundResponse) Reset() {
*x = StartBackgroundResponse{}
mi := &file_hostagent_proto_msgTypes[61]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StartBackgroundResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StartBackgroundResponse) ProtoMessage() {}
func (x *StartBackgroundResponse) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[61]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StartBackgroundResponse.ProtoReflect.Descriptor instead.
func (*StartBackgroundResponse) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{61}
}
func (x *StartBackgroundResponse) GetPid() uint32 {
if x != nil {
return x.Pid
}
return 0
}
func (x *StartBackgroundResponse) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
type ListProcessesRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListProcessesRequest) Reset() {
*x = ListProcessesRequest{}
mi := &file_hostagent_proto_msgTypes[62]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListProcessesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListProcessesRequest) ProtoMessage() {}
func (x *ListProcessesRequest) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[62]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListProcessesRequest.ProtoReflect.Descriptor instead.
func (*ListProcessesRequest) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{62}
}
func (x *ListProcessesRequest) GetSandboxId() string {
if x != nil {
return x.SandboxId
}
return ""
}
type ProcessEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
Pid uint32 `protobuf:"varint,1,opt,name=pid,proto3" json:"pid,omitempty"`
Tag string `protobuf:"bytes,2,opt,name=tag,proto3" json:"tag,omitempty"`
Cmd string `protobuf:"bytes,3,opt,name=cmd,proto3" json:"cmd,omitempty"`
Args []string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProcessEntry) Reset() {
*x = ProcessEntry{}
mi := &file_hostagent_proto_msgTypes[63]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ProcessEntry) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ProcessEntry) ProtoMessage() {}
func (x *ProcessEntry) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[63]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ProcessEntry.ProtoReflect.Descriptor instead.
func (*ProcessEntry) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{63}
}
func (x *ProcessEntry) GetPid() uint32 {
if x != nil {
return x.Pid
}
return 0
}
func (x *ProcessEntry) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *ProcessEntry) GetCmd() string {
if x != nil {
return x.Cmd
}
return ""
}
func (x *ProcessEntry) GetArgs() []string {
if x != nil {
return x.Args
}
return nil
}
type ListProcessesResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Processes []*ProcessEntry `protobuf:"bytes,1,rep,name=processes,proto3" json:"processes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListProcessesResponse) Reset() {
*x = ListProcessesResponse{}
mi := &file_hostagent_proto_msgTypes[64]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListProcessesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListProcessesResponse) ProtoMessage() {}
func (x *ListProcessesResponse) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[64]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListProcessesResponse.ProtoReflect.Descriptor instead.
func (*ListProcessesResponse) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{64}
}
func (x *ListProcessesResponse) GetProcesses() []*ProcessEntry {
if x != nil {
return x.Processes
}
return nil
}
type KillProcessRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"`
// Types that are valid to be assigned to Selector:
//
// *KillProcessRequest_Pid
// *KillProcessRequest_Tag
Selector isKillProcessRequest_Selector `protobuf_oneof:"selector"`
// Signal to send: "SIGTERM" or "SIGKILL" (default: "SIGKILL").
Signal string `protobuf:"bytes,4,opt,name=signal,proto3" json:"signal,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *KillProcessRequest) Reset() {
*x = KillProcessRequest{}
mi := &file_hostagent_proto_msgTypes[65]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KillProcessRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KillProcessRequest) ProtoMessage() {}
func (x *KillProcessRequest) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[65]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KillProcessRequest.ProtoReflect.Descriptor instead.
func (*KillProcessRequest) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{65}
}
func (x *KillProcessRequest) GetSandboxId() string {
if x != nil {
return x.SandboxId
}
return ""
}
func (x *KillProcessRequest) GetSelector() isKillProcessRequest_Selector {
if x != nil {
return x.Selector
}
return nil
}
func (x *KillProcessRequest) GetPid() uint32 {
if x != nil {
if x, ok := x.Selector.(*KillProcessRequest_Pid); ok {
return x.Pid
}
}
return 0
}
func (x *KillProcessRequest) GetTag() string {
if x != nil {
if x, ok := x.Selector.(*KillProcessRequest_Tag); ok {
return x.Tag
}
}
return ""
}
func (x *KillProcessRequest) GetSignal() string {
if x != nil {
return x.Signal
}
return ""
}
type isKillProcessRequest_Selector interface {
isKillProcessRequest_Selector()
}
type KillProcessRequest_Pid struct {
Pid uint32 `protobuf:"varint,2,opt,name=pid,proto3,oneof"`
}
type KillProcessRequest_Tag struct {
Tag string `protobuf:"bytes,3,opt,name=tag,proto3,oneof"`
}
func (*KillProcessRequest_Pid) isKillProcessRequest_Selector() {}
func (*KillProcessRequest_Tag) isKillProcessRequest_Selector() {}
type KillProcessResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *KillProcessResponse) Reset() {
*x = KillProcessResponse{}
mi := &file_hostagent_proto_msgTypes[66]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KillProcessResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KillProcessResponse) ProtoMessage() {}
func (x *KillProcessResponse) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[66]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KillProcessResponse.ProtoReflect.Descriptor instead.
func (*KillProcessResponse) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{66}
}
type ConnectProcessRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"`
// Types that are valid to be assigned to Selector:
//
// *ConnectProcessRequest_Pid
// *ConnectProcessRequest_Tag
Selector isConnectProcessRequest_Selector `protobuf_oneof:"selector"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ConnectProcessRequest) Reset() {
*x = ConnectProcessRequest{}
mi := &file_hostagent_proto_msgTypes[67]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConnectProcessRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectProcessRequest) ProtoMessage() {}
func (x *ConnectProcessRequest) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[67]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnectProcessRequest.ProtoReflect.Descriptor instead.
func (*ConnectProcessRequest) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{67}
}
func (x *ConnectProcessRequest) GetSandboxId() string {
if x != nil {
return x.SandboxId
}
return ""
}
func (x *ConnectProcessRequest) GetSelector() isConnectProcessRequest_Selector {
if x != nil {
return x.Selector
}
return nil
}
func (x *ConnectProcessRequest) GetPid() uint32 {
if x != nil {
if x, ok := x.Selector.(*ConnectProcessRequest_Pid); ok {
return x.Pid
}
}
return 0
}
func (x *ConnectProcessRequest) GetTag() string {
if x != nil {
if x, ok := x.Selector.(*ConnectProcessRequest_Tag); ok {
return x.Tag
}
}
return ""
}
type isConnectProcessRequest_Selector interface {
isConnectProcessRequest_Selector()
}
type ConnectProcessRequest_Pid struct {
Pid uint32 `protobuf:"varint,2,opt,name=pid,proto3,oneof"`
}
type ConnectProcessRequest_Tag struct {
Tag string `protobuf:"bytes,3,opt,name=tag,proto3,oneof"`
}
func (*ConnectProcessRequest_Pid) isConnectProcessRequest_Selector() {}
func (*ConnectProcessRequest_Tag) isConnectProcessRequest_Selector() {}
// Reuses ExecStream event types for symmetry.
type ConnectProcessResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Event:
//
// *ConnectProcessResponse_Start
// *ConnectProcessResponse_Data
// *ConnectProcessResponse_End
Event isConnectProcessResponse_Event `protobuf_oneof:"event"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ConnectProcessResponse) Reset() {
*x = ConnectProcessResponse{}
mi := &file_hostagent_proto_msgTypes[68]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConnectProcessResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectProcessResponse) ProtoMessage() {}
func (x *ConnectProcessResponse) ProtoReflect() protoreflect.Message {
mi := &file_hostagent_proto_msgTypes[68]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ConnectProcessResponse.ProtoReflect.Descriptor instead.
func (*ConnectProcessResponse) Descriptor() ([]byte, []int) {
return file_hostagent_proto_rawDescGZIP(), []int{68}
}
func (x *ConnectProcessResponse) GetEvent() isConnectProcessResponse_Event {
if x != nil {
return x.Event
}
return nil
}
func (x *ConnectProcessResponse) GetStart() *ExecStreamStart {
if x != nil {
if x, ok := x.Event.(*ConnectProcessResponse_Start); ok {
return x.Start
}
}
return nil
}
func (x *ConnectProcessResponse) GetData() *ExecStreamData {
if x != nil {
if x, ok := x.Event.(*ConnectProcessResponse_Data); ok {
return x.Data
}
}
return nil
}
func (x *ConnectProcessResponse) GetEnd() *ExecStreamEnd {
if x != nil {
if x, ok := x.Event.(*ConnectProcessResponse_End); ok {
return x.End
}
}
return nil
}
type isConnectProcessResponse_Event interface {
isConnectProcessResponse_Event()
}
type ConnectProcessResponse_Start struct {
Start *ExecStreamStart `protobuf:"bytes,1,opt,name=start,proto3,oneof"`
}
type ConnectProcessResponse_Data struct {
Data *ExecStreamData `protobuf:"bytes,2,opt,name=data,proto3,oneof"`
}
type ConnectProcessResponse_End struct {
End *ExecStreamEnd `protobuf:"bytes,3,opt,name=end,proto3,oneof"`
}
func (*ConnectProcessResponse_Start) isConnectProcessResponse_Event() {}
func (*ConnectProcessResponse_Data) isConnectProcessResponse_Event() {}
func (*ConnectProcessResponse_End) isConnectProcessResponse_Event() {}
var File_hostagent_proto protoreflect.FileDescriptor var File_hostagent_proto protoreflect.FileDescriptor
const file_hostagent_proto_rawDesc = "" + const file_hostagent_proto_rawDesc = "" +
@ -3725,7 +4342,52 @@ const file_hostagent_proto_rawDesc = "" +
"\n" + "\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x10\n" + "sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x10\n" +
"\x03tag\x18\x02 \x01(\tR\x03tag\"\x11\n" + "\x03tag\x18\x02 \x01(\tR\x03tag\"\x11\n" +
"\x0fPtyKillResponse2\xe6\x10\n" + "\x0fPtyKillResponse\"\xfe\x01\n" +
"\x16StartBackgroundRequest\x12\x1d\n" +
"\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x10\n" +
"\x03cmd\x18\x02 \x01(\tR\x03cmd\x12\x12\n" +
"\x04args\x18\x03 \x03(\tR\x04args\x12\x10\n" +
"\x03tag\x18\x04 \x01(\tR\x03tag\x12B\n" +
"\x04envs\x18\x05 \x03(\v2..hostagent.v1.StartBackgroundRequest.EnvsEntryR\x04envs\x12\x10\n" +
"\x03cwd\x18\x06 \x01(\tR\x03cwd\x1a7\n" +
"\tEnvsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"=\n" +
"\x17StartBackgroundResponse\x12\x10\n" +
"\x03pid\x18\x01 \x01(\rR\x03pid\x12\x10\n" +
"\x03tag\x18\x02 \x01(\tR\x03tag\"5\n" +
"\x14ListProcessesRequest\x12\x1d\n" +
"\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\"X\n" +
"\fProcessEntry\x12\x10\n" +
"\x03pid\x18\x01 \x01(\rR\x03pid\x12\x10\n" +
"\x03tag\x18\x02 \x01(\tR\x03tag\x12\x10\n" +
"\x03cmd\x18\x03 \x01(\tR\x03cmd\x12\x12\n" +
"\x04args\x18\x04 \x03(\tR\x04args\"Q\n" +
"\x15ListProcessesResponse\x128\n" +
"\tprocesses\x18\x01 \x03(\v2\x1a.hostagent.v1.ProcessEntryR\tprocesses\"\x7f\n" +
"\x12KillProcessRequest\x12\x1d\n" +
"\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x12\n" +
"\x03pid\x18\x02 \x01(\rH\x00R\x03pid\x12\x12\n" +
"\x03tag\x18\x03 \x01(\tH\x00R\x03tag\x12\x16\n" +
"\x06signal\x18\x04 \x01(\tR\x06signalB\n" +
"\n" +
"\bselector\"\x15\n" +
"\x13KillProcessResponse\"j\n" +
"\x15ConnectProcessRequest\x12\x1d\n" +
"\n" +
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\x12\x12\n" +
"\x03pid\x18\x02 \x01(\rH\x00R\x03pid\x12\x12\n" +
"\x03tag\x18\x03 \x01(\tH\x00R\x03tagB\n" +
"\n" +
"\bselector\"\xbd\x01\n" +
"\x16ConnectProcessResponse\x125\n" +
"\x05start\x18\x01 \x01(\v2\x1d.hostagent.v1.ExecStreamStartH\x00R\x05start\x122\n" +
"\x04data\x18\x02 \x01(\v2\x1c.hostagent.v1.ExecStreamDataH\x00R\x04data\x12/\n" +
"\x03end\x18\x03 \x01(\v2\x1b.hostagent.v1.ExecStreamEndH\x00R\x03endB\a\n" +
"\x05event2\xd3\x13\n" +
"\x10HostAgentService\x12X\n" + "\x10HostAgentService\x12X\n" +
"\rCreateSandbox\x12\".hostagent.v1.CreateSandboxRequest\x1a#.hostagent.v1.CreateSandboxResponse\x12[\n" + "\rCreateSandbox\x12\".hostagent.v1.CreateSandboxRequest\x1a#.hostagent.v1.CreateSandboxResponse\x12[\n" +
"\x0eDestroySandbox\x12#.hostagent.v1.DestroySandboxRequest\x1a$.hostagent.v1.DestroySandboxResponse\x12U\n" + "\x0eDestroySandbox\x12#.hostagent.v1.DestroySandboxRequest\x1a$.hostagent.v1.DestroySandboxResponse\x12U\n" +
@ -3753,7 +4415,11 @@ const file_hostagent_proto_rawDesc = "" +
"\tPtyAttach\x12\x1e.hostagent.v1.PtyAttachRequest\x1a\x1f.hostagent.v1.PtyAttachResponse0\x01\x12U\n" + "\tPtyAttach\x12\x1e.hostagent.v1.PtyAttachRequest\x1a\x1f.hostagent.v1.PtyAttachResponse0\x01\x12U\n" +
"\fPtySendInput\x12!.hostagent.v1.PtySendInputRequest\x1a\".hostagent.v1.PtySendInputResponse\x12L\n" + "\fPtySendInput\x12!.hostagent.v1.PtySendInputRequest\x1a\".hostagent.v1.PtySendInputResponse\x12L\n" +
"\tPtyResize\x12\x1e.hostagent.v1.PtyResizeRequest\x1a\x1f.hostagent.v1.PtyResizeResponse\x12F\n" + "\tPtyResize\x12\x1e.hostagent.v1.PtyResizeRequest\x1a\x1f.hostagent.v1.PtyResizeResponse\x12F\n" +
"\aPtyKill\x12\x1c.hostagent.v1.PtyKillRequest\x1a\x1d.hostagent.v1.PtyKillResponseB\xae\x01\n" + "\aPtyKill\x12\x1c.hostagent.v1.PtyKillRequest\x1a\x1d.hostagent.v1.PtyKillResponse\x12^\n" +
"\x0fStartBackground\x12$.hostagent.v1.StartBackgroundRequest\x1a%.hostagent.v1.StartBackgroundResponse\x12X\n" +
"\rListProcesses\x12\".hostagent.v1.ListProcessesRequest\x1a#.hostagent.v1.ListProcessesResponse\x12R\n" +
"\vKillProcess\x12 .hostagent.v1.KillProcessRequest\x1a!.hostagent.v1.KillProcessResponse\x12]\n" +
"\x0eConnectProcess\x12#.hostagent.v1.ConnectProcessRequest\x1a$.hostagent.v1.ConnectProcessResponse0\x01B\xae\x01\n" +
"\x10com.hostagent.v1B\x0eHostagentProtoP\x01Z9git.omukk.dev/wrenn/wrenn/proto/hostagent/gen;hostagentv1\xa2\x02\x03HXX\xaa\x02\fHostagent.V1\xca\x02\fHostagent\\V1\xe2\x02\x18Hostagent\\V1\\GPBMetadata\xea\x02\rHostagent::V1b\x06proto3" "\x10com.hostagent.v1B\x0eHostagentProtoP\x01Z9git.omukk.dev/wrenn/wrenn/proto/hostagent/gen;hostagentv1\xa2\x02\x03HXX\xaa\x02\fHostagent.V1\xca\x02\fHostagent\\V1\xe2\x02\x18Hostagent\\V1\\GPBMetadata\xea\x02\rHostagent::V1b\x06proto3"
var ( var (
@ -3768,7 +4434,7 @@ func file_hostagent_proto_rawDescGZIP() []byte {
return file_hostagent_proto_rawDescData return file_hostagent_proto_rawDescData
} }
var file_hostagent_proto_msgTypes = make([]protoimpl.MessageInfo, 63) var file_hostagent_proto_msgTypes = make([]protoimpl.MessageInfo, 73)
var file_hostagent_proto_goTypes = []any{ var file_hostagent_proto_goTypes = []any{
(*CreateSandboxRequest)(nil), // 0: hostagent.v1.CreateSandboxRequest (*CreateSandboxRequest)(nil), // 0: hostagent.v1.CreateSandboxRequest
(*CreateSandboxResponse)(nil), // 1: hostagent.v1.CreateSandboxResponse (*CreateSandboxResponse)(nil), // 1: hostagent.v1.CreateSandboxResponse
@ -3830,13 +4496,23 @@ var file_hostagent_proto_goTypes = []any{
(*PtyResizeResponse)(nil), // 57: hostagent.v1.PtyResizeResponse (*PtyResizeResponse)(nil), // 57: hostagent.v1.PtyResizeResponse
(*PtyKillRequest)(nil), // 58: hostagent.v1.PtyKillRequest (*PtyKillRequest)(nil), // 58: hostagent.v1.PtyKillRequest
(*PtyKillResponse)(nil), // 59: hostagent.v1.PtyKillResponse (*PtyKillResponse)(nil), // 59: hostagent.v1.PtyKillResponse
nil, // 60: hostagent.v1.CreateSandboxRequest.DefaultEnvEntry (*StartBackgroundRequest)(nil), // 60: hostagent.v1.StartBackgroundRequest
nil, // 61: hostagent.v1.ResumeSandboxRequest.DefaultEnvEntry (*StartBackgroundResponse)(nil), // 61: hostagent.v1.StartBackgroundResponse
nil, // 62: hostagent.v1.PtyAttachRequest.EnvsEntry (*ListProcessesRequest)(nil), // 62: hostagent.v1.ListProcessesRequest
(*ProcessEntry)(nil), // 63: hostagent.v1.ProcessEntry
(*ListProcessesResponse)(nil), // 64: hostagent.v1.ListProcessesResponse
(*KillProcessRequest)(nil), // 65: hostagent.v1.KillProcessRequest
(*KillProcessResponse)(nil), // 66: hostagent.v1.KillProcessResponse
(*ConnectProcessRequest)(nil), // 67: hostagent.v1.ConnectProcessRequest
(*ConnectProcessResponse)(nil), // 68: hostagent.v1.ConnectProcessResponse
nil, // 69: hostagent.v1.CreateSandboxRequest.DefaultEnvEntry
nil, // 70: hostagent.v1.ResumeSandboxRequest.DefaultEnvEntry
nil, // 71: hostagent.v1.PtyAttachRequest.EnvsEntry
nil, // 72: hostagent.v1.StartBackgroundRequest.EnvsEntry
} }
var file_hostagent_proto_depIdxs = []int32{ var file_hostagent_proto_depIdxs = []int32{
60, // 0: hostagent.v1.CreateSandboxRequest.default_env:type_name -> hostagent.v1.CreateSandboxRequest.DefaultEnvEntry 69, // 0: hostagent.v1.CreateSandboxRequest.default_env:type_name -> hostagent.v1.CreateSandboxRequest.DefaultEnvEntry
61, // 1: hostagent.v1.ResumeSandboxRequest.default_env:type_name -> hostagent.v1.ResumeSandboxRequest.DefaultEnvEntry 70, // 1: hostagent.v1.ResumeSandboxRequest.default_env:type_name -> hostagent.v1.ResumeSandboxRequest.DefaultEnvEntry
16, // 2: hostagent.v1.ListSandboxesResponse.sandboxes:type_name -> hostagent.v1.SandboxInfo 16, // 2: hostagent.v1.ListSandboxesResponse.sandboxes:type_name -> hostagent.v1.SandboxInfo
23, // 3: hostagent.v1.ExecStreamResponse.start:type_name -> hostagent.v1.ExecStreamStart 23, // 3: hostagent.v1.ExecStreamResponse.start:type_name -> hostagent.v1.ExecStreamStart
24, // 4: hostagent.v1.ExecStreamResponse.data:type_name -> hostagent.v1.ExecStreamData 24, // 4: hostagent.v1.ExecStreamResponse.data:type_name -> hostagent.v1.ExecStreamData
@ -3848,65 +4524,78 @@ var file_hostagent_proto_depIdxs = []int32{
42, // 10: hostagent.v1.FlushSandboxMetricsResponse.points_10m:type_name -> hostagent.v1.MetricPoint 42, // 10: hostagent.v1.FlushSandboxMetricsResponse.points_10m:type_name -> hostagent.v1.MetricPoint
42, // 11: hostagent.v1.FlushSandboxMetricsResponse.points_2h:type_name -> hostagent.v1.MetricPoint 42, // 11: hostagent.v1.FlushSandboxMetricsResponse.points_2h:type_name -> hostagent.v1.MetricPoint
42, // 12: hostagent.v1.FlushSandboxMetricsResponse.points_24h:type_name -> hostagent.v1.MetricPoint 42, // 12: hostagent.v1.FlushSandboxMetricsResponse.points_24h:type_name -> hostagent.v1.MetricPoint
62, // 13: hostagent.v1.PtyAttachRequest.envs:type_name -> hostagent.v1.PtyAttachRequest.EnvsEntry 71, // 13: hostagent.v1.PtyAttachRequest.envs:type_name -> hostagent.v1.PtyAttachRequest.EnvsEntry
51, // 14: hostagent.v1.PtyAttachResponse.started:type_name -> hostagent.v1.PtyStarted 51, // 14: hostagent.v1.PtyAttachResponse.started:type_name -> hostagent.v1.PtyStarted
52, // 15: hostagent.v1.PtyAttachResponse.output:type_name -> hostagent.v1.PtyOutput 52, // 15: hostagent.v1.PtyAttachResponse.output:type_name -> hostagent.v1.PtyOutput
53, // 16: hostagent.v1.PtyAttachResponse.exited:type_name -> hostagent.v1.PtyExited 53, // 16: hostagent.v1.PtyAttachResponse.exited:type_name -> hostagent.v1.PtyExited
0, // 17: hostagent.v1.HostAgentService.CreateSandbox:input_type -> hostagent.v1.CreateSandboxRequest 72, // 17: hostagent.v1.StartBackgroundRequest.envs:type_name -> hostagent.v1.StartBackgroundRequest.EnvsEntry
2, // 18: hostagent.v1.HostAgentService.DestroySandbox:input_type -> hostagent.v1.DestroySandboxRequest 63, // 18: hostagent.v1.ListProcessesResponse.processes:type_name -> hostagent.v1.ProcessEntry
4, // 19: hostagent.v1.HostAgentService.PauseSandbox:input_type -> hostagent.v1.PauseSandboxRequest 23, // 19: hostagent.v1.ConnectProcessResponse.start:type_name -> hostagent.v1.ExecStreamStart
6, // 20: hostagent.v1.HostAgentService.ResumeSandbox:input_type -> hostagent.v1.ResumeSandboxRequest 24, // 20: hostagent.v1.ConnectProcessResponse.data:type_name -> hostagent.v1.ExecStreamData
12, // 21: hostagent.v1.HostAgentService.Exec:input_type -> hostagent.v1.ExecRequest 25, // 21: hostagent.v1.ConnectProcessResponse.end:type_name -> hostagent.v1.ExecStreamEnd
14, // 22: hostagent.v1.HostAgentService.ListSandboxes:input_type -> hostagent.v1.ListSandboxesRequest 0, // 22: hostagent.v1.HostAgentService.CreateSandbox:input_type -> hostagent.v1.CreateSandboxRequest
17, // 23: hostagent.v1.HostAgentService.WriteFile:input_type -> hostagent.v1.WriteFileRequest 2, // 23: hostagent.v1.HostAgentService.DestroySandbox:input_type -> hostagent.v1.DestroySandboxRequest
19, // 24: hostagent.v1.HostAgentService.ReadFile:input_type -> hostagent.v1.ReadFileRequest 4, // 24: hostagent.v1.HostAgentService.PauseSandbox:input_type -> hostagent.v1.PauseSandboxRequest
31, // 25: hostagent.v1.HostAgentService.ListDir:input_type -> hostagent.v1.ListDirRequest 6, // 25: hostagent.v1.HostAgentService.ResumeSandbox:input_type -> hostagent.v1.ResumeSandboxRequest
34, // 26: hostagent.v1.HostAgentService.MakeDir:input_type -> hostagent.v1.MakeDirRequest 12, // 26: hostagent.v1.HostAgentService.Exec:input_type -> hostagent.v1.ExecRequest
36, // 27: hostagent.v1.HostAgentService.RemovePath:input_type -> hostagent.v1.RemovePathRequest 14, // 27: hostagent.v1.HostAgentService.ListSandboxes:input_type -> hostagent.v1.ListSandboxesRequest
8, // 28: hostagent.v1.HostAgentService.CreateSnapshot:input_type -> hostagent.v1.CreateSnapshotRequest 17, // 28: hostagent.v1.HostAgentService.WriteFile:input_type -> hostagent.v1.WriteFileRequest
10, // 29: hostagent.v1.HostAgentService.DeleteSnapshot:input_type -> hostagent.v1.DeleteSnapshotRequest 19, // 29: hostagent.v1.HostAgentService.ReadFile:input_type -> hostagent.v1.ReadFileRequest
21, // 30: hostagent.v1.HostAgentService.ExecStream:input_type -> hostagent.v1.ExecStreamRequest 31, // 30: hostagent.v1.HostAgentService.ListDir:input_type -> hostagent.v1.ListDirRequest
26, // 31: hostagent.v1.HostAgentService.WriteFileStream:input_type -> hostagent.v1.WriteFileStreamRequest 34, // 31: hostagent.v1.HostAgentService.MakeDir:input_type -> hostagent.v1.MakeDirRequest
29, // 32: hostagent.v1.HostAgentService.ReadFileStream:input_type -> hostagent.v1.ReadFileStreamRequest 36, // 32: hostagent.v1.HostAgentService.RemovePath:input_type -> hostagent.v1.RemovePathRequest
38, // 33: hostagent.v1.HostAgentService.PingSandbox:input_type -> hostagent.v1.PingSandboxRequest 8, // 33: hostagent.v1.HostAgentService.CreateSnapshot:input_type -> hostagent.v1.CreateSnapshotRequest
40, // 34: hostagent.v1.HostAgentService.Terminate:input_type -> hostagent.v1.TerminateRequest 10, // 34: hostagent.v1.HostAgentService.DeleteSnapshot:input_type -> hostagent.v1.DeleteSnapshotRequest
43, // 35: hostagent.v1.HostAgentService.GetSandboxMetrics:input_type -> hostagent.v1.GetSandboxMetricsRequest 21, // 35: hostagent.v1.HostAgentService.ExecStream:input_type -> hostagent.v1.ExecStreamRequest
45, // 36: hostagent.v1.HostAgentService.FlushSandboxMetrics:input_type -> hostagent.v1.FlushSandboxMetricsRequest 26, // 36: hostagent.v1.HostAgentService.WriteFileStream:input_type -> hostagent.v1.WriteFileStreamRequest
47, // 37: hostagent.v1.HostAgentService.FlattenRootfs:input_type -> hostagent.v1.FlattenRootfsRequest 29, // 37: hostagent.v1.HostAgentService.ReadFileStream:input_type -> hostagent.v1.ReadFileStreamRequest
49, // 38: hostagent.v1.HostAgentService.PtyAttach:input_type -> hostagent.v1.PtyAttachRequest 38, // 38: hostagent.v1.HostAgentService.PingSandbox:input_type -> hostagent.v1.PingSandboxRequest
54, // 39: hostagent.v1.HostAgentService.PtySendInput:input_type -> hostagent.v1.PtySendInputRequest 40, // 39: hostagent.v1.HostAgentService.Terminate:input_type -> hostagent.v1.TerminateRequest
56, // 40: hostagent.v1.HostAgentService.PtyResize:input_type -> hostagent.v1.PtyResizeRequest 43, // 40: hostagent.v1.HostAgentService.GetSandboxMetrics:input_type -> hostagent.v1.GetSandboxMetricsRequest
58, // 41: hostagent.v1.HostAgentService.PtyKill:input_type -> hostagent.v1.PtyKillRequest 45, // 41: hostagent.v1.HostAgentService.FlushSandboxMetrics:input_type -> hostagent.v1.FlushSandboxMetricsRequest
1, // 42: hostagent.v1.HostAgentService.CreateSandbox:output_type -> hostagent.v1.CreateSandboxResponse 47, // 42: hostagent.v1.HostAgentService.FlattenRootfs:input_type -> hostagent.v1.FlattenRootfsRequest
3, // 43: hostagent.v1.HostAgentService.DestroySandbox:output_type -> hostagent.v1.DestroySandboxResponse 49, // 43: hostagent.v1.HostAgentService.PtyAttach:input_type -> hostagent.v1.PtyAttachRequest
5, // 44: hostagent.v1.HostAgentService.PauseSandbox:output_type -> hostagent.v1.PauseSandboxResponse 54, // 44: hostagent.v1.HostAgentService.PtySendInput:input_type -> hostagent.v1.PtySendInputRequest
7, // 45: hostagent.v1.HostAgentService.ResumeSandbox:output_type -> hostagent.v1.ResumeSandboxResponse 56, // 45: hostagent.v1.HostAgentService.PtyResize:input_type -> hostagent.v1.PtyResizeRequest
13, // 46: hostagent.v1.HostAgentService.Exec:output_type -> hostagent.v1.ExecResponse 58, // 46: hostagent.v1.HostAgentService.PtyKill:input_type -> hostagent.v1.PtyKillRequest
15, // 47: hostagent.v1.HostAgentService.ListSandboxes:output_type -> hostagent.v1.ListSandboxesResponse 60, // 47: hostagent.v1.HostAgentService.StartBackground:input_type -> hostagent.v1.StartBackgroundRequest
18, // 48: hostagent.v1.HostAgentService.WriteFile:output_type -> hostagent.v1.WriteFileResponse 62, // 48: hostagent.v1.HostAgentService.ListProcesses:input_type -> hostagent.v1.ListProcessesRequest
20, // 49: hostagent.v1.HostAgentService.ReadFile:output_type -> hostagent.v1.ReadFileResponse 65, // 49: hostagent.v1.HostAgentService.KillProcess:input_type -> hostagent.v1.KillProcessRequest
32, // 50: hostagent.v1.HostAgentService.ListDir:output_type -> hostagent.v1.ListDirResponse 67, // 50: hostagent.v1.HostAgentService.ConnectProcess:input_type -> hostagent.v1.ConnectProcessRequest
35, // 51: hostagent.v1.HostAgentService.MakeDir:output_type -> hostagent.v1.MakeDirResponse 1, // 51: hostagent.v1.HostAgentService.CreateSandbox:output_type -> hostagent.v1.CreateSandboxResponse
37, // 52: hostagent.v1.HostAgentService.RemovePath:output_type -> hostagent.v1.RemovePathResponse 3, // 52: hostagent.v1.HostAgentService.DestroySandbox:output_type -> hostagent.v1.DestroySandboxResponse
9, // 53: hostagent.v1.HostAgentService.CreateSnapshot:output_type -> hostagent.v1.CreateSnapshotResponse 5, // 53: hostagent.v1.HostAgentService.PauseSandbox:output_type -> hostagent.v1.PauseSandboxResponse
11, // 54: hostagent.v1.HostAgentService.DeleteSnapshot:output_type -> hostagent.v1.DeleteSnapshotResponse 7, // 54: hostagent.v1.HostAgentService.ResumeSandbox:output_type -> hostagent.v1.ResumeSandboxResponse
22, // 55: hostagent.v1.HostAgentService.ExecStream:output_type -> hostagent.v1.ExecStreamResponse 13, // 55: hostagent.v1.HostAgentService.Exec:output_type -> hostagent.v1.ExecResponse
28, // 56: hostagent.v1.HostAgentService.WriteFileStream:output_type -> hostagent.v1.WriteFileStreamResponse 15, // 56: hostagent.v1.HostAgentService.ListSandboxes:output_type -> hostagent.v1.ListSandboxesResponse
30, // 57: hostagent.v1.HostAgentService.ReadFileStream:output_type -> hostagent.v1.ReadFileStreamResponse 18, // 57: hostagent.v1.HostAgentService.WriteFile:output_type -> hostagent.v1.WriteFileResponse
39, // 58: hostagent.v1.HostAgentService.PingSandbox:output_type -> hostagent.v1.PingSandboxResponse 20, // 58: hostagent.v1.HostAgentService.ReadFile:output_type -> hostagent.v1.ReadFileResponse
41, // 59: hostagent.v1.HostAgentService.Terminate:output_type -> hostagent.v1.TerminateResponse 32, // 59: hostagent.v1.HostAgentService.ListDir:output_type -> hostagent.v1.ListDirResponse
44, // 60: hostagent.v1.HostAgentService.GetSandboxMetrics:output_type -> hostagent.v1.GetSandboxMetricsResponse 35, // 60: hostagent.v1.HostAgentService.MakeDir:output_type -> hostagent.v1.MakeDirResponse
46, // 61: hostagent.v1.HostAgentService.FlushSandboxMetrics:output_type -> hostagent.v1.FlushSandboxMetricsResponse 37, // 61: hostagent.v1.HostAgentService.RemovePath:output_type -> hostagent.v1.RemovePathResponse
48, // 62: hostagent.v1.HostAgentService.FlattenRootfs:output_type -> hostagent.v1.FlattenRootfsResponse 9, // 62: hostagent.v1.HostAgentService.CreateSnapshot:output_type -> hostagent.v1.CreateSnapshotResponse
50, // 63: hostagent.v1.HostAgentService.PtyAttach:output_type -> hostagent.v1.PtyAttachResponse 11, // 63: hostagent.v1.HostAgentService.DeleteSnapshot:output_type -> hostagent.v1.DeleteSnapshotResponse
55, // 64: hostagent.v1.HostAgentService.PtySendInput:output_type -> hostagent.v1.PtySendInputResponse 22, // 64: hostagent.v1.HostAgentService.ExecStream:output_type -> hostagent.v1.ExecStreamResponse
57, // 65: hostagent.v1.HostAgentService.PtyResize:output_type -> hostagent.v1.PtyResizeResponse 28, // 65: hostagent.v1.HostAgentService.WriteFileStream:output_type -> hostagent.v1.WriteFileStreamResponse
59, // 66: hostagent.v1.HostAgentService.PtyKill:output_type -> hostagent.v1.PtyKillResponse 30, // 66: hostagent.v1.HostAgentService.ReadFileStream:output_type -> hostagent.v1.ReadFileStreamResponse
42, // [42:67] is the sub-list for method output_type 39, // 67: hostagent.v1.HostAgentService.PingSandbox:output_type -> hostagent.v1.PingSandboxResponse
17, // [17:42] is the sub-list for method input_type 41, // 68: hostagent.v1.HostAgentService.Terminate:output_type -> hostagent.v1.TerminateResponse
17, // [17:17] is the sub-list for extension type_name 44, // 69: hostagent.v1.HostAgentService.GetSandboxMetrics:output_type -> hostagent.v1.GetSandboxMetricsResponse
17, // [17:17] is the sub-list for extension extendee 46, // 70: hostagent.v1.HostAgentService.FlushSandboxMetrics:output_type -> hostagent.v1.FlushSandboxMetricsResponse
0, // [0:17] is the sub-list for field type_name 48, // 71: hostagent.v1.HostAgentService.FlattenRootfs:output_type -> hostagent.v1.FlattenRootfsResponse
50, // 72: hostagent.v1.HostAgentService.PtyAttach:output_type -> hostagent.v1.PtyAttachResponse
55, // 73: hostagent.v1.HostAgentService.PtySendInput:output_type -> hostagent.v1.PtySendInputResponse
57, // 74: hostagent.v1.HostAgentService.PtyResize:output_type -> hostagent.v1.PtyResizeResponse
59, // 75: hostagent.v1.HostAgentService.PtyKill:output_type -> hostagent.v1.PtyKillResponse
61, // 76: hostagent.v1.HostAgentService.StartBackground:output_type -> hostagent.v1.StartBackgroundResponse
64, // 77: hostagent.v1.HostAgentService.ListProcesses:output_type -> hostagent.v1.ListProcessesResponse
66, // 78: hostagent.v1.HostAgentService.KillProcess:output_type -> hostagent.v1.KillProcessResponse
68, // 79: hostagent.v1.HostAgentService.ConnectProcess:output_type -> hostagent.v1.ConnectProcessResponse
51, // [51:80] is the sub-list for method output_type
22, // [22:51] is the sub-list for method input_type
22, // [22:22] is the sub-list for extension type_name
22, // [22:22] is the sub-list for extension extendee
0, // [0:22] is the sub-list for field type_name
} }
func init() { file_hostagent_proto_init() } func init() { file_hostagent_proto_init() }
@ -3933,13 +4622,26 @@ func file_hostagent_proto_init() {
(*PtyAttachResponse_Output)(nil), (*PtyAttachResponse_Output)(nil),
(*PtyAttachResponse_Exited)(nil), (*PtyAttachResponse_Exited)(nil),
} }
file_hostagent_proto_msgTypes[65].OneofWrappers = []any{
(*KillProcessRequest_Pid)(nil),
(*KillProcessRequest_Tag)(nil),
}
file_hostagent_proto_msgTypes[67].OneofWrappers = []any{
(*ConnectProcessRequest_Pid)(nil),
(*ConnectProcessRequest_Tag)(nil),
}
file_hostagent_proto_msgTypes[68].OneofWrappers = []any{
(*ConnectProcessResponse_Start)(nil),
(*ConnectProcessResponse_Data)(nil),
(*ConnectProcessResponse_End)(nil),
}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_hostagent_proto_rawDesc), len(file_hostagent_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_hostagent_proto_rawDesc), len(file_hostagent_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 63, NumMessages: 73,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -107,6 +107,18 @@ const (
// HostAgentServicePtyKillProcedure is the fully-qualified name of the HostAgentService's PtyKill // HostAgentServicePtyKillProcedure is the fully-qualified name of the HostAgentService's PtyKill
// RPC. // RPC.
HostAgentServicePtyKillProcedure = "/hostagent.v1.HostAgentService/PtyKill" HostAgentServicePtyKillProcedure = "/hostagent.v1.HostAgentService/PtyKill"
// HostAgentServiceStartBackgroundProcedure is the fully-qualified name of the HostAgentService's
// StartBackground RPC.
HostAgentServiceStartBackgroundProcedure = "/hostagent.v1.HostAgentService/StartBackground"
// HostAgentServiceListProcessesProcedure is the fully-qualified name of the HostAgentService's
// ListProcesses RPC.
HostAgentServiceListProcessesProcedure = "/hostagent.v1.HostAgentService/ListProcesses"
// HostAgentServiceKillProcessProcedure is the fully-qualified name of the HostAgentService's
// KillProcess RPC.
HostAgentServiceKillProcessProcedure = "/hostagent.v1.HostAgentService/KillProcess"
// HostAgentServiceConnectProcessProcedure is the fully-qualified name of the HostAgentService's
// ConnectProcess RPC.
HostAgentServiceConnectProcessProcedure = "/hostagent.v1.HostAgentService/ConnectProcess"
) )
// HostAgentServiceClient is a client for the hostagent.v1.HostAgentService service. // HostAgentServiceClient is a client for the hostagent.v1.HostAgentService service.
@ -172,6 +184,15 @@ type HostAgentServiceClient interface {
PtyResize(context.Context, *connect.Request[gen.PtyResizeRequest]) (*connect.Response[gen.PtyResizeResponse], error) PtyResize(context.Context, *connect.Request[gen.PtyResizeRequest]) (*connect.Response[gen.PtyResizeResponse], error)
// PtyKill sends a signal to a PTY process. // PtyKill sends a signal to a PTY process.
PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error) PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error)
// StartBackground starts a process in the background and returns immediately
// with the PID and tag. The process survives RPC disconnection.
StartBackground(context.Context, *connect.Request[gen.StartBackgroundRequest]) (*connect.Response[gen.StartBackgroundResponse], error)
// ListProcesses returns all running processes inside a sandbox.
ListProcesses(context.Context, *connect.Request[gen.ListProcessesRequest]) (*connect.Response[gen.ListProcessesResponse], error)
// KillProcess sends a signal to a process identified by PID or tag.
KillProcess(context.Context, *connect.Request[gen.KillProcessRequest]) (*connect.Response[gen.KillProcessResponse], error)
// ConnectProcess re-attaches to a running process and streams its output.
ConnectProcess(context.Context, *connect.Request[gen.ConnectProcessRequest]) (*connect.ServerStreamForClient[gen.ConnectProcessResponse], error)
} }
// NewHostAgentServiceClient constructs a client for the hostagent.v1.HostAgentService service. By // NewHostAgentServiceClient constructs a client for the hostagent.v1.HostAgentService service. By
@ -335,6 +356,30 @@ func NewHostAgentServiceClient(httpClient connect.HTTPClient, baseURL string, op
connect.WithSchema(hostAgentServiceMethods.ByName("PtyKill")), connect.WithSchema(hostAgentServiceMethods.ByName("PtyKill")),
connect.WithClientOptions(opts...), connect.WithClientOptions(opts...),
), ),
startBackground: connect.NewClient[gen.StartBackgroundRequest, gen.StartBackgroundResponse](
httpClient,
baseURL+HostAgentServiceStartBackgroundProcedure,
connect.WithSchema(hostAgentServiceMethods.ByName("StartBackground")),
connect.WithClientOptions(opts...),
),
listProcesses: connect.NewClient[gen.ListProcessesRequest, gen.ListProcessesResponse](
httpClient,
baseURL+HostAgentServiceListProcessesProcedure,
connect.WithSchema(hostAgentServiceMethods.ByName("ListProcesses")),
connect.WithClientOptions(opts...),
),
killProcess: connect.NewClient[gen.KillProcessRequest, gen.KillProcessResponse](
httpClient,
baseURL+HostAgentServiceKillProcessProcedure,
connect.WithSchema(hostAgentServiceMethods.ByName("KillProcess")),
connect.WithClientOptions(opts...),
),
connectProcess: connect.NewClient[gen.ConnectProcessRequest, gen.ConnectProcessResponse](
httpClient,
baseURL+HostAgentServiceConnectProcessProcedure,
connect.WithSchema(hostAgentServiceMethods.ByName("ConnectProcess")),
connect.WithClientOptions(opts...),
),
} }
} }
@ -365,6 +410,10 @@ type hostAgentServiceClient struct {
ptySendInput *connect.Client[gen.PtySendInputRequest, gen.PtySendInputResponse] ptySendInput *connect.Client[gen.PtySendInputRequest, gen.PtySendInputResponse]
ptyResize *connect.Client[gen.PtyResizeRequest, gen.PtyResizeResponse] ptyResize *connect.Client[gen.PtyResizeRequest, gen.PtyResizeResponse]
ptyKill *connect.Client[gen.PtyKillRequest, gen.PtyKillResponse] ptyKill *connect.Client[gen.PtyKillRequest, gen.PtyKillResponse]
startBackground *connect.Client[gen.StartBackgroundRequest, gen.StartBackgroundResponse]
listProcesses *connect.Client[gen.ListProcessesRequest, gen.ListProcessesResponse]
killProcess *connect.Client[gen.KillProcessRequest, gen.KillProcessResponse]
connectProcess *connect.Client[gen.ConnectProcessRequest, gen.ConnectProcessResponse]
} }
// CreateSandbox calls hostagent.v1.HostAgentService.CreateSandbox. // CreateSandbox calls hostagent.v1.HostAgentService.CreateSandbox.
@ -492,6 +541,26 @@ func (c *hostAgentServiceClient) PtyKill(ctx context.Context, req *connect.Reque
return c.ptyKill.CallUnary(ctx, req) return c.ptyKill.CallUnary(ctx, req)
} }
// StartBackground calls hostagent.v1.HostAgentService.StartBackground.
func (c *hostAgentServiceClient) StartBackground(ctx context.Context, req *connect.Request[gen.StartBackgroundRequest]) (*connect.Response[gen.StartBackgroundResponse], error) {
return c.startBackground.CallUnary(ctx, req)
}
// ListProcesses calls hostagent.v1.HostAgentService.ListProcesses.
func (c *hostAgentServiceClient) ListProcesses(ctx context.Context, req *connect.Request[gen.ListProcessesRequest]) (*connect.Response[gen.ListProcessesResponse], error) {
return c.listProcesses.CallUnary(ctx, req)
}
// KillProcess calls hostagent.v1.HostAgentService.KillProcess.
func (c *hostAgentServiceClient) KillProcess(ctx context.Context, req *connect.Request[gen.KillProcessRequest]) (*connect.Response[gen.KillProcessResponse], error) {
return c.killProcess.CallUnary(ctx, req)
}
// ConnectProcess calls hostagent.v1.HostAgentService.ConnectProcess.
func (c *hostAgentServiceClient) ConnectProcess(ctx context.Context, req *connect.Request[gen.ConnectProcessRequest]) (*connect.ServerStreamForClient[gen.ConnectProcessResponse], error) {
return c.connectProcess.CallServerStream(ctx, req)
}
// HostAgentServiceHandler is an implementation of the hostagent.v1.HostAgentService service. // HostAgentServiceHandler is an implementation of the hostagent.v1.HostAgentService service.
type HostAgentServiceHandler interface { type HostAgentServiceHandler interface {
// CreateSandbox boots a new microVM with the given configuration. // CreateSandbox boots a new microVM with the given configuration.
@ -555,6 +624,15 @@ type HostAgentServiceHandler interface {
PtyResize(context.Context, *connect.Request[gen.PtyResizeRequest]) (*connect.Response[gen.PtyResizeResponse], error) PtyResize(context.Context, *connect.Request[gen.PtyResizeRequest]) (*connect.Response[gen.PtyResizeResponse], error)
// PtyKill sends a signal to a PTY process. // PtyKill sends a signal to a PTY process.
PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error) PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error)
// StartBackground starts a process in the background and returns immediately
// with the PID and tag. The process survives RPC disconnection.
StartBackground(context.Context, *connect.Request[gen.StartBackgroundRequest]) (*connect.Response[gen.StartBackgroundResponse], error)
// ListProcesses returns all running processes inside a sandbox.
ListProcesses(context.Context, *connect.Request[gen.ListProcessesRequest]) (*connect.Response[gen.ListProcessesResponse], error)
// KillProcess sends a signal to a process identified by PID or tag.
KillProcess(context.Context, *connect.Request[gen.KillProcessRequest]) (*connect.Response[gen.KillProcessResponse], error)
// ConnectProcess re-attaches to a running process and streams its output.
ConnectProcess(context.Context, *connect.Request[gen.ConnectProcessRequest], *connect.ServerStream[gen.ConnectProcessResponse]) error
} }
// NewHostAgentServiceHandler builds an HTTP handler from the service implementation. It returns the // NewHostAgentServiceHandler builds an HTTP handler from the service implementation. It returns the
@ -714,6 +792,30 @@ func NewHostAgentServiceHandler(svc HostAgentServiceHandler, opts ...connect.Han
connect.WithSchema(hostAgentServiceMethods.ByName("PtyKill")), connect.WithSchema(hostAgentServiceMethods.ByName("PtyKill")),
connect.WithHandlerOptions(opts...), connect.WithHandlerOptions(opts...),
) )
hostAgentServiceStartBackgroundHandler := connect.NewUnaryHandler(
HostAgentServiceStartBackgroundProcedure,
svc.StartBackground,
connect.WithSchema(hostAgentServiceMethods.ByName("StartBackground")),
connect.WithHandlerOptions(opts...),
)
hostAgentServiceListProcessesHandler := connect.NewUnaryHandler(
HostAgentServiceListProcessesProcedure,
svc.ListProcesses,
connect.WithSchema(hostAgentServiceMethods.ByName("ListProcesses")),
connect.WithHandlerOptions(opts...),
)
hostAgentServiceKillProcessHandler := connect.NewUnaryHandler(
HostAgentServiceKillProcessProcedure,
svc.KillProcess,
connect.WithSchema(hostAgentServiceMethods.ByName("KillProcess")),
connect.WithHandlerOptions(opts...),
)
hostAgentServiceConnectProcessHandler := connect.NewServerStreamHandler(
HostAgentServiceConnectProcessProcedure,
svc.ConnectProcess,
connect.WithSchema(hostAgentServiceMethods.ByName("ConnectProcess")),
connect.WithHandlerOptions(opts...),
)
return "/hostagent.v1.HostAgentService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return "/hostagent.v1.HostAgentService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { switch r.URL.Path {
case HostAgentServiceCreateSandboxProcedure: case HostAgentServiceCreateSandboxProcedure:
@ -766,6 +868,14 @@ func NewHostAgentServiceHandler(svc HostAgentServiceHandler, opts ...connect.Han
hostAgentServicePtyResizeHandler.ServeHTTP(w, r) hostAgentServicePtyResizeHandler.ServeHTTP(w, r)
case HostAgentServicePtyKillProcedure: case HostAgentServicePtyKillProcedure:
hostAgentServicePtyKillHandler.ServeHTTP(w, r) hostAgentServicePtyKillHandler.ServeHTTP(w, r)
case HostAgentServiceStartBackgroundProcedure:
hostAgentServiceStartBackgroundHandler.ServeHTTP(w, r)
case HostAgentServiceListProcessesProcedure:
hostAgentServiceListProcessesHandler.ServeHTTP(w, r)
case HostAgentServiceKillProcessProcedure:
hostAgentServiceKillProcessHandler.ServeHTTP(w, r)
case HostAgentServiceConnectProcessProcedure:
hostAgentServiceConnectProcessHandler.ServeHTTP(w, r)
default: default:
http.NotFound(w, r) http.NotFound(w, r)
} }
@ -874,3 +984,19 @@ func (UnimplementedHostAgentServiceHandler) PtyResize(context.Context, *connect.
func (UnimplementedHostAgentServiceHandler) PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error) { func (UnimplementedHostAgentServiceHandler) PtyKill(context.Context, *connect.Request[gen.PtyKillRequest]) (*connect.Response[gen.PtyKillResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.PtyKill is not implemented")) return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.PtyKill is not implemented"))
} }
func (UnimplementedHostAgentServiceHandler) StartBackground(context.Context, *connect.Request[gen.StartBackgroundRequest]) (*connect.Response[gen.StartBackgroundResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.StartBackground is not implemented"))
}
func (UnimplementedHostAgentServiceHandler) ListProcesses(context.Context, *connect.Request[gen.ListProcessesRequest]) (*connect.Response[gen.ListProcessesResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.ListProcesses is not implemented"))
}
func (UnimplementedHostAgentServiceHandler) KillProcess(context.Context, *connect.Request[gen.KillProcessRequest]) (*connect.Response[gen.KillProcessResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.KillProcess is not implemented"))
}
func (UnimplementedHostAgentServiceHandler) ConnectProcess(context.Context, *connect.Request[gen.ConnectProcessRequest], *connect.ServerStream[gen.ConnectProcessResponse]) error {
return connect.NewError(connect.CodeUnimplemented, errors.New("hostagent.v1.HostAgentService.ConnectProcess is not implemented"))
}

View File

@ -91,6 +91,19 @@ service HostAgentService {
// PtyKill sends a signal to a PTY process. // PtyKill sends a signal to a PTY process.
rpc PtyKill(PtyKillRequest) returns (PtyKillResponse); rpc PtyKill(PtyKillRequest) returns (PtyKillResponse);
// StartBackground starts a process in the background and returns immediately
// with the PID and tag. The process survives RPC disconnection.
rpc StartBackground(StartBackgroundRequest) returns (StartBackgroundResponse);
// ListProcesses returns all running processes inside a sandbox.
rpc ListProcesses(ListProcessesRequest) returns (ListProcessesResponse);
// KillProcess sends a signal to a process identified by PID or tag.
rpc KillProcess(KillProcessRequest) returns (KillProcessResponse);
// ConnectProcess re-attaches to a running process and streams its output.
rpc ConnectProcess(ConnectProcessRequest) returns (stream ConnectProcessResponse);
} }
message CreateSandboxRequest { message CreateSandboxRequest {
@ -476,3 +489,64 @@ message PtyKillRequest {
} }
message PtyKillResponse {} message PtyKillResponse {}
// ── Background Processes ───────────────────────────────────────────
message StartBackgroundRequest {
string sandbox_id = 1;
string cmd = 2;
repeated string args = 3;
// User-chosen tag for the process. If empty, the host agent generates one.
string tag = 4;
map<string, string> envs = 5;
string cwd = 6;
}
message StartBackgroundResponse {
uint32 pid = 1;
string tag = 2;
}
message ListProcessesRequest {
string sandbox_id = 1;
}
message ProcessEntry {
uint32 pid = 1;
string tag = 2;
string cmd = 3;
repeated string args = 4;
}
message ListProcessesResponse {
repeated ProcessEntry processes = 1;
}
message KillProcessRequest {
string sandbox_id = 1;
oneof selector {
uint32 pid = 2;
string tag = 3;
}
// Signal to send: "SIGTERM" or "SIGKILL" (default: "SIGKILL").
string signal = 4;
}
message KillProcessResponse {}
message ConnectProcessRequest {
string sandbox_id = 1;
oneof selector {
uint32 pid = 2;
string tag = 3;
}
}
// Reuses ExecStream event types for symmetry.
message ConnectProcessResponse {
oneof event {
ExecStreamStart start = 1;
ExecStreamData data = 2;
ExecStreamEnd end = 3;
}
}