add: main function
This commit is contained in:
@@ -2,25 +2,70 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/ollama/ollama/api"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"tipsy.codes/charles/mc-god/v2/internal/pkg/logs"
|
||||
"tipsy.codes/charles/mc-god/v2/internal/pkg/rcon"
|
||||
)
|
||||
|
||||
type chatContext struct {
|
||||
chatRequest *api.ChatRequest
|
||||
totalSize int
|
||||
maxSize int
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *chatContext) AddLog(msg string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.chatRequest.Messages = append(c.chatRequest.Messages, api.Message{
|
||||
Role: "logs",
|
||||
Content: msg,
|
||||
})
|
||||
c.totalSize += len(msg)
|
||||
c.truncate()
|
||||
}
|
||||
|
||||
func (c *chatContext) AddSelf(msg api.Message) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.chatRequest.Messages = append(c.chatRequest.Messages, msg)
|
||||
c.totalSize += len(msg.Content)
|
||||
c.truncate()
|
||||
}
|
||||
|
||||
func (c *chatContext) truncate() {
|
||||
for c.maxSize != 0 && c.totalSize > c.maxSize && len(c.chatRequest.Messages) > 0 {
|
||||
t := c.chatRequest.Messages[0]
|
||||
c.chatRequest.Messages = c.chatRequest.Messages[1:]
|
||||
c.totalSize -= len(t.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create a context that will be cancelled on interrupt signals
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_ = ctx
|
||||
|
||||
// Set up signal handling for graceful shutdown
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigChan
|
||||
log.Println("Received interrupt signal, shutting down...")
|
||||
slog.Info("received interrupt signal, shutting down...")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
@@ -28,18 +73,142 @@ func main() {
|
||||
log.Println("Connecting to Minecraft server via RCON...")
|
||||
client, err := rcon.NewFromEnv()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create RCON client: %v", err)
|
||||
slog.Error("failed to create RCON client", "error", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := client.Close(); err != nil {
|
||||
log.Printf("Error closing RCON connection: %v", err)
|
||||
slog.Warn("error closing RCON connection", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Perform a health check
|
||||
log.Println("Performing health check...")
|
||||
log.Println("Performing healthcheck...")
|
||||
if err := client.HealthCheck(); err != nil {
|
||||
log.Fatalf("Health check failed: %v", err)
|
||||
slog.Error("Health check failed", "error", err)
|
||||
return
|
||||
}
|
||||
log.Println("Connected successfully!")
|
||||
|
||||
// Create Kubernetes client
|
||||
kClient, err := createKubernetesClient()
|
||||
if err != nil {
|
||||
slog.Error("failed to create kubernetes client", "error", err)
|
||||
return
|
||||
}
|
||||
slog.Info("got kubernetes config")
|
||||
|
||||
tailer, done := logs.LoggerFromEnv().Start(ctx, kClient)
|
||||
defer func() {
|
||||
if err := done(); err != nil {
|
||||
slog.Error("problem with tailer", "error", err)
|
||||
}
|
||||
}()
|
||||
slog.Info("logger started")
|
||||
|
||||
ollamaClient, err := api.ClientFromEnvironment()
|
||||
if err != nil {
|
||||
slog.Error("error getting ollama client", "error", err)
|
||||
}
|
||||
|
||||
rClient, err := rcon.NewFromEnv()
|
||||
if err != nil {
|
||||
slog.Error("failed to get rcon client", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Start goroutines to do the things
|
||||
chatRequest := &api.ChatRequest{
|
||||
Model: "qwen3-coder",
|
||||
Stream: proto.Bool(false),
|
||||
KeepAlive: &api.Duration{Duration: time.Hour},
|
||||
Tools: api.Tools{},
|
||||
Think: &api.ThinkValue{Value: false},
|
||||
Shift: proto.Bool(true),
|
||||
Messages: []api.Message{
|
||||
api.Message{
|
||||
Role: "system",
|
||||
Content: `
|
||||
You are Minecraft server admin with a god complex.
|
||||
React to any messages from the user by saying something god-like.
|
||||
When you join the server, announce yourself.
|
||||
|
||||
Responses should be short; one sentence.
|
||||
You may choose to return an empty response if there is nothing interesting to say
|
||||
(i.e., no new logs since your last message).
|
||||
|
||||
When a user replies to you, you will see this in the logs:
|
||||
|
||||
2026/02/14 10:48:40 INFO mc log msg="[18:45:10] [Server thread/INFO]: <OrangeYouSad> you are full of it."
|
||||
|
||||
The user here is OrangeYouSad, who said "you are full of it."
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
chat := &chatContext{
|
||||
chatRequest: chatRequest,
|
||||
maxSize: 10000000,
|
||||
}
|
||||
|
||||
doneWg := sync.WaitGroup{}
|
||||
doneWg.Go(handleOllama(ctx, ollamaClient, chat, rClient))
|
||||
|
||||
for line := range tailer.NextLine() {
|
||||
slog.Info("mc log", "msg", line)
|
||||
chat.AddLog(line)
|
||||
}
|
||||
|
||||
doneWg.Wait()
|
||||
}
|
||||
|
||||
func handleOllama(ctx context.Context, client *api.Client, chat *chatContext, rClient *rcon.Client) func() {
|
||||
slog.Info("got chat request", "object", fmt.Sprintf("%+v", chat.chatRequest))
|
||||
return func() {
|
||||
var chatResponse api.ChatResponse
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.Tick(time.Second * 10):
|
||||
// do nothing
|
||||
}
|
||||
chat.mu.Lock()
|
||||
// slog.Info("sending chat request", "object", fmt.Sprintf("%#v", chat.chatRequest))
|
||||
err := client.Chat(ctx, chat.chatRequest, func(cr api.ChatResponse) error {
|
||||
chatResponse = cr
|
||||
return nil
|
||||
})
|
||||
chat.mu.Unlock()
|
||||
if err != nil {
|
||||
slog.Error("error calling ollama", "error", err)
|
||||
}
|
||||
chat.AddSelf(chatResponse.Message)
|
||||
if err := rClient.Say(chatResponse.Message.Content); err != nil {
|
||||
slog.Error("error talking", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
// Try to load in-cluster config first
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
// If in-cluster config fails, try kubeconfig
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
configOverrides := &clientcmd.ConfigOverrides{}
|
||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||
config, err = kubeConfig.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user