additional commands for litestream, one to create a bucket and the other to show the backups done by litestream in the bucket.
+147
-6
Diff
round #1
+3
.envrc-dist
+3
.envrc-dist
+4
.gitignore
+4
.gitignore
+12
-1
Makefile
+12
-1
Makefile
···
1
1
DB ?= firehose.db
2
2
LISTEN ?= :8282
3
3
4
-
.PHONY: run-dev build generate vet tidy clean
4
+
.PHONY: run-dev run-dev-litestream build generate vet tidy clean
5
5
6
6
run-dev: generate
7
7
go run ./cmd/firehose --db $(DB) --listen $(LISTEN) --debug
8
8
9
+
# Same as run-dev but with Litestream replication. Expects LITESTREAM_URL
10
+
# and AWS_* in the environment (export them in your shell or a wrapper).
11
+
run-dev-litestream: guard-LITESTREAM_URL guard-AWS_ACCESS_KEY_ID guard-AWS_SECRET_ACCESS_KEY generate
12
+
go run ./cmd/firehose --db $(DB) --listen $(LISTEN) --debug
13
+
14
+
guard-%:
15
+
@ if [ "${${*}}" = "" ]; then \
16
+
echo "Environment variable $* not set"; \
17
+
exit 1; \
18
+
fi
19
+
9
20
build: generate
10
21
go build -o bin/firehose ./cmd/firehose
11
22
+98
cmd/firehose/main.go
+98
cmd/firehose/main.go
···
3
3
import (
4
4
"context"
5
5
"errors"
6
+
"fmt"
6
7
"log/slog"
7
8
"net/http"
8
9
"net/mail"
10
+
"net/url"
9
11
"os"
10
12
"os/signal"
11
13
"syscall"
···
16
18
"tangled.sh/chown.de/tangled-repo-firehose/store"
17
19
"tangled.sh/chown.de/tangled-repo-firehose/web"
18
20
21
+
"github.com/aws/aws-sdk-go-v2/aws"
22
+
awsconfig "github.com/aws/aws-sdk-go-v2/config"
23
+
"github.com/aws/aws-sdk-go-v2/credentials"
24
+
"github.com/aws/aws-sdk-go-v2/service/s3"
25
+
"github.com/aws/smithy-go"
19
26
"github.com/bluesky-social/indigo/atproto/identity"
20
27
"github.com/urfave/cli/v3"
21
28
)
···
63
70
}, litestreamFlags(false)...),
64
71
Commands: []*cli.Command{
65
72
restoreCommand(),
73
+
setupCommand(),
74
+
backupsCommand(),
66
75
},
67
76
Before: func(ctx context.Context, c *cli.Command) (context.Context, error) {
68
77
level := slog.LevelInfo
···
70
79
level = slog.LevelDebug
71
80
}
72
81
logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level}))
82
+
slog.SetDefault(logger)
73
83
return ctx, nil
74
84
},
75
85
Action: func(ctx context.Context, c *cli.Command) error {
···
201
211
}
202
212
}
203
213
214
+
// setupCommand creates the S3 bucket configured for Litestream replication.
215
+
// Litestream itself never calls CreateBucket, so a fresh deployment needs the
216
+
// bucket provisioned out-of-band — this is the out-of-band step.
217
+
func setupCommand() *cli.Command {
218
+
return &cli.Command{
219
+
Name: "setup",
220
+
Usage: "Create the S3 bucket configured for Litestream replication",
221
+
Flags: litestreamFlags(true),
222
+
Action: func(ctx context.Context, c *cli.Command) error {
223
+
s3c, bucket, err := s3Client(ctx, c)
224
+
if err != nil {
225
+
return err
226
+
}
227
+
228
+
_, err = s3c.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)})
229
+
if err != nil {
230
+
var ae smithy.APIError
231
+
if errors.As(err, &ae) && ae.ErrorCode() == "BucketAlreadyOwnedByYou" {
232
+
logger.Info("bucket already exists", "bucket", bucket)
233
+
return nil
234
+
}
235
+
return fmt.Errorf("create bucket: %w", err)
236
+
}
237
+
logger.Info("created bucket", "bucket", bucket)
238
+
return nil
239
+
},
240
+
}
241
+
}
242
+
243
+
func backupsCommand() *cli.Command {
244
+
return &cli.Command{
245
+
Name: "backups",
246
+
Flags: litestreamFlags(true),
247
+
Action: func(ctx context.Context, c *cli.Command) error {
248
+
s3c, bucket, err := s3Client(ctx, c)
249
+
if err != nil {
250
+
return err
251
+
}
252
+
253
+
output, err := s3c.ListObjects(ctx, &s3.ListObjectsInput{
254
+
Bucket: &bucket,
255
+
})
256
+
if err != nil {
257
+
return err
258
+
}
259
+
260
+
for _, object := range output.Contents {
261
+
fmt.Printf("%s: %.2f KB\n", *object.Key, float64(aws.ToInt64(object.Size))/1024)
262
+
}
263
+
return nil
264
+
},
265
+
}
266
+
}
267
+
268
+
func s3Client(ctx context.Context, c *cli.Command) (*s3.Client, string, error) {
269
+
u, err := url.Parse(c.String("litestream-url"))
270
+
if err != nil {
271
+
return nil, "", fmt.Errorf("parse litestream-url: %w", err)
272
+
}
273
+
bucket := u.Host
274
+
q := u.Query()
275
+
region := q.Get("region")
276
+
if region == "" {
277
+
region = "us-east-1"
278
+
}
279
+
endpoint := q.Get("endpoint")
280
+
forcePathStyle := q.Get("force-path-style") == "true"
281
+
282
+
cfg, err := awsconfig.LoadDefaultConfig(ctx,
283
+
awsconfig.WithRegion(region),
284
+
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
285
+
c.String("s3-access-key-id"), c.String("s3-secret-access-key"), "",
286
+
)),
287
+
)
288
+
if err != nil {
289
+
return nil, "", fmt.Errorf("load aws config: %w", err)
290
+
}
291
+
292
+
s3c := s3.NewFromConfig(cfg, func(o *s3.Options) {
293
+
if endpoint != "" {
294
+
o.BaseEndpoint = aws.String(endpoint)
295
+
}
296
+
o.UsePathStyle = forcePathStyle
297
+
})
298
+
299
+
return s3c, bucket, nil
300
+
}
301
+
204
302
// litestreamFlags returns --litestream-url plus the S3 credential flags.
205
303
// The URL encodes bucket/path/region/endpoint/force-path-style via standard
206
304
// query params
+5
-5
go.mod
+5
-5
go.mod
···
4
4
5
5
require (
6
6
entgo.io/ent v0.14.6
7
+
github.com/aws/aws-sdk-go-v2 v1.41.0
8
+
github.com/aws/aws-sdk-go-v2/config v1.32.6
9
+
github.com/aws/aws-sdk-go-v2/credentials v1.19.6
10
+
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0
11
+
github.com/aws/smithy-go v1.24.0
7
12
github.com/benbjohnson/litestream v0.5.11
8
13
github.com/bluesky-social/indigo v0.0.0-20260428083920-ce62b8fce9e0
9
14
github.com/bluesky-social/jetstream v0.0.0-20260415170838-8a65de4eda28
···
15
20
ariga.io/atlas v0.36.2-0.20250730182955-2c6300d0a3e1 // indirect
16
21
github.com/agext/levenshtein v1.2.3 // indirect
17
22
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
18
-
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
19
23
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
20
-
github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect
21
-
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect
22
24
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
23
25
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.18 // indirect
24
26
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
···
29
31
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect
30
32
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
31
33
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
32
-
github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect
33
34
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
34
35
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
35
36
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
36
37
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
37
-
github.com/aws/smithy-go v1.24.0 // indirect
38
38
github.com/beorn7/perks v1.0.1 // indirect
39
39
github.com/bmatcuk/doublestar v1.3.4 // indirect
40
40
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+24
go.sum
+24
go.sum
···
82
82
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
83
83
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
84
84
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
85
+
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
86
+
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
87
+
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
88
+
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
89
+
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
90
+
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
85
91
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
86
92
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
87
93
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
···
90
96
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
91
97
github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg=
92
98
github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw=
99
+
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
100
+
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
93
101
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
94
102
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
95
103
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
···
204
212
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
205
213
github.com/markusmobius/go-dateparser v1.2.4 h1:2e8XJozaERVxGwsRg72coi51L2aiYqE2gukkdLc85ck=
206
214
github.com/markusmobius/go-dateparser v1.2.4/go.mod h1:CBAUADJuMNhJpyM6IYaWAoFhtKaqnUcznY2cL7gNugY=
215
+
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
216
+
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
207
217
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
208
218
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
209
219
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
220
+
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
221
+
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
210
222
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
211
223
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
212
224
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
···
234
246
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
235
247
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
236
248
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
249
+
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
250
+
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
251
+
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
252
+
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
253
+
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM=
254
+
github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
255
+
github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA=
256
+
github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM=
237
257
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
238
258
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
239
259
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
···
272
292
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
273
293
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
274
294
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
295
+
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
296
+
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
297
+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
298
+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
275
299
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
276
300
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
277
301
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
History
2 rounds
0 comments
2 commits
expand
collapse
feat: setup/backups command
additional commands for litestream, one to create a bucket and
the other to show the backups done by litestream in the bucket.
fix(ci): add gcc for --race
merge conflicts detected
expand
collapse
expand
collapse
- .gitignore:3
- Makefile:1
- cmd/firehose/main.go:3
- go.mod:4
- go.sum:82