From 69cc80ec1e86917095f19cca811470782739c6ea Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 22 Mar 2026 20:52:25 -0400 Subject: [PATCH] Add comments about AWS S3 --- attachment/store.go | 5 +++-- docs/config.md | 28 ++++++++++++++++++++++++++++ docs/releases.md | 2 +- s3/client.go | 32 +++++++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/attachment/store.go b/attachment/store.go index f250d106..d70ea2ab 100644 --- a/attachment/store.go +++ b/attachment/store.go @@ -157,7 +157,7 @@ func (c *Store) sync() error { // than the grace period to account for races, and skipping objects with invalid IDs. cutoff := time.Now().Add(-orphanGracePeriod) var orphanIDs []string - var size int64 + var count, size int64 sizes := make(map[string]int64, len(remoteObjects)) for _, obj := range remoteObjects { if !fileIDRegex.MatchString(obj.ID) { @@ -166,11 +166,12 @@ func (c *Store) sync() error { if _, ok := localIDMap[obj.ID]; !ok && obj.LastModified.Before(cutoff) { orphanIDs = append(orphanIDs, obj.ID) } else { + count++ size += obj.Size sizes[obj.ID] = obj.Size } } - log.Tag(tagStore).Debug("Attachment store updated: %d attachment(s), %s", len(localIDs), util.FormatSizeHuman(size)) + log.Tag(tagStore).Debug("Attachment store updated: %d attachment(s), %s", count, util.FormatSizeHuman(size)) c.mu.Lock() c.size = size c.sizes = sizes diff --git a/docs/config.md b/docs/config.md index b6ae3009..3456b661 100644 --- a/docs/config.md +++ b/docs/config.md @@ -565,6 +565,34 @@ When `endpoint` is specified, path-style addressing is enabled automatically (us Note that the access key and secret key may have to be URL encoded. For instance, a secret key `YmxhY+mxhYmxhC` (note the `+`) should be encoded as `YmxhY%2BmxhYmxhC` (note the `%2B`), so the URL would be `s3://ACCESS_KEY:YmxhY%2BmxhYmxhC@my-bucket/attachments...`. +For **AWS S3**, the IAM user needs the following permissions on the bucket: + +``` json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket", + "s3:ListBucketMultipartUploads" + ], + "Resource": "arn:aws:s3:::BUCKET_NAME" + }, + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:AbortMultipartUpload" + ], + "Resource": "arn:aws:s3:::BUCKET_NAME/*" + } + ] +} +``` + ## Access control By default, the ntfy server is open for everyone, meaning **everyone can read and write to any topic** (this is how ntfy.sh is configured). To restrict access to your own server, you can optionally configure authentication and authorization. diff --git a/docs/releases.md b/docs/releases.md index afdb8065..975c7536 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1802,7 +1802,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release **Features:** -* Add S3-compatible object storage as an alternative [attachment](config.md#attachments) backend via `attachment-cache-dir` config option +* Add S3-compatible object storage as an alternative [attachment store](config.md#attachments) via `attachment-cache-dir` config option **Bug fixes + maintenance:** diff --git a/s3/client.go b/s3/client.go index e06ff5c9..8e84bbc5 100644 --- a/s3/client.go +++ b/s3/client.go @@ -25,6 +25,32 @@ const ( // and ListObjectsV2 operations using AWS Signature V4 signing. The bucket and optional key prefix // are fixed at construction time. All operations target the same bucket and prefix. // +// The following IAM policy is required for AWS S3: +// +// { +// "Version": "2012-10-17", +// "Statement": [ +// { +// "Effect": "Allow", +// "Action": [ +// "s3:ListBucket", +// "s3:ListBucketMultipartUploads" +// ], +// "Resource": "arn:aws:s3:::BUCKET_NAME" +// }, +// { +// "Effect": "Allow", +// "Action": [ +// "s3:GetObject", +// "s3:PutObject", +// "s3:DeleteObject", +// "s3:AbortMultipartUpload" +// ], +// "Resource": "arn:aws:s3:::BUCKET_NAME/*" +// } +// ] +// } +// // Fields must not be modified after the Client is passed to any method or goroutine. type Client struct { config *Config @@ -149,7 +175,11 @@ func (c *Client) ListObjectsV2(ctx context.Context) ([]*Object, error) { // listObjectsV2 performs a single ListObjectsV2 request using the client's configured prefix. func (c *Client) listObjectsV2(ctx context.Context, continuationToken string) (*listObjectsV2Result, error) { - log.Tag(tagS3Client).Debug("Listing remote objects with continuation token '%s'", continuationToken) + if continuationToken == "" { + log.Tag(tagS3Client).Debug("Listing remote objects") + } else { + log.Tag(tagS3Client).Debug("Listing remote objects, continuing with token '%s'", continuationToken) + } query := url.Values{"list-type": {"2"}} if prefix := c.config.ListPrefix(); prefix != "" { query.Set("prefix", prefix)