Skip to content
Open
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
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ TYPESENSE_URL=http://localhost:8108
BUZZ_BIND_ADDR=0.0.0.0:3000
# Public WebSocket URL — used in NIP-42 auth challenges
RELAY_URL=ws://localhost:3000
# Stable relay signing key. Set this in dev if you want REST-created forum posts
# to keep resolving to the original author across relay restarts.
# Stable relay signing key (required). `just bootstrap` generates a random key in
# the gitignored .env file. Preserve that value across restarts and backups.
# BUZZ_RELAY_PRIVATE_KEY=<32-byte hex private key>
# Optional: path to the web UI dist directory. When set, the relay serves
# the web frontend at / for browser requests. Leave unset for local dev
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ jobs:
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
BUZZ_BIND_ADDR=0.0.0.0:3000 \
BUZZ_RELAY_PRIVATE_KEY="$(openssl rand -hex 32)" \
BUZZ_REQUIRE_AUTH_TOKEN=false \
BUZZ_RECONCILE_CHANNELS=true \
BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=100000 \
Expand Down Expand Up @@ -682,6 +683,7 @@ jobs:
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
BUZZ_BIND_ADDR=0.0.0.0:3000 \
BUZZ_RELAY_PRIVATE_KEY="$(openssl rand -hex 32)" \
BUZZ_REQUIRE_AUTH_TOKEN=false \
BUZZ_RECONCILE_CHANNELS=true \
BUZZ_GIT_PROBE_WRITERS=8 \
Expand Down
21 changes: 20 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ bootstrap:
cp .env.example .env
echo "Created .env from .env.example — review it before running just dev."
fi
./scripts/ensure-local-relay-key.sh .env

# Start Docker services, run migrations, install desktop deps
setup: bootstrap
Expand Down Expand Up @@ -307,6 +308,7 @@ test:
test-unit:
#!/usr/bin/env bash
set -euo pipefail
./scripts/test-ensure-local-relay-key.sh
if command -v cargo-nextest &>/dev/null; then
cargo nextest run -p buzz-core -p buzz-auth --lib
cargo nextest run -p buzz-voice --lib
Expand Down Expand Up @@ -423,13 +425,19 @@ relay: bootstrap _ensure-migrations
#!/usr/bin/env bash
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
set -o allexport
source .env
set +o allexport
cargo run -p buzz-relay

# Start the relay with the built web UI served from it
relay-web: bootstrap _ensure-migrations
#!/usr/bin/env bash
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
set -o allexport
source .env
set +o allexport
[[ -d node_modules ]] || pnpm install
pnpm -C web build
BUZZ_WEB_DIR=./web/dist cargo run -p buzz-relay
Expand All @@ -439,6 +447,9 @@ admin: bootstrap _ensure-migrations
#!/usr/bin/env bash
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
set -o allexport
source .env
set +o allexport
[[ -d node_modules ]] || pnpm install
pnpm -C admin-web build
export BUZZ_ADMIN_HOST="${BUZZ_ADMIN_HOST:-admin.localhost:3000}"
Expand All @@ -459,7 +470,12 @@ admin-check: fmt-check
pnpm -C admin-web exec playwright test

# Start the relay server in release mode
relay-release: _ensure-migrations
relay-release: bootstrap _ensure-migrations
#!/usr/bin/env bash
set -euo pipefail
set -o allexport
source .env
set +o allexport
cargo run -p buzz-relay --release


Expand All @@ -468,6 +484,9 @@ dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations
#!/usr/bin/env bash
set -euo pipefail
export PATH="{{justfile_directory()}}/bin:$PATH"
set -o allexport
source .env
set +o allexport
bind_addr="${BUZZ_BIND_ADDR:-0.0.0.0:3000}"
relay_port="${bind_addr##*:}"; [[ -n "$relay_port" ]] || relay_port=3000
health_port="${BUZZ_HEALTH_PORT:-8080}"
Expand Down
11 changes: 7 additions & 4 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ CLI signs every request with NIP-98, so you don't need `nak` or hand-rolled

```bash
. ./bin/activate-hermit # activate pinned toolchain
cp .env.example .env # one-time
just bootstrap # create .env and its stable relay key once
just setup # start Docker services, run migrations
```

Expand Down Expand Up @@ -70,6 +70,9 @@ Rebuild after any code change — the steps below use the release binaries.
In a separate terminal (it runs in the foreground):

```bash
set -o allexport
source .env # includes the key generated by just bootstrap
set +o allexport
buzz-relay # release binary from step 2, serves ws://localhost:3000
# alternatives:
# cargo run --release -p buzz-relay # rebuild + run in release
Expand All @@ -89,9 +92,9 @@ curl -s http://localhost:8080/_readiness # → {"status":"ready"}
> `BUZZ_HEALTH_PORT`) so K8s probes bypass auth middleware. The main app
> port also exposes `/health` for convenience.

The relay starts in dev mode (`BUZZ_REQUIRE_AUTH_TOKEN=false`). The startup
log emits a WARN about this — that's expected for local testing. See the env
vars table at the bottom if you need to lock it down.
The relay starts in dev mode (`BUZZ_REQUIRE_AUTH_TOKEN=false`) with the stable
relay identity generated in `.env`. See the env vars table at the bottom if
you need to lock it down.

> **Already running Buzz Desktop (or another relay) on `:3000` / `:8080` /
> `:9102`?** Buzz binds three ports — main, health, metrics — and any of
Expand Down
55 changes: 30 additions & 25 deletions crates/buzz-relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ fn buzz_auto_migrate_enabled(value: Option<&str>) -> bool {
})
}

fn relay_keypair_from_config(relay_private_key: Option<&str>) -> anyhow::Result<nostr::Keys> {
let hex = relay_private_key.ok_or_else(|| {
anyhow::anyhow!(
"BUZZ_RELAY_PRIVATE_KEY must be set. Run `just bootstrap` for local \
development or configure a stable 32-byte hex private key."
)
})?;
nostr::Keys::parse(hex).map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))
}

/// Controls how many per-community gauge series the usage poller emits.
///
/// Datadog cost is proportional to the number of unique time-series. With ~25
Expand Down Expand Up @@ -143,6 +153,7 @@ async fn main() -> anyhow::Result<()> {
error!("Invalid configuration: {e}");
anyhow::anyhow!("Configuration error: {e}")
})?;
let relay_keypair = relay_keypair_from_config(config.relay_private_key.as_deref())?;
info!(
bind_addr = %config.bind_addr,
relay_url = %config.relay_url,
Expand Down Expand Up @@ -422,29 +433,6 @@ async fn main() -> anyhow::Result<()> {
let workflow_config = buzz_workflow::WorkflowConfig::default();
let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config));

let relay_keypair = if let Some(hex) = &config.relay_private_key {
nostr::Keys::parse(hex)
.map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))?
} else if !config.require_auth_token {
// Dev mode: use a deterministic keypair so addressable events (kind:39000/39001/39002)
// replace correctly across restarts. Without this, each restart generates a new pubkey
// and replace_addressable_event inserts duplicates instead of replacing.
const DEV_RELAY_PRIVKEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
let keys = nostr::Keys::parse(DEV_RELAY_PRIVKEY).expect("hardcoded dev key is valid");
tracing::warn!(
pubkey = %keys.public_key().to_hex(),
"Using hardcoded dev relay keypair (BUZZ_REQUIRE_AUTH_TOKEN=false). \
Set BUZZ_RELAY_PRIVATE_KEY for production."
);
keys
} else {
panic!(
"BUZZ_RELAY_PRIVATE_KEY must be set when BUZZ_REQUIRE_AUTH_TOKEN=true. \
A stable relay identity is required for production."
);
};

config
.media
.validate()
Expand Down Expand Up @@ -2037,8 +2025,8 @@ mod tests {

use super::{
buzz_auto_migrate_enabled, dropped_in_memory_keys, idle_timeout_secs,
refresh_legacy_active_gauge_recency, run_periodic_until_cancelled, EmissionScope,
InMemoryMetricKey,
refresh_legacy_active_gauge_recency, relay_keypair_from_config,
run_periodic_until_cancelled, EmissionScope, InMemoryMetricKey,
};
use metrics::GaugeFn;
use metrics_util::{
Expand Down Expand Up @@ -2086,6 +2074,23 @@ mod tests {
assert!(buzz_auto_migrate_enabled(Some("on")));
}

#[test]
fn configured_relay_identity_is_preserved() {
let configured = nostr::Keys::generate();
let secret = configured.secret_key().to_secret_hex();

let selected = relay_keypair_from_config(Some(&secret)).expect("configured key");

assert_eq!(selected.public_key(), configured.public_key());
}

#[test]
fn missing_relay_identity_is_rejected() {
let result = relay_keypair_from_config(None);

assert!(result.is_err());
}

#[test]
fn test_emission_scope_off_disallows_every_community() {
assert!(EmissionScope::All.allows(&Uuid::new_v4()));
Expand Down
1 change: 1 addition & 0 deletions scripts/e2e-git-perms.sh
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export BUZZ_GIT_HOOK_HMAC_SECRET="${HMAC_SECRET}"
export BUZZ_BIND_ADDR="${RELAY_HOST}:${RELAY_PORT}"
export RELAY_URL="${RELAY_WS}"
export RUST_LOG="buzz_relay=warn"
export BUZZ_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:-$(openssl rand -hex 32)}"
export BUZZ_REQUIRE_AUTH_TOKEN=false

# Clean repos dir (isolated test state)
Expand Down
66 changes: 66 additions & 0 deletions scripts/ensure-local-relay-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail

ENV_FILE="${1:-.env}"

if [[ ! -f "${ENV_FILE}" ]]; then
echo "error: ${ENV_FILE} does not exist" >&2
exit 1
fi

existing_key="$({
unset BUZZ_RELAY_PRIVATE_KEY
set +u
# shellcheck disable=SC1090
source "${ENV_FILE}" || exit 1
printf '%s' "${BUZZ_RELAY_PRIVATE_KEY:-}"
})"

if [[ -n "${existing_key}" ]]; then
chmod 600 "${ENV_FILE}"
exit 0
fi

relay_key="$(node <<'NODE'
const { randomBytes } = require("node:crypto");
const curveOrder = BigInt(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
);

let bytes;
let scalar;
do {
bytes = randomBytes(32);
scalar = BigInt(`0x${bytes.toString("hex")}`);
} while (scalar === 0n || scalar >= curveOrder);

process.stdout.write(bytes.toString("hex"));
NODE
)"

temp_file="$(mktemp "${ENV_FILE}.tmp.XXXXXX")"
trap 'rm -f "${temp_file}"' EXIT

awk -v key="${relay_key}" '
BEGIN { replaced = 0 }
/^[[:space:]]*(export[[:space:]]+)?BUZZ_RELAY_PRIVATE_KEY=/ {
if (!replaced) {
print "BUZZ_RELAY_PRIVATE_KEY=" key
replaced = 1
}
next
}
{ print }
END {
if (!replaced) {
if (NR > 0) print ""
print "BUZZ_RELAY_PRIVATE_KEY=" key
}
}
' "${ENV_FILE}" > "${temp_file}"

chmod 600 "${temp_file}"
mv "${temp_file}" "${ENV_FILE}"
trap - EXIT

echo "Generated BUZZ_RELAY_PRIVATE_KEY in ${ENV_FILE}."
2 changes: 2 additions & 0 deletions scripts/run-desktop-release-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ else
RELAY_BIN="${ROOT}/target/ci/buzz-relay"
fi
log "starting relay at ${RELAY_HTTP_URL}"
RELAY_PRIVATE_KEY="$(openssl rand -hex 32)"
env \
DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/${DB_NAME}" \
REDIS_URL="redis://localhost:6379/${REDIS_DB}" \
RELAY_URL="ws://${COMMUNITY_HOST}" \
BUZZ_BIND_ADDR="127.0.0.1:${RELAY_PORT}" \
BUZZ_HEALTH_PORT="${HEALTH_PORT}" \
BUZZ_METRICS_PORT="${METRICS_PORT}" \
BUZZ_RELAY_PRIVATE_KEY="${RELAY_PRIVATE_KEY}" \
BUZZ_REQUIRE_AUTH_TOKEN=false \
BUZZ_RECONCILE_CHANNELS=true \
BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=1000000 \
Expand Down
2 changes: 2 additions & 0 deletions scripts/start-isolated-test-relay.sh
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ ok "Relay built"
# survives (same pattern the perf stack uses). Logs to ${RELAY_LOG}.
RELAY_LOG="${RELAY_LOG:-/tmp/dawn-relay-run.log}"
TMUX_SESSION="${TMUX_SESSION:-dawn-relay}"
RELAY_PRIVATE_KEY="$(openssl rand -hex 32)"
tmux kill-session -t "${TMUX_SESSION}" 2>/dev/null || true
if command -v lsof >/dev/null 2>&1 && lsof -nP -iTCP:"${RELAY_MAIN}" -sTCP:LISTEN >/dev/null 2>&1; then
err "Port ${RELAY_MAIN} is already in use; refusing to report a stale relay as this harness."
Expand All @@ -151,6 +152,7 @@ tmux new-session -d -s "${TMUX_SESSION}" "cd '${REPO_ROOT}' && env \
BUZZ_S3_ACCESS_KEY=buzz_dev \
BUZZ_S3_SECRET_KEY=buzz_dev_secret \
BUZZ_S3_BUCKET=buzz-media \
BUZZ_RELAY_PRIVATE_KEY=${RELAY_PRIVATE_KEY} \
BUZZ_REQUIRE_AUTH_TOKEN=false \
BUZZ_RECONCILE_CHANNELS=true \
'./target/${CARGO_TARGET_PROFILE}/buzz-relay' > '${RELAY_LOG}' 2>&1"
Expand Down
5 changes: 4 additions & 1 deletion scripts/start-relay-for-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ fi

log "Starting relay..."

TEST_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:-$(openssl rand -hex 32)}"

# Optional NIP-43 membership gating: exported by callers that need a
# membership-gated relay (e.g. the mesh lifecycle smoke). All three must be
# set together — the relay fails fast otherwise.
Expand All @@ -160,8 +162,8 @@ if [[ "${BUZZ_REQUIRE_RELAY_MEMBERSHIP:-}" == "true" ]]; then
MEMBERSHIP_ENV+=(
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
RELAY_OWNER_PUBKEY="${RELAY_OWNER_PUBKEY:?RELAY_OWNER_PUBKEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
BUZZ_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:?BUZZ_RELAY_PRIVATE_KEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
)
: "${BUZZ_RELAY_PRIVATE_KEY:?BUZZ_RELAY_PRIVATE_KEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
log "Membership gating enabled (NIP-43)"
fi

Expand All @@ -170,6 +172,7 @@ nohup env \
REDIS_URL=redis://localhost:6379 \
RELAY_URL=ws://localhost:3000 \
BUZZ_BIND_ADDR=0.0.0.0:3000 \
BUZZ_RELAY_PRIVATE_KEY="${TEST_RELAY_PRIVATE_KEY}" \
BUZZ_REQUIRE_AUTH_TOKEN=false \
BUZZ_RECONCILE_CHANNELS=true \
BUZZ_GIT_PROBE_WRITERS=8 \
Expand Down
33 changes: 33 additions & 0 deletions scripts/test-ensure-local-relay-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/buzz-relay-key-test.XXXXXX")"
trap 'rm -rf "${TEST_DIR}"' EXIT

ENV_FILE="${TEST_DIR}/.env"
cp "${REPO_ROOT}/.env.example" "${ENV_FILE}"

"${SCRIPT_DIR}/ensure-local-relay-key.sh" "${ENV_FILE}" >/dev/null
first_key="$(sed -n 's/^BUZZ_RELAY_PRIVATE_KEY=//p' "${ENV_FILE}")"

if [[ ! "${first_key}" =~ ^[0-9a-f]{64}$ ]]; then
echo "FAIL: bootstrap did not generate a valid 32-byte hex relay key" >&2
exit 1
fi

"${SCRIPT_DIR}/ensure-local-relay-key.sh" "${ENV_FILE}" >/dev/null
second_key="$(sed -n 's/^BUZZ_RELAY_PRIVATE_KEY=//p' "${ENV_FILE}")"

if [[ "${first_key}" != "${second_key}" ]]; then
echo "FAIL: bootstrap replaced the existing relay key" >&2
exit 1
fi

if [[ "$(grep -c '^BUZZ_RELAY_PRIVATE_KEY=' "${ENV_FILE}")" -ne 1 ]]; then
echo "FAIL: bootstrap wrote more than one relay key" >&2
exit 1
fi

echo "PASS: bootstrap generates one relay key and reuses it"
Loading