mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat: complete HomeAgent architecture v2
- IO abstraction layer with OutputChannel routing and capability validation - Three-layer memory (Context-Document-Graph) with TF-IDF relevance pruning - OneBot V11 QQ protocol plugin with Reverse WebSocket client - Plugin system with hot-reload (SKILL.md + native factories) - Knowledge system with TF-IDF vector indexing - Personality system (personal.md) - Text memory (JSONL with rotation) - Change tracker (overlayfs) with rollback - Lua adapter VM - Design document (DESIGN.md) Module: gitcode.com/JianFeeeee/HomeAgent
This commit is contained in:
209
internal/container/manager.go
Normal file
209
internal/container/manager.go
Normal file
@ -0,0 +1,209 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
dataDir string
|
||||
}
|
||||
|
||||
func NewManager(dataDir string) *Manager {
|
||||
return &Manager{dataDir: dataDir}
|
||||
}
|
||||
|
||||
type ContainerInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, cfg *types.AgentConfig) (*ContainerInfo, error) {
|
||||
args := []string{
|
||||
"create",
|
||||
"--name", "ha-" + string(cfg.ID),
|
||||
"--hostname", string(cfg.ID),
|
||||
"--restart", "no",
|
||||
"--stop-timeout", "10",
|
||||
"--memory", cfg.ResourceLimit.Memory,
|
||||
"--cpus", cfg.ResourceLimit.CPU,
|
||||
"--label", "homeagent.managed=true",
|
||||
"--label", "homeagent.agent-id=" + string(cfg.ID),
|
||||
}
|
||||
|
||||
if !cfg.ResourceLimit.Network {
|
||||
args = append(args, "--network", "none")
|
||||
}
|
||||
|
||||
args = append(args, cfg.Image)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "docker", args...)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker create: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
|
||||
id := strings.TrimSpace(string(out))
|
||||
return &ContainerInfo{ID: id, Name: "ha-" + string(cfg.ID), Status: "created", Image: cfg.Image}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Start(ctx context.Context, containerID string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "start", containerID)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker start: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Stop(ctx context.Context, containerID string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "stop", "--time", "5", containerID)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker stop: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(ctx context.Context, containerID string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "rm", "-f", containerID)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker rm: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Inspect(ctx context.Context, containerID string) (*ContainerInfo, error) {
|
||||
cmd := exec.CommandContext(ctx, "docker", "inspect", containerID)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker inspect: %w", err)
|
||||
}
|
||||
|
||||
var containers []struct {
|
||||
ID string `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
State struct {
|
||||
Status string `json:"Status"`
|
||||
} `json:"State"`
|
||||
Config struct {
|
||||
Image string `json:"Image"`
|
||||
} `json:"Config"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(out, &containers); err != nil {
|
||||
return nil, fmt.Errorf("parse inspect: %w", err)
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("container %s not found", containerID)
|
||||
}
|
||||
|
||||
c := containers[0]
|
||||
return &ContainerInfo{
|
||||
ID: c.ID,
|
||||
Name: strings.TrimPrefix(c.Name, "/"),
|
||||
Status: c.State.Status,
|
||||
Image: c.Config.Image,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) WaitHealthy(ctx context.Context, containerID string) error {
|
||||
args := []string{
|
||||
"exec", containerID,
|
||||
"agentd", "--probe",
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "docker", args...)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (m *Manager) Exec(ctx context.Context, containerID string, cmdArgs []string) ([]byte, error) {
|
||||
args := append([]string{"exec", containerID}, cmdArgs...)
|
||||
cmd := exec.CommandContext(ctx, "docker", args...)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker exec: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Commit(ctx context.Context, containerID string, imageTag string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "commit", containerID, imageTag)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker commit: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) SaveImage(ctx context.Context, imageTag string, outputPath string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "save", "-o", outputPath, imageTag)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker save: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) LoadImage(ctx context.Context, inputPath string) error {
|
||||
cmd := exec.CommandContext(ctx, "docker", "load", "-i", inputPath)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("docker load: %s: %w", strings.TrimSpace(stderr.String()), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) ListManaged(ctx context.Context) ([]ContainerInfo, error) {
|
||||
cmd := exec.CommandContext(ctx, "docker", "ps", "-a",
|
||||
"--filter", "label=homeagent.managed=true",
|
||||
"--format", "{{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}",
|
||||
)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker ps: %w", err)
|
||||
}
|
||||
|
||||
var containers []ContainerInfo
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "\t", 4)
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
}
|
||||
containers = append(containers, ContainerInfo{
|
||||
ID: parts[0], Name: parts[1], Status: parts[2], Image: parts[3],
|
||||
})
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
Reference in New Issue
Block a user