149 lines
5.2 KiB
Go
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
|
|
}
|