Files
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7andWill Pfleger 85782fc71a feat(relay): add moderator relay-member role with full capability grid
Add a tenant-scoped 'moderator' role to relay_members that holds
ViewQueue, ResolveReport, DeleteMessage, Kick, Timeout, Untimeout
capabilities community-wide. Ban/Unban remain admin+ only. Guard
rails prevent a moderator from Kicking or Timing-out the community
owner or a fellow admin.

Relay/DB changes:
- migrations/0028_relay_moderator_role.sql: additive migration that
  drops and recreates the relay_members.role CHECK to include
  'moderator'; schema/schema.sql snapshot updated; migration count
  assertion advanced to 28
- moderation_authz.rs: ModerationAuthority::CommunityModerator; full
  decide_authority arm for 'moderator' with exhaustive unit tests
- side_effects.rs: kind 9005 validator gains additive relay-role path
  through authorize_moderation_action(DeleteMessage); kind 9001 gains
  additive Kick path + routes to remove_member_as_community_moderator
  (new preauthorized DB mutation) when actor has no channel role
- moderation_commands.rs: 9044 resolve builds one resolved report-action
  value (required_capability, target_author, channel_id) from stored
  report + get_event_by_id_including_deleted; normalization matrix
  enforced before authorization; capability map delete→DeleteMessage,
  kick→Kick, ban→Ban, timeout→Timeout, dismiss/escalate→ResolveReport
- relay_admin.rs: 9030/9032 accept 'moderator'; 9031 admin removal uses
  atomic role = ANY('member','moderator') predicate (no TOCTOU race)
- buzz-db channel.rs: remove_member_as_community_moderator acquires same
  per-channel membership lock as remove_member, re-checks last-owner
  protection in-transaction, performs soft removal without channel role
- relay main.rs metrics role set extended; buzz-admin CLI role validation
  extended; deploy/compose/run.sh docs updated

Desktop changes:
- capabilities.ts: moderationCapabilities(role) helper returning
  typed capability struct; single source of truth consumed by both
  MessageModerationMenuItems and ModerationQueueCard
- ModerationQueueCard: uses moderationCapabilities; passes canBan into
  resolvableActions; uses moderatorDeleteMessage (kind 9005) for delete
- MessageModerationMenuItems: capability-aware per action; Ban/Unban
  hidden for moderators
- TimelineMessageRow: moderator delete affordance via 9005 path when
  canDelete && !isOwnMessage
- CommunityMembersSettingsCard: moderator role shown with ShieldHalf
  icon; role management extended to moderator tier
- relayMembers.ts/types.ts/e2eBridge.ts: 'moderator' added to all role
  unions; downgrade-to-member normalization updated
- events.rs: VALID_RELAY_ROLES includes 'moderator'
- commands/moderator.rs: new Tauri command moderator_delete_message
  builds kind 9005 inline (avoids file-size ratchet growth on messages.rs)
- shared/api/moderator.ts: moderatorDeleteMessage TS wrapper

Tests added:
- 13 unit tests for decide_authority (moderator grid exhaustive)
- 9 unit tests for moderationCapabilities exhaustive cases
- 5 unit tests for resolvableActions(canBan=false)
- 3 DB integration tests for remove_member_as_community_moderator
  (ordinary kick, last-owner guard, non-member not-found) [ignored]
- 1 DB integration test for admin remove member/moderator matrix [ignored]

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-04 15:13:24 -04:00
..

Buzz Docker Compose deployment

This is the single-node/VPS deployment bundle. It is intentionally separate from the root docker-compose.yml, which remains local development infrastructure.

Quick start

cd deploy/compose
cp .env.example .env
$EDITOR .env       # replace every CHANGE_ME value
./run.sh start

For a public VPS with automatic Let's Encrypt certificates:

cd deploy/compose
BUZZ_COMPOSE_TLS=true ./run.sh start

The bootstrap script should eventually replace manual .env editing for normal users. It is responsible for generating stable secrets and, optionally, an owner keypair.

Production notes

  • Requires Docker Compose v2.24.4 or newer; the TLS override uses Compose's !reset tag to remove the direct relay port when Caddy terminates HTTPS.
  • Default BUZZ_IMAGE tracks ghcr.io/block/buzz:main for early testing. Pin it to ghcr.io/block/buzz:sha-<7> or a semver release tag for production once available.
  • Keep BUZZ_RELAY_PRIVATE_KEY, BUZZ_GIT_HOOK_HMAC_SECRET, database/Redis, and S3 secrets stable across restarts.
  • RELAY_OWNER_PUBKEY is intentionally not prefixed with BUZZ_; it must be a 64-character hex Nostr pubkey when closed relay mode is enabled.
  • BUZZ_AUTO_MIGRATE is opt-in. Set BUZZ_AUTO_MIGRATE=true or run buzz-admin migrate before starting the relay when bootstrapping a fresh database. Auto-migration requires an image that includes embedded SQLx migrations.
  • The stack uses Postgres, Redis, MinIO, and a git data volume because those are real Buzz dependencies today. Minimal mode can simplify this later.
  • The bundled Compose stack fixes the relay endpoint to http://minio:9000 and BUZZ_S3_ADDRESSING_STYLE=path: Docker DNS resolves minio, not <bucket>.minio. It is not configurable for an external S3 provider through .env; use the Helm chart or a custom Compose configuration for providers such as new Railway Storage Buckets that require virtual addressing.

Run ./run.sh backup-hint for the backup checklist.

Validation

Before sharing an install link publicly, verify a fresh install with:

cd deploy/compose
cp .env.example .env
$EDITOR .env
./run.sh config
./run.sh start
curl -fsS "http://127.0.0.1:$(grep -E '^BUZZ_HTTP_PORT=' .env | cut -d= -f2-)/_liveness"
./run.sh status