// Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 // Package main contains the one-shot Magistrala Atom bootstrap command. package main import ( "context" "flag" "fmt" "log" "os" "strconv" "strings" "time" "github.com/absmach/magistrala/pkg/atom" ) const ( defaultRetries = 30 defaultRetryInterval = 2 * time.Second defaultTimeout = 30 * time.Second ) func main() { log.SetFlags(log.LstdFlags | log.Lmicroseconds) cfg := atom.LoadConfig() if cfg.URL == "" { log.Fatal("ATOM_URL is required") } client := atom.NewClient(cfg) if len(os.Args) > 1 { switch os.Args[1] { case "bootstrap-actions": runBootstrapActions(client) return case "provision-tokens": runProvisionTokens(client, os.Args[2:]) return default: log.Fatalf("unknown command %q", os.Args[1]) } } runBootstrapActions(client) } func runBootstrapActions(client *atom.Client) { retries := envInt("MG_ATOM_BOOTSTRAP_RETRIES", defaultRetries) retryInterval := envDuration("MG_ATOM_BOOTSTRAP_RETRY_INTERVAL", defaultRetryInterval) timeout := envDuration("MG_ATOM_BOOTSTRAP_TIMEOUT", defaultTimeout) var lastErr error for attempt := 1; attempt <= retries; attempt++ { ctx, cancel := context.WithTimeout(context.Background(), timeout) err := atom.BootstrapMagistralaActions(ctx, client) cancel() if err == nil { log.Printf("Magistrala Atom action bootstrap completed") return } lastErr = err if attempt < retries { log.Printf("Magistrala Atom action bootstrap attempt %d/%d failed: %v; retrying in %s", attempt, retries, err, retryInterval) time.Sleep(retryInterval) } } log.Fatalf("Magistrala Atom action bootstrap failed after %d attempts: %v", retries, lastErr) } func runProvisionTokens(client *atom.Client, args []string) { fs := flag.NewFlagSet("provision-tokens", flag.ExitOnError) output := fs.String("output", envString("MG_ATOM_TOKENS_OUTPUT", "docker/.env.tokens"), "path to write generated token env file") rotate := fs.String("rotate", "", "rotate one token by name/env var, or all") entityID := fs.String("entity-id", envString("ATOM_SERVICE_ENTITY_ID", atom.DefaultServiceEntityID), "Atom service entity ID to receive access tokens") if err := fs.Parse(args); err != nil { log.Fatal(err) } retries := envInt("MG_ATOM_BOOTSTRAP_RETRIES", defaultRetries) retryInterval := envDuration("MG_ATOM_BOOTSTRAP_RETRY_INTERVAL", defaultRetryInterval) timeout := envDuration("MG_ATOM_BOOTSTRAP_TIMEOUT", defaultTimeout) var lastErr error for attempt := 1; attempt <= retries; attempt++ { ctx, cancel := context.WithTimeout(context.Background(), timeout) result, err := atom.ProvisionServiceTokens(ctx, client, atom.TokenProvisionOptions{ OutputPath: *output, ServiceEntityID: *entityID, Rotate: *rotate, }) cancel() if err == nil { log.Printf("Magistrala Atom token provisioning completed: output=%s preserved=%d created=%d rotated=%d", result.OutputPath, len(result.Preserved), len(result.Created), len(result.Rotated)) return } lastErr = err if attempt < retries { log.Printf("Magistrala Atom token provisioning attempt %d/%d failed: %v; retrying in %s", attempt, retries, err, retryInterval) time.Sleep(retryInterval) } } log.Fatalf("Magistrala Atom token provisioning failed after %d attempts: %v", retries, lastErr) } func envInt(key string, fallback int) int { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" { return fallback } value, err := strconv.Atoi(raw) if err != nil || value <= 0 { return fallback } return value } func envDuration(key string, fallback time.Duration) time.Duration { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" { return fallback } value, err := time.ParseDuration(raw) if err == nil && value > 0 { return value } seconds, err := strconv.Atoi(raw) if err == nil && seconds > 0 { return time.Duration(seconds) * time.Second } fmt.Fprintf(os.Stderr, "invalid %s=%q, using %s\n", key, raw, fallback) return fallback } func envString(key, fallback string) string { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback } return value }