package main import ( "context" "errors" "flag" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "git.jdbnet.co.uk/jamie/tx-sync/internal/config" "git.jdbnet.co.uk/jamie/tx-sync/internal/sync" ) /* tx-sync uploads txAdmin playersDB.json to an HTTP(S) endpoint on a schedule. Expected server contract (for your dashboard ingest implementation): - Method: POST - Header: X-API-Key: - Header: Content-Type: application/json - Body: raw bytes of playersDB.json - URL: full URL from config (no fixed path in this client) */ func main() { log.SetFlags(log.LstdFlags | log.Lmsgprefix) log.SetPrefix("tx-sync: ") defaultPath, err := config.DefaultPath() if err != nil { log.Fatalf("resolve config path: %v", err) } configPath := flag.String("config", defaultPath, "path to config.json") setup := flag.Bool("setup", false, "run interactive configuration wizard and exit") flag.Parse() if *setup { if err := config.RunWizard(*configPath); err != nil { log.Fatal(err) } os.Exit(0) } if _, statErr := os.Stat(*configPath); statErr != nil { if errors.Is(statErr, os.ErrNotExist) { if err := config.RunWizard(*configPath); err != nil { log.Fatal(err) } os.Exit(0) } log.Fatalf("config file: %v", statErr) } cfg, err := config.Load(*configPath) if err != nil { log.Fatalf("load config: %v", err) } if err := cfg.Validate(); err != nil { log.Fatalf("invalid config: %v (run with -setup to reconfigure)", err) } interval, err := cfg.IntervalDuration() if err != nil { log.Fatalf("sync interval: %v", err) } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() client := sync.NewHTTPClient() if err := pushOnce(ctx, client, cfg); err != nil { log.Printf("upload: %v", err) } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): log.Println("shutting down") return case <-ticker.C: if err := pushOnce(ctx, client, cfg); err != nil { log.Printf("upload: %v", err) } } } } func pushOnce(ctx context.Context, client *http.Client, cfg *config.Config) error { body, err := sync.ReadPlayersDB(cfg.PlayersDBPath) if err != nil { return fmt.Errorf("read %q: %w", cfg.PlayersDBPath, err) } if err := sync.Upload(ctx, client, cfg.Endpoint, cfg.APIKey, body); err != nil { return err } log.Printf("upload ok (%d bytes)", len(body)) return nil }