mikronova/kyokkou
docker/agent/main.go
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"sync"
)
const (
blenderBin = "/opt/blender/blender"
scenesDir = "/agent/scenes"
configPath = "/in/config.json"
)
var scenes = map[string]string{
"fluid_pipes": "fluid_pipes.py",
"god_rays": "fluid_pipes.py",
"metal_reflections": "fluid_pipes.py",
"water_liquid": "fluid_pipes.py",
"volumetric_clouds": "fluid_pipes.py",
}
var mu sync.Mutex
func main() {
http.HandleFunc("/health", health)
http.HandleFunc("/render", render)
log.Println("kyokkou agent listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func health(w http.ResponseWriter, _ *http.Request) {
if _, err := os.Stat(blenderBin); err != nil {
http.Error(w, "blender not found at "+blenderBin, http.StatusServiceUnavailable)
return
}
fmt.Fprint(w, "ok")
}
func render(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var cfg struct {
SceneType string `json:"scene_type"`
}
if err := json.Unmarshal(body, &cfg); err != nil {
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
return
}
script, ok := scenes[cfg.SceneType]
if !ok {
http.Error(w, "unknown scene_type: "+cfg.SceneType, http.StatusBadRequest)
return
}
if err := os.WriteFile(configPath, body, 0644); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
mu.Lock()
defer mu.Unlock()
cmd := exec.Command(blenderBin,
"--background",
"--python", filepath.Join(scenesDir, script),
"--", "--config", configPath,
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
http.Error(w, "blender: "+err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "ok")
}