Files
lpra/internal/proto/proto.go
T
redtea 86cb0226ab feat: machine status page, speed test, live presence, forwards without ufw (0.0.5)
Status page per machine: CPU load, RAM and disk of the client, sampled
by the agent only while the page is open and visible (one reading
serves every open tab), plus a button that has the agent measure its
link to the host: ping, down and up.

Online/offline now comes from the control polls the hub holds open, so
a dropped agent shows offline at once instead of 45 s later, and the
dashboard keeps the status column live.

A forward rule can be created "without ufw": the panel opens nothing in
ufw for it and so revokes nothing when it is deleted. The checkbox is
shown only while the ufw integration is on.

Dashboard row actions are icons; a status action is added.
2026-10-13 18:56:15 +03:00

189 lines
6.5 KiB
Go

// Package proto describes the wire messages shared by the server and the agent.
//
// All agent<->server traffic is plain JSON over HTTP(S) long-polling so it
// survives NAT and restrictive egress firewalls: the agent only ever makes
// outbound requests.
package proto
import "encoding/json"
// Control poll message types (server -> agent, returned from /api/agent/poll).
const (
// CtrlIdle is returned when the long-poll times out with nothing to do.
CtrlIdle = "idle"
// CtrlOpenTerminal tells the agent to spawn a PTY for the session id.
CtrlOpenTerminal = "open_terminal"
// CtrlOpenForward tells the agent to dial 127.0.0.1:Port and bridge it to
// the session id (reverse TCP port-forward).
CtrlOpenForward = "open_forward"
// CtrlTask tells the agent to fetch and run the one-shot task TaskID.
CtrlTask = "task"
)
// EnrollRequest is sent once by a freshly installed agent.
type EnrollRequest struct {
Name string `json:"name"`
Hostname string `json:"hostname"`
OS string `json:"os"`
Arch string `json:"arch"`
Version string `json:"version"`
User string `json:"user,omitempty"` // OS user the agent runs as
}
// EnrollResponse carries the identity the agent persists and uses from then on.
type EnrollResponse struct {
AgentID string `json:"agent_id"`
Token string `json:"token"`
}
// ControlMessage is the body returned by the control long-poll.
type ControlMessage struct {
Type string `json:"type"`
SessionID string `json:"session_id,omitempty"`
Port int `json:"port,omitempty"` // CtrlOpenForward: client-side port to dial
TaskID string `json:"task_id,omitempty"` // CtrlTask
User string `json:"user,omitempty"` // CtrlOpenTerminal: OS user for the shell ("" = the agent's own)
}
// Terminal / stream frame types on the session "in" channel (server -> agent).
// Reused for both PTY terminals and raw TCP port-forward streams.
const (
FrameStdin = "stdin" // base64 bytes for the PTY / socket
FrameResize = "resize" // PTY window size (terminal only)
FrameClose = "close" // tear the stream down
)
// InFrame is one element of the stream downstream batch (server -> agent).
type InFrame struct {
Type string `json:"type"`
Data string `json:"data,omitempty"` // base64 bytes
Cols uint16 `json:"cols,omitempty"`
Rows uint16 `json:"rows,omitempty"`
}
// InBatch is the JSON body returned by GET /api/agent/term/{sid}/in.
type InBatch struct {
Frames []InFrame `json:"frames"`
}
// OutMessage is POSTed by the agent to /api/agent/term/{sid}/out.
type OutMessage struct {
Data string `json:"data,omitempty"` // base64 bytes
Exit bool `json:"exit,omitempty"`
}
// --- one-shot tasks (file manager, native in the agent) ---
const (
TaskFSList = "fs.list" // list a directory
TaskFSRead = "fs.read" // read file content (for view/edit)
TaskFSWrite = "fs.write" // write/overwrite file (edit save, create, upload)
TaskFSMkdir = "fs.mkdir" // create directory
TaskFSDelete = "fs.delete" // delete a single file or empty dir
TaskFSDelTree = "fs.deltree" // recursively delete a directory
TaskFSUnzip = "fs.unzip" // unzip an archive (needs `unzip` on the client)
TaskFSZip = "fs.zip" // zip selected paths (native archive/zip)
TaskFSMove = "fs.move" // move/rename
TaskFSDownload = "fs.download" // read a whole file (larger cap) for browser download
TaskSysUpdate = "sys.update" // download the latest agent binary and restart
TaskSysUsers = "sys.users" // report the agent's user and the login-capable users
TaskSysUserAdd = "sys.useradd" // create a new OS user
TaskSysStat = "sys.stat" // CPU load, RAM and disk usage right now
TaskSysSpeed = "sys.speed" // measure the link to the server (ping, down, up)
)
// FSReadCap bounds how many bytes TaskFSRead returns (5 MiB). The panel lets a
// file be edited only when it fits, so that a save never cuts it short.
const FSReadCap = 5 * 1024 * 1024
// TaskEnvelope is what the agent fetches for a task id.
type TaskEnvelope struct {
Type string `json:"type"`
Args json.RawMessage `json:"args"`
}
// TaskResult is POSTed back by the agent.
type TaskResult struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// fs task argument shapes.
type FSPathArgs struct {
Path string `json:"path"`
}
type FSWriteArgs struct {
Path string `json:"path"`
Data string `json:"data"` // base64 content
}
type FSUnzipArgs struct {
Path string `json:"path"`
Dest string `json:"dest"`
}
type FSZipArgs struct {
Paths []string `json:"paths"`
Dest string `json:"dest"`
}
type FSMoveArgs struct {
Src string `json:"src"`
Dst string `json:"dst"`
}
// FSEntry is one directory entry.
type FSEntry struct {
Name string `json:"name"`
IsDir bool `json:"is_dir"`
Size int64 `json:"size"`
Mode string `json:"mode"`
MTime int64 `json:"mtime"`
}
// FSListResult is returned for TaskFSList.
type FSListResult struct {
Path string `json:"path"`
Entries []FSEntry `json:"entries"`
}
// FSReadResult is returned for TaskFSRead.
type FSReadResult struct {
Path string `json:"path"`
Size int64 `json:"size"`
Content string `json:"content"` // base64
Truncated bool `json:"truncated"`
Binary bool `json:"binary"`
}
// SysUsersResult is returned for TaskSysUsers.
type SysUsersResult struct {
Current string `json:"current"` // user the agent runs as
CanSwitch bool `json:"can_switch"` // agent is root, so it can open shells as (and create) other users
Users []string `json:"users"`
}
// SysUserAddArgs are the arguments of TaskSysUserAdd.
type SysUserAddArgs struct {
Name string `json:"name"`
Password string `json:"password,omitempty"` // empty leaves the account without a password
Sudo bool `json:"sudo"` // add to the sudo/wheel group
}
// SysStatResult is returned for TaskSysStat. Sizes are in bytes.
type SysStatResult struct {
CPU float64 `json:"cpu"` // load across all cores, 0..100
NCPU int `json:"ncpu"`
MemTotal uint64 `json:"mem_total"`
MemUsed uint64 `json:"mem_used"`
DiskTotal uint64 `json:"disk_total"` // filesystem holding "/"
DiskFree uint64 `json:"disk_free"`
}
// SysSpeedResult is returned for TaskSysSpeed: the agent's HTTP link to the
// server as the agent measured it. Down is server -> agent.
type SysSpeedResult struct {
PingMs float64 `json:"ping_ms"`
DownBps float64 `json:"down_bps"` // bits per second
UpBps float64 `json:"up_bps"`
}