its a bit broken

This commit is contained in:
2025-11-25 03:48:28 -06:00
parent a53ea4dce7
commit 690e6b13f8
16 changed files with 1542 additions and 861 deletions

View File

@@ -3,7 +3,6 @@ package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
@@ -17,36 +16,46 @@ import (
"jiggablend/internal/runner"
)
type SecretsFile struct {
RunnerID int64 `json:"runner_id"`
RunnerSecret string `json:"runner_secret"`
ManagerSecret string `json:"manager_secret"`
}
// Removed SecretsFile - runners now generate ephemeral instance IDs
func main() {
log.Printf("Runner starting up...")
// Create client early so we can clean it up on panic
var client *runner.Client
defer func() {
if r := recover(); r != nil {
log.Printf("Runner panicked: %v", r)
// Clean up workspace even on panic
if client != nil {
client.CleanupWorkspace()
}
os.Exit(1)
}
}()
var (
managerURL = flag.String("manager", getEnv("MANAGER_URL", "http://localhost:8080"), "Manager URL")
name = flag.String("name", getEnv("RUNNER_NAME", ""), "Runner name")
hostname = flag.String("hostname", getEnv("RUNNER_HOSTNAME", ""), "Runner hostname")
token = flag.String("token", getEnv("REGISTRATION_TOKEN", ""), "Registration token")
secretsFile = flag.String("secrets-file", getEnv("SECRETS_FILE", ""), "Path to secrets file for persistent storage (default: ./runner-secrets.json, or ./runner-secrets-{id}.json if multiple runners)")
runnerIDSuffix = flag.String("runner-id", getEnv("RUNNER_ID", ""), "Unique runner ID suffix (auto-generated if not provided)")
logDir = flag.String("log-dir", getEnv("LOG_DIR", "./logs"), "Log directory")
logMaxSize = flag.Int("log-max-size", getEnvInt("LOG_MAX_SIZE", 100), "Maximum log file size in MB before rotation")
logMaxBackups = flag.Int("log-max-backups", getEnvInt("LOG_MAX_BACKUPS", 5), "Maximum number of rotated log files to keep")
logMaxAge = flag.Int("log-max-age", getEnvInt("LOG_MAX_AGE", 30), "Maximum age in days for rotated log files")
managerURL = flag.String("manager", getEnv("MANAGER_URL", "http://localhost:8080"), "Manager URL")
name = flag.String("name", getEnv("RUNNER_NAME", ""), "Runner name")
hostname = flag.String("hostname", getEnv("RUNNER_HOSTNAME", ""), "Runner hostname")
apiKeyFlag = flag.String("api-key", getEnv("API_KEY", ""), "API key for authentication")
logDir = flag.String("log-dir", getEnv("LOG_DIR", "./logs"), "Log directory")
logMaxSize = flag.Int("log-max-size", getEnvInt("LOG_MAX_SIZE", 100), "Maximum log file size in MB before rotation")
logMaxBackups = flag.Int("log-max-backups", getEnvInt("LOG_MAX_BACKUPS", 5), "Maximum number of rotated log files to keep")
logMaxAge = flag.Int("log-max-age", getEnvInt("LOG_MAX_AGE", 30), "Maximum age in days for rotated log files")
)
flag.Parse()
log.Printf("Flags parsed, hostname: %s", *hostname)
if *hostname == "" {
*hostname, _ = os.Hostname()
}
// Generate or use provided runner ID suffix
runnerIDStr := *runnerIDSuffix
if runnerIDStr == "" {
runnerIDStr = generateShortID()
}
// Always generate a random runner ID suffix on startup
// This ensures every runner has a unique local identifier
runnerIDStr := generateShortID()
log.Printf("Generated runner ID suffix: %s", runnerIDStr)
// Generate runner name with ID if not provided
if *name == "" {
@@ -70,20 +79,15 @@ func main() {
l.Close()
}
}()
log.Printf("Logger initialized, continuing with startup...")
log.Printf("Log rotation configured: max_size=%dMB, max_backups=%d, max_age=%d days", *logMaxSize, *logMaxBackups, *logMaxAge)
// Set default secrets file if not provided - always use current directory
if *secretsFile == "" {
if *runnerIDSuffix != "" || getEnv("RUNNER_ID", "") != "" {
// Multiple runners - use local file with ID
*secretsFile = fmt.Sprintf("./runner-secrets-%s.json", runnerIDStr)
} else {
// Single runner - use local file
*secretsFile = "./runner-secrets.json"
}
}
log.Printf("About to create client...")
client = runner.NewClient(*managerURL, *name, *hostname)
log.Printf("Client created successfully")
client := runner.NewClient(*managerURL, *name, *hostname)
// Clean up any orphaned workspace directories from previous runs
client.CleanupWorkspace()
// Probe capabilities once at startup (before any registration attempts)
log.Printf("Probing runner capabilities...")
@@ -106,69 +110,44 @@ func main() {
log.Printf("Warning: No capabilities detected")
}
// Try to load secrets from file
var runnerID int64
var runnerSecret, managerSecret string
if *secretsFile != "" {
if secrets, err := loadSecrets(*secretsFile); err == nil {
runnerID = secrets.RunnerID
runnerSecret = secrets.RunnerSecret
managerSecret = secrets.ManagerSecret
client.SetSecrets(runnerID, runnerSecret, managerSecret)
log.Printf("Loaded secrets from %s", *secretsFile)
}
// Register with API key (with retry logic)
if *apiKeyFlag == "" {
log.Fatalf("API key required (use --api-key or set API_KEY env var)")
}
// If no secrets loaded, register with token (with retry logic)
if runnerID == 0 {
if *token == "" {
log.Fatalf("Registration token required (use --token or set REGISTRATION_TOKEN env var)")
// Retry registration with exponential backoff
backoff := 1 * time.Second
maxBackoff := 30 * time.Second
maxRetries := 10
retryCount := 0
var runnerID int64
for {
var err error
runnerID, _, _, err = client.Register(*apiKeyFlag)
if err == nil {
log.Printf("Registered runner with ID: %d", runnerID)
break
}
// Retry registration with exponential backoff
backoff := 1 * time.Second
maxBackoff := 30 * time.Second
maxRetries := 10
retryCount := 0
// Check if it's a token error (invalid/expired/used token) - shutdown immediately
errMsg := err.Error()
if strings.Contains(errMsg, "token error:") {
log.Fatalf("Registration failed (token error): %v", err)
}
for {
var err error
runnerID, runnerSecret, managerSecret, err = client.Register(*token)
if err == nil {
log.Printf("Registered runner with ID: %d", runnerID)
// Only retry on connection errors or other retryable errors
retryCount++
if retryCount >= maxRetries {
log.Fatalf("Failed to register runner after %d attempts: %v", maxRetries, err)
}
// Always save secrets to file (secretsFile is now always set to a default if not provided)
secrets := SecretsFile{
RunnerID: runnerID,
RunnerSecret: runnerSecret,
ManagerSecret: managerSecret,
}
if err := saveSecrets(*secretsFile, secrets); err != nil {
log.Printf("Warning: Failed to save secrets to %s: %v", *secretsFile, err)
} else {
log.Printf("Saved secrets to %s", *secretsFile)
}
break
}
// Check if it's a token error (invalid/expired/used token) - shutdown immediately
errMsg := err.Error()
if strings.Contains(errMsg, "token error:") {
log.Fatalf("Registration failed (token error): %v", err)
}
// Only retry on connection errors or other retryable errors
retryCount++
if retryCount >= maxRetries {
log.Fatalf("Failed to register runner after %d attempts: %v", maxRetries, err)
}
log.Printf("Registration failed (attempt %d/%d): %v, retrying in %v", retryCount, maxRetries, err, backoff)
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
log.Printf("Registration failed (attempt %d/%d): %v, retrying in %v", retryCount, maxRetries, err, backoff)
time.Sleep(backoff)
backoff *= 2
if backoff > maxBackoff {
backoff = maxBackoff
}
}
@@ -188,8 +167,10 @@ func main() {
go func() {
sig := <-sigChan
log.Printf("Received signal: %v, killing all processes and shutting down...", sig)
log.Printf("Received signal: %v, killing all processes and cleaning up...", sig)
client.KillAllProcesses()
// Cleanup happens in defer, but also do it here for good measure
client.CleanupWorkspace()
os.Exit(0)
}()
@@ -197,28 +178,6 @@ func main() {
select {}
}
func loadSecrets(path string) (*SecretsFile, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var secrets SecretsFile
if err := json.Unmarshal(data, &secrets); err != nil {
return nil, err
}
return &secrets, nil
}
func saveSecrets(path string, secrets SecretsFile) error {
data, err := json.MarshalIndent(secrets, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0600)
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {