Files
wx-win-agent/control-plane/cmd/wxagent-control-plane/main.go
T

149 lines
5.2 KiB
Go

package main
import (
"context"
"encoding/json"
"log"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
controlplane "git.ipao.vip/rogee/wx-win-agent/control-plane"
)
func main() {
config := controlplane.DefaultServerConfig()
if value := os.Getenv("WXAGENT_CONTROL_PLANE_ADDR"); value != "" {
config.ListenAddr = value
}
if value := os.Getenv("WXAGENT_CONTROL_PLANE_DATA"); value != "" {
config.DataFile = value
}
config.BackupDir = os.Getenv("WXAGENT_CONTROL_PLANE_BACKUP_DIR")
config.BackupCount = readIntEnv("WXAGENT_CONTROL_PLANE_BACKUP_COUNT", config.BackupCount)
config.BackupInterval = readDurationEnv("WXAGENT_CONTROL_PLANE_BACKUP_INTERVAL", config.BackupInterval)
config.TaskRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_TASK_RETENTION", config.TaskRetention)
config.EventRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_EVENT_RETENTION", config.EventRetention)
config.AuditRetention = readDurationEnv("WXAGENT_CONTROL_PLANE_AUDIT_RETENTION", config.AuditRetention)
config.AccountRetention = readDurationEnv("WXAGENT_ACCOUNT_DATA_RETENTION", config.AccountRetention)
config.AccountMaxShardBytes = readInt64Env("WXAGENT_ACCOUNT_MAX_SHARD_BYTES", config.AccountMaxShardBytes)
config.AccountMaxBatchBytes = readInt64Env("WXAGENT_ACCOUNT_MAX_BATCH_BYTES", config.AccountMaxBatchBytes)
config.AccountMaintenanceInterval = readDurationEnv("WXAGENT_ACCOUNT_MAINTENANCE_INTERVAL", config.AccountMaintenanceInterval)
config.TLSCertFile = os.Getenv("WXAGENT_CONTROL_PLANE_TLS_CERT_FILE")
config.TLSKeyFile = os.Getenv("WXAGENT_CONTROL_PLANE_TLS_KEY_FILE")
config.MTLSClientCAFile = os.Getenv("WXAGENT_MTLS_CLIENT_CA_FILE")
config.MTLSRequireNodeCert = readBoolEnv("WXAGENT_MTLS_REQUIRE_NODE_CERT", false)
config.MTLSRevokedCertsFile = os.Getenv("WXAGENT_MTLS_REVOKED_CERTS_FILE")
config.AIBaseURL = os.Getenv("WXAGENT_AI_BASE_URL")
config.AIAPIKey = readSecret("WXAGENT_AI_API_KEY", "WXAGENT_AI_API_KEY_FILE")
if value := os.Getenv("WXAGENT_AI_MODEL"); value != "" {
config.AIModel = value
}
config.AITimeout = readDurationEnv("WXAGENT_AI_TIMEOUT", config.AITimeout)
config.AISchedulerInterval = readDurationEnv("WXAGENT_AI_SCHEDULER_INTERVAL", config.AISchedulerInterval)
config.NodeTokens = readMapSecret("WXAGENT_NODE_TOKENS", "WXAGENT_NODE_TOKENS_FILE")
if len(config.NodeTokens) == 0 {
nodeID, nodeToken := os.Getenv("WXAGENT_NODE_ID"), readSecret("WXAGENT_NODE_TOKEN", "WXAGENT_NODE_TOKEN_FILE")
if strings.TrimSpace(nodeID) != "" && nodeToken != "" {
config.NodeTokens = map[string]string{nodeID: nodeToken}
}
}
config.WebUsers = readMapSecret("WXAGENT_WEB_USERS", "WXAGENT_WEB_USERS_FILE")
if len(config.WebUsers) == 0 {
webUser, webPassword := os.Getenv("WXAGENT_WEB_USER"), readSecret("WXAGENT_WEB_PASSWORD", "WXAGENT_WEB_PASSWORD_FILE")
if strings.TrimSpace(webUser) != "" && webPassword != "" {
config.WebUsers = map[string]string{webUser: webPassword}
}
}
if len(config.NodeTokens) == 0 || len(config.WebUsers) == 0 {
log.Fatal("configure node and web credentials with environment variables or *_FILE secret files")
}
server, err := controlplane.NewServer(config)
if err != nil {
log.Fatal(err)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
log.Printf("wxagent control plane listening on %s; data=%s", config.ListenAddr, config.DataFile)
if err := server.ListenAndServe(ctx); err != nil {
log.Printf("control plane stopped: %v", err)
os.Exit(1)
}
}
func readSecret(valueName, fileName string) string {
if path := strings.TrimSpace(os.Getenv(fileName)); path != "" {
data, err := os.ReadFile(path)
if err != nil {
log.Fatalf("read %s: %v", fileName, err)
}
return strings.TrimRight(string(data), "\r\n")
}
return os.Getenv(valueName)
}
func readMapSecret(valueName, fileName string) map[string]string {
value := readSecret(valueName, fileName)
if value == "" {
return nil
}
result := make(map[string]string)
if err := json.Unmarshal([]byte(value), &result); err != nil {
log.Fatalf("%s must contain a JSON object", fileName)
}
return result
}
func readBoolEnv(name string, fallback bool) bool {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := strconv.ParseBool(value)
if err != nil {
log.Fatalf("%s must be true or false", name)
}
return parsed
}
func readIntEnv(name string, fallback int) int {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := strconv.Atoi(value)
if err != nil || parsed < 1 {
log.Fatalf("%s must be a positive integer", name)
}
return parsed
}
func readInt64Env(name string, fallback int64) int64 {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil || parsed < 0 {
log.Fatalf("%s must be a non-negative integer", name)
}
return parsed
}
func readDurationEnv(name string, fallback time.Duration) time.Duration {
value := strings.TrimSpace(os.Getenv(name))
if value == "" {
return fallback
}
parsed, err := time.ParseDuration(value)
if err != nil || parsed < 0 {
log.Fatalf("%s must be a non-negative duration", name)
}
return parsed
}