Go SDK
Public Go client for the grain daemon — DialUnix, create, exec, lifecycle, and more.
Package: github.com/cxdy/grain/client
This package talks HTTP to a running daemon. It does not import internal/ packages. Types mirror the OpenAPI schema.
Install
go get github.com/cxdy/grain/client@latest
Connect
package main
import (
"context"
"log"
"os"
"path/filepath"
"github.com/cxdy/grain/client"
)
func main() {
ctx := context.Background()
home, _ := os.UserHomeDir()
sock := filepath.Join(home, ".grain", "grain.sock")
// Local unix socket (typical CLI setup)
c, err := client.DialUnix(sock)
if err != nil {
log.Fatal(err)
}
// With Bearer token when daemon has api_token set:
// c, err = client.DialUnixToken(sock, os.Getenv("GRAIN_TOKEN"))
// TCP:
// c, err = client.DialHTTP("http://127.0.0.1:7474", os.Getenv("GRAIN_TOKEN"))
if err := c.Health(ctx); err != nil {
log.Fatal(err)
}
}
Create with readiness
Wait and Timeout are query parameters, not JSON body fields:
| Wait | Meaning |
|---|---|
"" / auto |
Daemon chooses (agent for golden images, else SSH) |
ssh |
SSH accepts connections |
agent |
Guest agent /health OK |
userdata |
Agent reports userdata finished |
inst, err := c.Create(ctx, client.CreateRequest{
Name: "dev",
Persistent: false,
CPUs: 2,
MemoryMB: 2048,
Wait: client.WaitAgent,
Timeout: "3m",
})
// Progress as NDJSON phases
inst, err = c.CreateStream(ctx, client.CreateRequest{
Wait: client.WaitSSH,
}, func(ev client.CreateEvent) {
log.Printf("%s: %s", ev.Phase, ev.Message)
})
Exec and agent
res, err := c.Exec(ctx, inst.Name, "uname", "-a")
// res.Stdout, res.Stderr, res.ExitCode — non-zero exit is not always a Go error
exitCode, err := c.ExecStream(ctx, inst.Name, client.ExecOpts{
Cmd: "bash",
Args: []string{"-lc", "for i in 1 2 3; do echo $i; done"},
}, func(fr client.ExecFrame) error {
if fr.Type == "stdout" {
os.Stdout.WriteString(fr.Data)
}
return nil
})
h, err := c.AgentHealth(ctx, inst.Name)
st, err := c.Stats(ctx, inst.Name)
Files
f, _ := os.Open("app.tgz")
defer f.Close()
fi, _ := f.Stat()
_ = c.PutFile(ctx, inst.Name, "/tmp/app.tgz", f, fi.Size(), client.CPOpts{})
var buf bytes.Buffer
_ = c.GetFile(ctx, inst.Name, "/etc/os-release", &buf)
entries, _ := c.ReadDir(ctx, inst.Name, "/tmp")
_ = c.Mkdir(ctx, inst.Name, "/opt/app", true, "0755")
Lifecycle
| Method | Behavior |
|---|---|
Start |
Boot stopped persistent VM |
Shutdown / Stop |
Stop; ephemeral deleted |
Pause / Resume |
Freeze / unfreeze vCPUs |
Suspend / Restore |
Free RAM / restore (persistent) |
Delete |
Remove VM |
AddForward / RemoveForward |
Live TCP forwards |
_ = c.Pause(ctx, name)
_ = c.Resume(ctx, name)
_ = c.Suspend(ctx, name)
inst, err = c.Restore(ctx, name)
lf, err := c.AddForward(ctx, name, 0, 8080) // host port allocated
_ = c.RemoveForward(ctx, name, lf.HostPort)
Secrets
_, _ = c.SetSecret(ctx, client.SecretPut{Name: "k", Data: []byte("value")})
_, _ = c.InjectSecret(ctx, inst.Name, "k", "") // default guest path
_ = c.DeleteSecret(ctx, "k")
Errors
HTTP API errors decode to messages from the JSON error field. Always check err after dial and mutating calls. For Exec, inspect ExitCode even when err == nil.
See also
- HTTP API
- TypeScript SDK
- Repo package docs:
client/README.md