go-pulse

module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: Apache-2.0

README

Pulse

A personal, durable event bus on NATS JetStream.
Every tool, bot and agent publishes and consumes through it, and any new tool can replay years of history from a timestamp.

Documentation · Try it locally · Clients & SDKs · Security model

Why

Home automation, GitHub, payment providers, scripts and AI agents all produce events. Usually each pair of tools gets its own glue: a webhook here, a cron job there, a table nobody remembers. When a new tool needs last month's events, they are gone.

Pulse is one place for all of it. Events go into a single JetStream stream that is kept for as long as the disk allows (years at personal volumes). Each tool gets its own credentials that allow only the subjects it needs. A new tool subscribes with --since 2025-01-01 and gets everything from that day on, then live events, in order.

How it works

flowchart LR
    T["Tools, bots, agents<br/>(own .creds)"] -- "NATS TLS :4222" --> N["NATS JetStream<br/>EVENTS · KV · objects"]
    W["Webhook senders,<br/>browsers, HTTP tools"] -- "HTTPS" --> C["Cloudflare"] -- ":443, mTLS" --> P["pulsed<br/>API · dashboard · bans · rollups"]
    P <--> N
    O["Operators<br/>(password + TOTP, or SSO)"] -- "Cloudflare or ssh -L" --> P
    P --> X["S3 backups · Telegram/Discord<br/>· outbound webhooks"]
  1. pulsed init sets NATS up in JWT operator mode: its own operator, accounts and signing keys, sealed with a master key.
  2. In the dashboard you create a client per tool with grants (publish:evt.home.>, subscribe:evt.github.>). Its .creds file is shown once. A new client can do nothing until you add grants.
  3. Tools publish events like evt.home.door.opened with a small CloudEvents-style envelope, using the Go, Python or TypeScript SDK, the pulse CLI, plain nats, the HTTP API, or a signed webhook.
  4. Anyone with a subscribe grant replays from a time or sequence and then follows live events. Durable consumers keep their position across restarts.

Key features

  • History you can replay. One EVENTS stream on evt.>, kept until it reaches its size limit (60% of the disk in the reference deployment), with S2 compression and 2-minute de-duplication on the event ID.
  • Least-privilege clients. Every client gets its own NATS user with permissions built from its grants and its own inbox. Rotation and revocation take effect within seconds and disconnect live sessions.
  • HTTP when NATS is not an option. Tokens limited to subjects and source networks for publish, batch publish, request/reply, replay and an SSE live tail. Browsers can use NATS over WebSocket at /ws.
  • Webhooks in and out. Inbound GitHub, Stripe, HMAC and shared-secret verifiers publish to a subject. Outbound hooks deliver events in order, signed and retried, with a delivery log and an SSRF guard.
  • Users, roles and single sign-on. Dashboard users sign in with a password and TOTP or through any OpenID Connect provider (PKCE, group → role mappings, just-in-time users, step-up), with administrator, operator, auditor and viewer roles, recovery codes, invitations, break-glass recovery and personal access tokens for the CLI and automation. The dashboard stays behind an SSH tunnel or, if you choose, is served behind Cloudflare (on its own hostname or the API hostname).
  • A dashboard to see it all. Event explorer with filters, live tail, subject tree, throughput and a disk forecast, streams and consumers, KV and object browser, clients, tokens, webhooks, security, alerts, backups and audit.
  • Configuration as code. A Terraform provider manages clients, tokens, streams, buckets, webhooks, alert rules, users and single sign-on through the admin API, from the SSH tunnel or from CI with a token limited to the runner's networks.
  • Edge security. pulsed terminates TLS itself behind Cloudflare with Authenticated Origin Pulls. Raw NATS gets a Let's Encrypt certificate through DNS-01. Repeated authentication failures from NATS, HTTP or webhooks become bans, enforced in nftables, over HTTP and optionally in a Cloudflare IP list.
  • Backups that keep timestamps. Nightly, age-encrypted, deduplicated backups to any S3-compatible store, kept on a 7/4/12 schedule. Restores keep sequence numbers and stored times, so replay by time works the same afterwards.
  • Operable on a small box. Designed for one VPS with 2 vCPU and 2 GB of RAM. One distroless image for pulsed plus the official NATS image, Prometheus metrics, alert rules with Telegram and Discord notifications, and an audit log of every change.

Pulse is single-node: there is no clustering or high availability. It is built for one owner and the people they invite; there is no multi-tenant isolation between users or tools beyond roles and grants.

Screenshots

Event explorer with filters and a list of events
Event explorer
One event with its envelope headers and JSON payload
One event, headers and payload
Clients with their grants and live connections
Clients and their grants
A new client's credentials shown once, with connection snippets
Credentials shown once, with snippets
Outbound webhooks with filters, destinations and pending events
Outbound webhooks
An outbound hook's delivery log with delivered and retried attempts
Signed, retried deliveries with a log
Security page with automatic and manual bans and recent authentication failures
Automatic bans and authentication failures
Alerts with a firing rule, Telegram and Discord notifiers and the rule list
Alerts to Telegram and Discord
Backups page with verified nightly backups
Verified, encrypted backups
The restore dialog listing the streams in a backup
Restore selected streams
Sign-in page with password and single sign-on
Sign in with a password and TOTP, or SSO
Users page with roles and sign-in methods
Users and roles

Quick start

Try it on your machine

Needs Docker with Compose v2, Go (the version in go.mod), make, openssl, curl and jq.

git clone https://github.com/aurimasniekis/go-pulse.git && cd go-pulse
make dev-up                     # init, NATS, pulsed, Alloy, SeaweedFS; dashboard on http://127.0.0.1:8080
make dev-admin                  # one-time setup link: open it, choose a password, sign in, enroll TOTP
go install github.com/aurimasniekis/go-pulse/cmd/pulse@latest

# In the dashboard: Account → Personal access tokens → create one (role administrator); it is shown once.
export PULSE_ADMIN_TOKEN=pulse_pat_…
# The dev NATS listener serves a certificate from Pulse's internal CA.
curl -fsS -H "Authorization: Bearer $PULSE_ADMIN_TOKEN" http://127.0.0.1:8080/api/admin/v1/system/info \
  | jq -r .connect.ca_cert > pulse-ca.pem
pulse clients create demo --grant 'publish:evt.demo.>' --grant 'subscribe:evt.demo.>'   # writes demo.creds once
pulse context add dev --url tls://127.0.0.1:4222 --creds ./demo.creds --ca ./pulse-ca.pem

pulse pub evt.demo.thing.created '{"hello":"world"}' -t demo.thing.created.v1
pulse sub 'evt.demo.>' --since 1h

The local guide explains each step.

Deploy

The reference deployment is one Debian VPS behind Cloudflare, with deploy/examples/compose.yaml: init, nats, pulsed, banagent and alloy. Read Deploy on a VPS and the configuration reference.

Images are on Docker Hub as aurimasniekis/pulsed (mirrored to ghcr.io/aurimasniekis/pulsed) for linux/amd64 and linux/arm64, with an SBOM and build provenance. Pin the image by digest and verify it first:

gh attestation verify oci://docker.io/aurimasniekis/pulsed:0.1.5 --repo aurimasniekis/go-pulse

Every release is listed in CHANGELOG.md.

Clients

c, _ := pulse.Connect("tls://r.events.example.com:4222", pulse.WithCredsFile("door-bot.creds"), pulse.WithSource("door-bot"))
c.Publish(ctx, "evt.home.door.opened", "home.door.opened.v1", map[string]any{"door": "front"})
c.Subscribe(ctx, "evt.home.>", pulse.Handle(func(ctx context.Context, e pulse.Event, d Door) error {
	return nil
}), pulse.Since(time.Now().Add(-24*time.Hour)))
c = await connect("tls://r.events.example.com:4222", creds="door-bot.creds", source="door-bot")
async for e in c.subscribe("evt.home.>", since="24h"):
    print(e.sequence, e.subject, e.json())
curl -X POST https://events.example.com/api/v1/publish/evt.home.door.opened \
  -H "Authorization: Bearer $PULSE_TOKEN" -H 'Ce-Type: home.door.opened.v1' \
  -H 'Content-Type: application/json' -d '{"door":"front"}'
Client Install
Go SDK go get github.com/aurimasniekis/go-pulse/pkg/pulse
Python SDK (asyncio) pip install "git+https://github.com/aurimasniekis/go-pulse#subdirectory=sdk/python"
TypeScript SDK (node and browser) pnpm add "github:aurimasniekis/go-pulse#path:/sdk/ts"
pulse CLI go install github.com/aurimasniekis/go-pulse/cmd/pulse@latest

Go, Python, node and the browser read each other's events the same way: the SDKs share one set of test vectors, an interop test (make test-sdk-interop) checks Go, Python and node against a real server, and a Playwright test checks the browser bundle over /ws. See Clients & SDKs.

Documentation

Everything is on the documentation website:

  • Get started: concepts, trying it locally, deploying on a VPS, connecting your first tool and a tour of the dashboard.
  • Clients & SDKs: subjects and the envelope, the Go, Python and TypeScript SDKs, the CLI, the HTTP API and webhooks.
  • Operator guides: users and single sign-on, clients and grants, streams, Cloudflare and TLS, firewall and bans, keys, backups, monitoring, upgrades and Terraform.
  • Reference: configuration, server commands, the admin API and metrics.
  • Security model: what Pulse guarantees and what it does not.

Security

Read the security model before deploying: it lists what Pulse defends against and the risks it accepts. Report vulnerabilities privately; see SECURITY.md.

Contributing

See CONTRIBUTING.md.

License

Apache License 2.0. See also NOTICE.

Directories

Path Synopsis
cmd
pulse command
Command pulse is the Pulse CLI: publish, subscribe and replay events, manage contexts (PL-SDK-006).
Command pulse is the Pulse CLI: publish, subscribe and replay events, manage contexts (PL-SDK-006).
pulse-spike command
Command pulse-spike is the M2 gate spike (PL-TST-006): it fills EVENTS with a large synthetic data set and measures NATS memory and replay latency.
Command pulse-spike is the M2 gate spike (PL-TST-006): it fills EVENTS with a large synthetic data set and measures NATS memory and replay latency.
pulsed command
Command pulsed is the Pulse server: NATS bootstrap, edge, dashboard, rollups, bans, webhooks, backups.
Command pulsed is the Pulse server: NATS bootstrap, edge, dashboard, rollups, bans, webhooks, backups.
internal
acme
Package acme obtains and renews the Let's Encrypt certificate for raw NATS (PL-EDGE-006, ADR-0010) with DNS-01.
Package acme obtains and renews the Let's Encrypt certificate for raw NATS (PL-EDGE-006, ADR-0010) with DNS-01.
alerts
Package alerts evaluates built-in and custom alert rules, keeps their state and history in SQLite, publishes `_sys.alert.<rule>` events and hands transitions to notifiers (PL-OBS-004/005, PL-AUTH-012, ADR-0033).
Package alerts evaluates built-in and custom alert rules, keeps their state and history in SQLite, publishes `_sys.alert.<rule>` events and hands transitions to notifiers (PL-OBS-004/005, PL-AUTH-012, ADR-0033).
api
Package api serves the admin API + UI and the public API with Huma v2 on net/http (PL-API-001..005, ADR-0013).
Package api serves the admin API + UI and the public API with Huma v2 on net/http (PL-API-001..005, ADR-0013).
app
Package app wires pulsed together and carries build metadata.
Package app wires pulsed together and carries build metadata.
audit
Package audit writes and reads the append-only audit log (PL-AUD-001..003).
Package audit writes and reads the append-only audit log (PL-AUD-001..003).
backup
Package backup implements nightly encrypted backups (PL-BAK-001..009, ADR-0035): the SQLite database and keys.enc as files, and every JetStream stream as a snapshot whose files are stored content-addressed, so unchanged message blocks are uploaded once and every backup is still a complete, restorable snapshot.
Package backup implements nightly encrypted backups (PL-BAK-001..009, ADR-0035): the SQLite database and keys.enc as files, and every JetStream stream as a snapshot whose files are stored content-addressed, so unchanged message blocks are uploaded once and every backup is still a complete, restorable snapshot.
ban
Package ban detects repeated authentication failures and bans the source (PL-BAN-001..009, ADR-0011).
Package ban detects repeated authentication failures and bans the source (PL-BAN-001..009, ADR-0011).
banagent
Package banagent mirrors pulsed's bans into nftables sets (PL-BAN-007).
Package banagent mirrors pulsed's bans into nftables sets (PL-BAN-007).
bootstrap
Package bootstrap implements `pulsed init` (PL-KEY-003/004): the sealed key set, operator JWT, nats.conf, the bootstrap TLS leaf for 4222 and the banagent token.
Package bootstrap implements `pulsed init` (PL-KEY-003/004): the sealed key set, operator JWT, nats.conf, the bootstrap TLS leaf for 4222 and the banagent token.
certs
Package certs holds TLS material handling: the internal CA and bootstrap leaf for NATS (PL-EDGE-007), and later the origin certificate, AOP verification and ACME (M8, M9).
Package certs holds TLS material handling: the internal CA and bootstrap leaf for NATS (PL-EDGE-007), and later the origin certificate, AOP verification and ACME (M8, M9).
clients
Package clients manages tool clients: records in SQLite, credentials, grants and the APP account JWT that carries one scoped signing key per client (PL-AUTH-002..012, ADR-0025).
Package clients manages tool clients: records in SQLite, credentials, grants and the APP account JWT that carries one scoped signing key per client (PL-AUTH-002..012, ADR-0025).
config
Package config loads pulsed configuration from PULSE_* environment variables, with *_FILE variants for secrets (PL-OPS-003/004).
Package config loads pulsed configuration from PULSE_* environment variables, with *_FILE variants for secrets (PL-OPS-003/004).
edge
Package edge implements the public listener's edge concerns: Cloudflare ranges, the real client IP (PL-EDGE-004), TLS with the origin certificate and Authenticated Origin Pulls (PL-EDGE-002/003), and the /ws proxy (PL-EDGE-005).
Package edge implements the public listener's edge concerns: Cloudflare ranges, the real client IP (PL-EDGE-004), TLS with the origin certificate and Authenticated Origin Pulls (PL-EDGE-002/003), and the /ws proxy (PL-EDGE-005).
hooks
Package hooks receives inbound webhooks: verifies signatures, then publishes the request as an event (PL-HOOK-001..004).
Package hooks receives inbound webhooks: verifies signatures, then publishes the request as an event (PL-HOOK-001..004).
logging
Package logging configures slog JSON logging (PL-OBS-001).
Package logging configures slog JSON logging (PL-OBS-001).
metrics
Package metrics holds pulsed's Prometheus registry (PL-OBS-002).
Package metrics holds pulsed's Prometheus registry (PL-OBS-002).
natsauth
Package natsauth mints NATS operator, account and user JWTs and turns client grants into NATS permissions (ADR-0006, ADR-0007, ADR-0025).
Package natsauth mints NATS operator, account and user JWTs and turns client grants into NATS permissions (ADR-0006, ADR-0007, ADR-0025).
natsconf
Package natsconf renders nats.conf for operator mode with the full resolver (PL-KEY-004, ADR-0006).
Package natsconf renders nats.conf for operator mode with the full resolver (PL-KEY-004, ADR-0006).
natsconn
Package natsconn opens pulsed's internal NATS connections (SYS and APP users) over TLS (ADR-0023).
Package natsconn opens pulsed's internal NATS connections (SYS and APP users) over TLS (ADR-0023).
netguard
Package netguard provides an HTTP client for outbound webhooks that refuses internal destinations (PL-HOOK-008).
Package netguard provides an HTTP client for outbound webhooks that refuses internal destinations (PL-HOOK-008).
notify
Package notify sends alert notifications to Telegram and Discord (PL-OBS-006).
Package notify sends alert notifications to Telegram and Discord (PL-OBS-006).
oidc
Package oidc is single sign-on for dashboard users (ADR-0038, PL-USR-008..011), ported from hallpass: provider configuration, Authorization Code flow with PKCE, ID-token validation, identity linking, just-in-time provisioning and group→role mapping, SSO-only enforcement.
Package oidc is single sign-on for dashboard users (ADR-0038, PL-USR-008..011), ported from hallpass: provider configuration, Authorization Code flow with PKCE, ID-token validation, identity linking, just-in-time provisioning and group→role mapping, SSO-only enforcement.
oidc/oidctest
Package oidctest is an in-process OpenID Connect provider for tests: discovery, JWKS with key rotation, authorization with PKCE, a token endpoint, and knobs for malformed or malicious tokens.
Package oidctest is an in-process OpenID Connect provider for tests: discovery, JWKS with key rotation, authorization with PKCE, a token endpoint, and knobs for malformed or malicious tokens.
outbound
Package outbound delivers events to HTTP endpoints (PL-HOOK-005..010): one durable consumer per hook, in order, with exponential backoff, HMAC signatures, an SSRF-safe client and a delivery log in SQLite.
Package outbound delivers events to HTTP endpoints (PL-HOOK-005..010): one durable consumer per hook, in order, with exponential backoff, HMAC signatures, an SSRF-safe client and a delivery log in SQLite.
query
Package query is the explorer engine (PL-EXP-001..006, ADR-0022): paged reads over a stream with server-side filters, bounded by budgets.
Package query is the explorer engine (PL-EXP-001..006, ADR-0022): paged reads over a stream with server-side filters, bounded by budgets.
rbac
Package rbac is the dashboard permission matrix (ADR-0038, PL-USR-003).
Package rbac is the dashboard permission matrix (ADR-0038, PL-USR-003).
rollup
Package rollup counts events per minute, hour and day by subject prefix and ce-type, from headers-only ordered consumers, and keeps the subject index (PL-ROLL-001..004, ADR-0020).
Package rollup counts events per minute, hour and day by subject prefix and ce-type, from headers-only ordered consumers, and keeps the subject index (PL-ROLL-001..004, ADR-0020).
secretbox
Package secretbox seals small secrets (the key store) with XChaCha20-Poly1305 under a key derived from the master key.
Package secretbox seals small secrets (the key store) with XChaCha20-Poly1305 under a key derived from the master key.
store
Package store is pulsed's SQLite state database (ADR-0024, PL-DB-001..007).
Package store is pulsed's SQLite state database (ADR-0024, PL-DB-001..007).
streams
Package streams ensures and manages JetStream streams in the APP account (PL-BUS-001, PL-BUS-004/005/006).
Package streams ensures and manages JetStream streams in the APP account (PL-BUS-001, PL-BUS-004/005/006).
sysapi
Package sysapi talks to the NATS system account: $SYS.REQ.* requests and advisories (PL-KEY-005, PL-AUTH-007/010).
Package sysapi talks to the NATS system account: $SYS.REQ.* requests and advisories (PL-KEY-005, PL-AUTH-007/010).
tail
Package tail runs live tail sessions (PL-EXP-007): one ordered DeliverNew consumer per session, at most MaxSessions sessions, per-session rate limiting with sampling notices.
Package tail runs live tail sessions (PL-EXP-007): one ordered DeliverNew consumer per session, at most MaxSessions sessions, per-session rate limiting with sampling notices.
testutil/natstest
Package natstest boots an embedded nats-server from the nats.conf that `pulsed init` generates (PL-TST-001).
Package natstest boots an embedded nats-server from the nats.conf that `pulsed init` generates (PL-TST-001).
tokens
Package tokens manages HTTP bearer tokens for the public API (PL-ING-001, PL-ING-007).
Package tokens manages HTTP bearer tokens for the public API (PL-ING-001, PL-ING-007).
ui
Package ui embeds the built web UI (web/ → internal/ui/dist via `make web`).
Package ui embeds the built web UI (web/ → internal/ui/dist via `make web`).
users
Package users is dashboard authentication (ADR-0038, PL-USR-*), ported from hallpass's operator auth: local accounts with Argon2id passwords, TOTP MFA and recovery codes, invitations and password resets, server-side web sessions with CSRF tokens, recent-authentication (step-up) checks, login throttling, personal access tokens for the CLI, and the setup and break-glass one-time tokens issued by `pulsed admin`.
Package users is dashboard authentication (ADR-0038, PL-USR-*), ported from hallpass's operator auth: local accounts with Argon2id passwords, TOTP MFA and recovery codes, invitations and password resets, server-side web sessions with CSRF tokens, recent-authentication (step-up) checks, login throttling, personal access tokens for the CLI, and the setup and break-glass one-time tokens issued by `pulsed admin`.
pkg
pulse
Package pulse is the Go SDK for Pulse (PL-SDK-001): publish, subscribe and replay events over NATS JetStream.
Package pulse is the Go SDK for Pulse (PL-SDK-001): publish, subscribe and replay events over NATS JetStream.
pulse/envelope
Package envelope is the Pulse event envelope (CloudEvents-lite in NATS headers, ADR-0005, docs/ENVELOPE.md).
Package envelope is the Pulse event envelope (CloudEvents-lite in NATS headers, ADR-0005, docs/ENVELOPE.md).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL