Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .env.quickstart-demo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# BeeBuzz quickstart screencast/demo environment.
#
# This file is intentionally separate from .env so the demo can run against
# disposable state without touching the normal local development database.

BEEBUZZ_ENV=development
BEEBUZZ_PRIVATE_BETA=true
BEEBUZZ_BOOTSTRAP_ADMIN_EMAIL=demo-quickstart@beebuzz.local
BEEBUZZ_PUSH_STUB=1

# Derived by .mise/setup-dev.sh when empty or not already a lancert.dev host.
BEEBUZZ_DOMAIN=example.com

# Keep demo state disposable and isolated from ./data.
BEEBUZZ_DB_DIR=/tmp/beebuzz-quickstart-demo/db
BEEBUZZ_ATTACHMENTS_DIR=/tmp/beebuzz-quickstart-demo/attachments

BEEBUZZ_MAILER_SENDER=noreply@beebuzz.local
BEEBUZZ_MAILER_REPLY_TO=support@beebuzz.local
BEEBUZZ_MAILER_SMTP_ADDRESS=localhost:1025
BEEBUZZ_MAILER_SMTP_USER=dev
BEEBUZZ_MAILER_SMTP_PASSWORD=dev

# Leave empty to let the demo runner generate temporary local VAPID keys.
BEEBUZZ_VAPID_PRIVATE_KEY=
BEEBUZZ_VAPID_PUBLIC_KEY=

VITE_BEEBUZZ_DEBUG=true
VITE_BEEBUZZ_DEPLOYMENT_MODE=self_hosted

# Demo runner inputs.
DEMO_EMAIL=demo-quickstart@beebuzz.local
DEMO_OUTPUT_DIR=/tmp/beebuzz-quickstart-demo/output
MAILPIT_API=http://localhost:8025/api/v1
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
/public
/tmp
.env
.DS_Store
.mise/certs/
AGENTS.local.md

152 changes: 90 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,135 @@
# BeeBuzz
<div align="center">
<img src="docs/assets/readme/beebuzz-logo.svg" alt="BeeBuzz logo" width="96">
</div>

Simple, private push notifications for servers, scripts, apps, and webhooks.
<h1 align="center">Private push notifications for your own devices</h1>

BeeBuzz is a focused Web Push delivery system for alerts that should reach your own
paired devices without becoming another chat surface. It supports both fast
server-trusted notifications and real end-to-end encrypted delivery, where message
content is encrypted before it reaches BeeBuzz and the server stores ciphertext
instead of plaintext.
<p align="center">
Send alerts from servers, scripts, apps, and webhooks without turning notification delivery into a chat system.
</p>

## Why BeeBuzz
BeeBuzz is a focused Web Push delivery system for machine-to-person alerts. It
supports two delivery modes:

- **Private alerting**: send high-signal machine-to-person notifications to paired devices.
- **Two delivery modes**: start quickly with trusted delivery, or use E2E mode when content privacy matters.
- **Real E2E push flow**: in E2E mode, the CLI encrypts locally for paired device keys and Hive decrypts locally on the receiving device.
- **Small auditable stack**: Go, SQLite, SvelteKit, Web Push, and a Hive PWA receiver.
- **Focused scope**: BeeBuzz is not a team chat, inbox, or general messaging platform.
- **Server-trusted notifications** for fast HTTP, webhook, and third-party service integrations.
- **End-to-end encrypted notifications** for senders you control, where the CLI encrypts locally and BeeBuzz stores only ciphertext.

## Architecture
BeeBuzz is built around paired personal devices, short-lived delivery state, and
a small auditable stack: Go, SQLite, SvelteKit, Web Push, and Hive, its PWA
receiver.

BeeBuzz is split into a few small pieces:
## Quickstart Demo

- **Server**: Go + SQLite API for accounts, topics, API tokens, devices, attachments, and Web Push dispatch.
- **Site**: SvelteKit web app for sign-in, device pairing, API tokens, webhook setup, and administration.
- **Hive**: PWA receiver that handles Web Push, stores pairing state locally, and decrypts E2E notifications on-device.
- **CLI**: sender for end-to-end encrypted notifications from terminals, scripts, and automation.
Hosted beta flow, shown on the real stack with Site and Hive side by side.

## Delivery Modes
<https://github.com/user-attachments/assets/edcd0981-119a-47e8-a947-91c70f888782>

### Server-trusted
<p align="center">
Sign in, create a pairing code, pair Hive, create a token, and deliver the first notification.
</p>

Use JSON or multipart requests when the sender trusts the BeeBuzz server with the
notification payload.
## BeeBuzz.app

```text
sender -> BeeBuzz API -> Web Push -> Hive
```
[BeeBuzz.app](https://beebuzz.app) is the hosted BeeBuzz SaaS.

BeeBuzz authenticates the API token, reads and validates the payload, optionally
handles an attachment, then sends a Web Push notification to subscribed devices.
This is the fastest path for tests, simple integrations, and webhooks.
Hosted access is currently a beta for approved users. During the beta you can:

### End-to-end encrypted
- sign in after approval
- pair a device with [Hive](https://hive.beebuzz.app)
- create API tokens scoped to topics
- send trusted-mode notifications over HTTP
- create webhook URLs for external services
- install the CLI and send end-to-end encrypted notifications

Use the CLI or an `application/octet-stream` request when notification content
should stay opaque to BeeBuzz.
Hosted access is free during beta. After beta, the hosted service is expected to
move to a single paid plan so the project can stay sustainable. Self-hosting
remains free, open source, and available under the AGPL license.

```text
CLI -> encrypt locally for paired devices -> BeeBuzz stores ciphertext -> Hive fetches and decrypts locally
```
Start here: [BeeBuzz quickstart](https://beebuzz.app/docs/quickstart).

## How It Works

BeeBuzz has two delivery modes because not every sender can encrypt before
calling the server.

<table>
<tr>
<td width="50%" valign="top">
<img src="docs/assets/readme/trusted-flow.svg" alt="Server-trusted BeeBuzz delivery flow">
</td>
<td width="50%" valign="top">
<img src="docs/assets/readme/e2e-flow.svg" alt="End-to-end encrypted BeeBuzz delivery flow">
</td>
</tr>
</table>

The CLI fetches paired device public keys, encrypts the notification locally with
age/X25519, and sends only ciphertext to BeeBuzz. The server stores the opaque
blob temporarily and pushes a small envelope containing the notification ID,
attachment token, and server acceptance time. Hive receives the envelope, fetches
the blob, and decrypts the final notification locally.
In both modes, Web Push transport is encrypted in transit between BeeBuzz and
the receiving browser. The difference is what the BeeBuzz server can read:
trusted mode gives BeeBuzz plaintext notification content; E2E mode gives
BeeBuzz only ciphertext plus routing and delivery metadata. In trusted mode,
BeeBuzz validates the payload, handles short-lived attachment data when present,
and dispatches the notification to paired devices.

## Try It

- Read the docs: <https://beebuzz.app/docs>
- Use the hosted BeeBuzz beta: <https://beebuzz.app/docs/quickstart>
- Run BeeBuzz locally for development: <https://beebuzz.app/docs/local-dev>
Use trusted mode when the sender can trust BeeBuzz with the notification content:

Install the CLI from a [GitHub release](https://github.com/lucor/beebuzz/releases) (no Go required) or with Go:
```bash
curl https://push.beebuzz.app \
-H "Authorization: Bearer $TOKEN" \
-F title="Hello from BeeBuzz" \
-F body="Trusted mode test"
```

Install the CLI from a [GitHub release](https://github.com/lucor/beebuzz/releases)
or with Go:

```bash
go install lucor.dev/beebuzz/cmd/beebuzz@latest
```

Send an encrypted notification after connecting the CLI:
Then connect the CLI and send an encrypted notification:

```bash
beebuzz connect
beebuzz send "Hello from BeeBuzz"
```

## Security Model
In E2E mode, the CLI fetches paired device public keys, encrypts the payload
locally with [age](https://age-encryption.org), and uploads ciphertext as
`application/octet-stream`. Hive fetches and decrypts the notification on the
receiving device.

In E2E mode:
## What's Inside

- BeeBuzz should not recover notification plaintext from stored blobs alone.
- BeeBuzz stores paired device public recipients, not device private identities.
- A database compromise alone should not reveal stored E2E message plaintext or device private keys.
- **Server**: Go + SQLite API for accounts, topics, API tokens, devices, attachments, and Web Push dispatch.
- **Site**: SvelteKit web app for sign-in, device pairing, API tokens, webhook setup, and administration.
- **Hive**: PWA receiver that handles Web Push, stores pairing state locally, and decrypts E2E notifications on-device.
- **CLI**: sender for end-to-end encrypted notifications from terminals, scripts, and automation.

E2E protects message content, not metadata. BeeBuzz still sees operational metadata
such as users, topics, device mappings, timestamps, delivery results, and whether
E2E mode was used. It also does not protect against a compromised endpoint or an
actively malicious server serving malicious client code or replacing recipient keys.
## Documentation

See [docs/E2E_ENCRYPTION.md](docs/E2E_ENCRYPTION.md) and
[docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) for the full model.
- [Quickstart](https://beebuzz.app/docs/quickstart)
- [Browser support](https://beebuzz.app/docs/browser-support)
- [Local development](https://beebuzz.app/docs/local-dev)
- [Webhooks](https://beebuzz.app/docs/webhooks)
- [E2E encryption model](docs/E2E_ENCRYPTION.md)
- [Threat model](docs/THREAT_MODEL.md)
- [OpenAPI contract](docs/openapi.yaml)
- [Development posts](https://lucor.dev/tags/beebuzz)

## Project Status

BeeBuzz is currently optimized for two workflows:

1. get approved for the hosted beta and send your first notification in seconds
1. get approved for the hosted beta and send your first notification quickly
2. run the stack locally with a fast development loop

Detailed production self-hosting docs will come later.

Hosted access is free during beta. After beta, the hosted service will move to a
single paid plan, priced to keep the project sustainable. Self-hosting remains
free, open source, and available under the AGPL license.

## License

BeeBuzz is licensed under the GNU Affero General Public License v3.0 only.
BeeBuzz is licensed under the GNU Affero General Public License v3.0 only. See
[LICENSE](LICENSE).

Third-party dependencies are tracked in the Go and frontend dependency manifests.
5 changes: 5 additions & 0 deletions cmd/beebuzz-server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func NewRouter(
webhookHandler *webhook.Handler,
attachmentHandler *attachment.Handler,
tokenHandler *token.Handler,
pushStubHandler http.HandlerFunc,
cfg *config.Config,
log *slog.Logger,
) http.Handler {
Expand Down Expand Up @@ -158,6 +159,10 @@ func NewRouter(
})
})

if pushStubHandler != nil {
r.Get("/_stub/push/next", pushStubHandler)
}

return r
}

Expand Down
14 changes: 14 additions & 0 deletions cmd/beebuzz-server/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type appServices struct {
topicSvc *topic.Service
userSvc *user.Service
webhookSvc *webhook.Service
pushStubBroker *notification.PushStubBroker
}

// runServe bootstraps and runs the HTTP server lifecycle.
Expand Down Expand Up @@ -171,6 +172,12 @@ func buildServices(db *sqlx.DB, cfg *config.Config, log *slog.Logger, m mailer.M
notifEventTracker := &notificationEventTrackerAdapter{eventSvc: eventSvc}
notifSvc := notification.NewService(notifDeviceAdapter, notifAttachmentAdapter, notifEventTracker, vapidKeys, cfg.VAPIDSubject, log)

var pushStubBroker *notification.PushStubBroker
if cfg.PushStub && cfg.Env != config.EnvProduction {
pushStubBroker = notification.NewPushStubBroker(log)
notifSvc.SetPushStubBroker(pushStubBroker)
}

systemNotifRepo := systemnotifications.NewRepository(db)
systemNotifTopics := &systemNotificationTopicProviderAdapter{topicSvc: topicSvc}
systemNotifDelivery := &systemNotificationDeliveryAdapter{notifSvc: notifSvc, log: log}
Expand All @@ -197,6 +204,7 @@ func buildServices(db *sqlx.DB, cfg *config.Config, log *slog.Logger, m mailer.M
topicSvc: topicSvc,
userSvc: userSvc,
webhookSvc: webhookSvc,
pushStubBroker: pushStubBroker,
}, nil
}

Expand Down Expand Up @@ -230,6 +238,11 @@ func buildHTTPHandler(services *appServices, cfg *config.Config, log *slog.Logge
attachmentHandler := attachment.NewHandler(services.attachmentSvc, log)
tokenHandler := token.NewHandler(services.tokenSvc, log)

var pushStubHandler http.HandlerFunc
if services.pushStubBroker != nil {
pushStubHandler = notification.NewPushStubHandler(services.pushStubBroker, log)
}

realIP := middleware.NewRealIP(cfg.ProxySubnet)
ipHasher := middleware.NewIPHasher(cfg.IPHashSalt)
requestID := middleware.NewRequestID(cfg.RequestIDHeader)
Expand All @@ -248,6 +261,7 @@ func buildHTTPHandler(services *appServices, cfg *config.Config, log *slog.Logge
webhookHandler,
attachmentHandler,
tokenHandler,
pushStubHandler,
cfg,
log,
)
Expand Down
11 changes: 11 additions & 0 deletions docs/MESSAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,16 @@ During push delivery:
- attachment retrieval is token-based and does not require a session cookie
- E2E mode depends on paired device age public keys

## Push-Stub Mode (Development Only)

When `BEEBUZZ_PUSH_STUB` is enabled (non-production environments only), the server does not dispatch notifications through real Web Push providers. Instead, it captures the raw push payload in an in-memory broker and exposes it via a long-polling endpoint:

- `GET /_stub/push/next`

This endpoint is restricted to loopback clients as defense-in-depth. It returns `200 OK` with a `PushStubEvent` when a payload is available, or `204 No Content` after a short timeout so clients can retry. Test drivers use this flow to inject pushes directly into the Hive service worker via Chrome DevTools Protocol, bypassing FCM/VAPID entirely.

**Never enable push-stub in production.**

## Agent Maintenance Rule

If you change any of the following, update the relevant section of this document in the same task:
Expand All @@ -287,3 +297,4 @@ If you change any of the following, update the relevant section of this document
- change webhook payload modes, dispatch, or priority handling
- change push delivery failure handling or subscription cleanup
- change the Hive service worker notification receive or decrypt flow
- change push-stub capture behavior, broker limits, or the `/_stub/push/next` endpoint
Loading