package main import ( "context" "database/sql" "flag" "log" "os" "path/filepath" "strconv" "time" pb "github.com/bergabruh/stats-gateway/gen/stats/v1" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" _ "modernc.org/sqlite" ) func main() { dbPath := flag.String("source-db", "/var/lib/librespeed-db/speedtest_telemetry.db", "Path to LibreSpeed SQLite (read-only)") statsAddr := flag.String("stats-addr", "stats.projectshitpost.fun:443", "stats-gateway gRPC address") cursorPath := flag.String("cursor", "/var/lib/speedtest-sync/cursor", "File holding the last synced source ID") tokenEnv := flag.String("token-env", "JWT_TOKEN", "Environment variable with JWT token") insecureFlag := flag.Bool("insecure", false, "Use insecure gRPC (testing only)") flag.Parse() token := os.Getenv(*tokenEnv) if token == "" { log.Fatalf("env %s is empty", *tokenEnv) } cursor := readCursor(*cursorPath) db, err := sql.Open("sqlite", "file:"+*dbPath+"?mode=ro") if err != nil { log.Fatalf("open source db: %v", err) } defer db.Close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() rows, err := extractRows(ctx, db, cursor) if err != nil { log.Fatalf("extract: %v", err) } if len(rows) == 0 { log.Printf("no new rows since id=%d", cursor) return } var dialOpt grpc.DialOption if *insecureFlag { dialOpt = grpc.WithTransportCredentials(insecure.NewCredentials()) } else { dialOpt = grpc.WithTransportCredentials(credentials.NewTLS(nil)) } conn, err := grpc.NewClient(*statsAddr, dialOpt) if err != nil { log.Fatalf("grpc dial: %v", err) } defer conn.Close() client := pb.NewSpeedStatusClient(conn) authCtx := metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token) sent := 0 for _, r := range rows { if err := sendRow(authCtx, client, r); err != nil { log.Printf("send id=%d failed: %v (stopping; cursor not advanced)", r.SourceID, err) break } cursor = r.SourceID sent++ } if err := writeCursor(*cursorPath, cursor); err != nil { log.Printf("WARN: cursor write failed: %v", err) } log.Printf("synced %d rows, cursor=%d", sent, cursor) } func readCursor(path string) int64 { b, err := os.ReadFile(path) if err != nil { return 0 } n, err := strconv.ParseInt(string(b), 10, 64) if err != nil { return 0 } return n } func writeCursor(path string, id int64) error { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } return os.WriteFile(path, []byte(strconv.FormatInt(id, 10)), 0o600) }