@@ -14,6 +14,7 @@ import (
1414 bk "github.com/tailscale/go-bluesky"
1515 "github.com/till/golangoss-bluesky/internal/bluesky"
1616 "github.com/till/golangoss-bluesky/internal/content"
17+ "github.com/till/golangoss-bluesky/internal/utils"
1718 "github.com/urfave/cli/v2"
1819)
1920
3435 githubToken string = ""
3536
3637 checkInterval time.Duration = 15 * time.Minute
38+ // How long to wait before retrying after a connection failure
39+ reconnectDelay time.Duration = 2 * time.Minute
3740)
3841
3942func init() {
@@ -44,6 +47,75 @@ func init() {
4447 ctx = context.Background()
4548}
4649
50+ // connectBluesky establishes a connection to Bluesky and logs in
51+ func connectBluesky(ctx context.Context) (*bk.Client, error) {
52+ client, err := bk.Dial(ctx, bk.ServerBskySocial)
53+ if err != nil {
54+ return nil, fmt.Errorf("failed to open connection: %v", err)
55+ }
56+
57+ if err := client.Login(ctx, blueskyHandle, blueskyAppKey); err != nil {
58+ client.Close()
59+ switch {
60+ case errors.Is(err, bk.ErrMasterCredentials):
61+ return nil, fmt.Errorf("you're not allowed to use your full-access credentials, please create an appkey")
62+ case errors.Is(err, bk.ErrLoginUnauthorized):
63+ return nil, fmt.Errorf("username of application password seems incorrect, please double check")
64+ default:
65+ return nil, fmt.Errorf("login failed: %v", err)
66+ }
67+ }
68+
69+ return client, nil
70+ }
71+
72+ // runWithReconnect attempts to run the bot with automatic reconnection on failure
73+ func runWithReconnect(ctx context.Context, mc *minio.Client) error {
74+ for {
75+ client, err := connectBluesky(ctx)
76+ if err != nil {
77+ slog.Error("failed to connect to Bluesky", "error", err)
78+ slog.Info("retrying connection", "delay", reconnectDelay)
79+ time.Sleep(reconnectDelay)
80+ continue
81+ }
82+
83+ c := bluesky.Client{
84+ Client: client,
85+ }
86+
87+ cacheClient := content.NewCacheClientS3(ctx, mc, cacheBucket)
88+
89+ // Initialize and start the cleanup handler
90+ cleanup := content.NewS3Cleanup(mc, cacheBucket)
91+ cleanup.Start(ctx)
92+ defer cleanup.Stop()
93+
94+ if err := content.Start(githubToken, cacheClient); err != nil {
95+ slog.Error("failed to start service", "error", err)
96+ client.Close()
97+ time.Sleep(reconnectDelay)
98+ continue
99+ }
100+
101+ // Run the main loop
102+ for {
103+ slog.DebugContext(ctx, "checking...")
104+ if err := content.Do(ctx, c); err != nil {
105+ if !errors.Is(err, content.ErrCouldNotContent) {
106+ slog.Error("error during content check", "error", err)
107+ client.Close()
108+ time.Sleep(reconnectDelay)
109+ break
110+ }
111+ slog.DebugContext(ctx, "backing off...")
112+ }
113+
114+ time.Sleep(checkInterval)
115+ }
116+ }
117+ }
118+
47119func main() {
48120 bot := cli.App{
49121 Name: "golangoss-bluesky",
@@ -88,25 +160,7 @@ func main() {
88160 },
89161
90162 Action: func(cCtx *cli.Context) error {
91- // FIXME: run this in a control loop; or we crash the app
92- client, err := bk.Dial(ctx, bk.ServerBskySocial)
93- if err != nil {
94- return fmt.Errorf("failed to open connection: %v", err)
95- }
96- defer client.Close()
97-
98- if err := client.Login(ctx, blueskyHandle, blueskyAppKey); err != nil {
99- switch {
100- case errors.Is(err, bk.ErrMasterCredentials):
101- return fmt.Errorf("you're not allowed to use your full-access credentials, please create an appkey")
102- case errors.Is(err, bk.ErrLoginUnauthorized):
103- return fmt.Errorf("username of application password seems incorrect, please double check")
104- default:
105- return fmt.Errorf("something else went wrong, please look at the returned error")
106- }
107- }
108-
109- // init s3 client
163+ // Initialize S3 client
110164 mc, err := minio.New(awsEndpoint, &minio.Options{
111165 Creds: credentials.NewStaticV4(awsAccessKeyId, awsSecretKey, ""),
112166 Secure: true,
@@ -115,47 +169,17 @@ func main() {
115169 return fmt.Errorf("failed to initialize minio client: %v", err)
116170 }
117171
118- // ensure the bucket exists
172+ // Ensure the bucket exists
119173 if err := mc.MakeBucket(ctx, cacheBucket, minio.MakeBucketOptions{}); err != nil {
120174 return fmt.Errorf("failed to create bucket: %v", err)
121175 }
122176
123- c := bluesky.Client{
124- Client: client,
125- }
126-
127- cacheClient := content.NewCacheClientS3(ctx, mc, cacheBucket)
128-
129- // Initialize and start the cleanup handler
130- cleanup := content.NewS3Cleanup(ctx, mc, cacheBucket)
131- cleanup.Start()
132- defer cleanup.Stop()
133-
134- if err := content.Start(githubToken, cacheClient); err != nil {
135- return fmt.Errorf("failed to start service: %v", err)
136- }
137-
138- var runErr error
139-
140- for {
141- slog.DebugContext(ctx, "checking...")
142- if err := content.Do(ctx, c); err != nil {
143- if !errors.Is(err, content.ErrCouldNotContent) {
144- runErr = err
145- break
146- }
147- slog.DebugContext(ctx, "backing off...")
148- }
149-
150- time.Sleep(checkInterval)
151- }
152- return runErr
177+ return runWithReconnect(ctx, mc)
153178 },
154179 }
155180
156181 if err := bot.Run(os.Args); err != nil {
157- slog.ErrorContext (ctx, err.Error() )
182+ utils.LogErrorWithContext (ctx, err)
158183 os.Exit(1)
159184 }
160-
161185}
0 commit comments