this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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.

+146 -6
+3
.envrc-dist
··· 1 + export LITESTREAM_URL="s3://bucket/prefix?endpoint=https://server&force-path-style=true&region=us-east-1" 2 + export AWS_ACCESS_KEY_ID="XXX" 3 + export AWS_SECRET_ACCESS_KEY="XXX"
+4
.gitignore
··· 3 3 /firehose.db-shm 4 4 /firehose.db-wal 5 5 /firehose.db.bak.* 6 + /.firehose.db-litestream 7 + 8 + # config 9 + /.envrc 6 10 7 11 # Local backup artifacts from scripts/migrate.sh 8 12 /*.bak.*
+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 + 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 8 19 9 20 build: generate 10 21 go build -o bin/firehose ./cmd/firehose
+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 { ··· 199 209 ) 200 210 }, 201 211 } 212 + } 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 202 300 } 203 301 204 302 // litestreamFlags returns --litestream-url plus the S3 credential flags.
+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
··· 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=