Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
251a959
feat: gate cli/tui behind a feature, add shutdown handle for serve/co…
single9 Oct 21, 2026
2159893
docs: scope multi-tenant serve and library readiness
single9 Oct 21, 2026
f2c3045
feat: add Registry::subscribe and crate-level embedding docs
single9 Oct 21, 2026
651f3de
refactor: split into an erbridge-core library and an erbridge binary …
single9 Oct 21, 2026
6dbc923
docs: describe the workspace and library usage in the README
single9 Oct 21, 2026
47da368
docs: add multi-tenant spec and tickets
single9 Oct 21, 2026
6879739
feat: add tenant model and Noise handshake that identifies the PSK used
single9 Oct 21, 2026
9edcd58
feat: add a ClientHello SNI parser that returns the bytes it consumed
single9 Oct 21, 2026
2276f3b
feat: multi-tenant serve with per-tenant tokens and SNI routing
single9 Oct 21, 2026
cd51508
feat: tenant stores and live reload of tenant changes
single9 Oct 21, 2026
7870595
feat: cap concurrent connections per tenant
single9 Oct 21, 2026
a25f7f8
feat: single-use enrollment codes that create tenants
single9 Oct 21, 2026
da59e67
feat: serve --tenants, serve-admin, enroll and connect --credentials
single9 Oct 21, 2026
9b3909d
test: end-to-end multi-tenant tests with real TLS and the real binary
single9 Oct 21, 2026
6eb8f74
build: prepare both crates for crates.io
single9 Oct 21, 2026
117271c
ci: add CI workflow and publish both crates to crates.io from the rel…
single9 Oct 21, 2026
10b9048
docs: add upgrade notes for 0.3 to the README
single9 Oct 22, 2026
5b4aabe
ci: pick the release version from Conventional Commits
single9 Oct 22, 2026
5e38814
ci: record the scripts as executable and run them through bash
single9 Oct 22, 2026
dc41f43
fix: bound the control handshake, redact secrets in Debug, harden the…
single9 Oct 22, 2026
ae6af50
fix: survive a poisoned lock and rate-limit the refused-connection log
single9 Oct 23, 2026
0f7b66f
fix: per-address handshake cap, withdraw orphaned tenants, close the …
single9 Oct 23, 2026
b2c8642
ci: keep file names from being read as options in the checksum step
single9 Oct 23, 2026
a058be7
refactor: wrap PSKs in a Psk type that redacts itself and compares in…
single9 Oct 23, 2026
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
68 changes: 68 additions & 0 deletions .github/scripts/next-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Prints the next release version (without a leading "v").
#
# next-version.sh BASE_VERSION BUMP
#
# BASE_VERSION is the latest released version, e.g. 0.2.0 (tag v0.2.0).
# BUMP is one of: auto, patch, minor, major.
#
# `auto` reads the Conventional Commits since the tag v<BASE_VERSION> (the
# whole history if that tag does not exist):
#
# breaking (`type!:` or a `BREAKING CHANGE` footer) -> major, or minor while
# the major version is 0
# feat -> minor
# anything else -> patch
#
# It fails when there are no commits to release. The decision lives here rather
# than in git-cliff's own bump logic, whose handling of 0.x differs between
# versions; git-cliff only writes the changelog.
set -euo pipefail

base="${1:?usage: next-version.sh BASE_VERSION BUMP}"
bump="${2:?usage: next-version.sh BASE_VERSION BUMP}"

if ! [[ "$base" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
echo "next-version: '$base' is not MAJOR.MINOR.PATCH" >&2
exit 2
fi
major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} patch=${BASH_REMATCH[3]}

level="$bump"
if [ "$bump" = auto ]; then
if git rev-parse -q --verify "refs/tags/v$base" >/dev/null; then
range="v$base..HEAD"
else
range="HEAD"
fi

subjects=$(git log --format=%s "$range" | grep -v '^chore(release)' || true)
if [ -z "$subjects" ]; then
echo "next-version: no commits since v$base, nothing to release" >&2
exit 1
fi

bodies=$(git log --format=%b "$range" | grep -v '^$' || true)
if grep -qE '^[a-zA-Z]+(\([^)]*\))?!:' <<<"$subjects" ||
grep -qE '^BREAKING[ -]CHANGE' <<<"$bodies"; then
if [ "$major" -ge 1 ]; then level=major; else level=minor; fi
reason="a breaking change"
elif grep -qE '^feat(\([^)]*\))?:' <<<"$subjects"; then
level="minor"
reason="a new feature"
else
level="patch"
reason="no feature or breaking change"
fi
echo "next-version: auto -> $level ($reason since v$base)" >&2
fi

case "$level" in
major) echo "$((major + 1)).0.0" ;;
minor) echo "$major.$((minor + 1)).0" ;;
patch) echo "$major.$minor.$((patch + 1))" ;;
*)
echo "next-version: unknown bump '$bump' (auto, patch, minor, major)" >&2
exit 2
;;
esac
82 changes: 82 additions & 0 deletions .github/scripts/test-next-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Tests next-version.sh against throwaway git repositories.
set -uo pipefail

script="$(cd "$(dirname "$0")" && pwd)/next-version.sh"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
failed=0

# repo NAME TAG COMMIT... (a commit message may contain a body after a blank line)
repo() {
local dir="$work/$1" tag="$2"
shift 2
mkdir -p "$dir" && git -C "$dir" init -q
git -C "$dir" config user.email t@t && git -C "$dir" config user.name t
git -C "$dir" commit -q --allow-empty -m "chore: init"
[ -n "$tag" ] && git -C "$dir" tag "$tag"
local msg
for msg in "$@"; do git -C "$dir" commit -q --allow-empty -m "$msg"; done
echo "$dir"
}

# expect DESCRIPTION WANT DIR BASE BUMP (WANT "FAIL" = non-zero exit)
expect() {
local desc="$1" want="$2" dir="$3" got rc
got=$(cd "$dir" && bash "$script" "$4" "$5" 2>/dev/null)
rc=$?
if [ "$want" = FAIL ]; then
if [ "$rc" -ne 0 ]; then
echo " ok $desc"
else
echo " FAIL $desc: expected failure, got '$got'"
failed=1
fi
elif [ "$rc" -eq 0 ] && [ "$got" = "$want" ]; then
echo " ok $desc -> $got"
else
echo " FAIL $desc: want $want, got '$got' (exit $rc)"
failed=1
fi
}

echo "auto, 0.x"
expect "docs only" 0.2.1 "$(repo a v0.2.0 'docs: tweak')" 0.2.0 auto
expect "fix" 0.2.1 "$(repo b v0.2.0 'fix: bug')" 0.2.0 auto
expect "feat" 0.3.0 "$(repo c v0.2.0 'fix: bug' 'feat: thing')" 0.2.0 auto
expect "scoped feat" 0.3.0 "$(repo d v0.2.0 'feat(core): thing')" 0.2.0 auto
expect "feat!, stays in 0.x" 0.3.0 "$(repo e v0.2.0 'feat!: drop it')" 0.2.0 auto
expect "fix!, stays in 0.x" 0.3.0 "$(repo f v0.2.0 'fix(api)!: change')" 0.2.0 auto
expect "BREAKING CHANGE body" 0.3.0 "$(repo g v0.2.0 $'refactor: x\n\nBREAKING CHANGE: y')" 0.2.0 auto
expect "release commit only" FAIL "$(repo h v0.2.0 'chore(release): v0.2.0')" 0.2.0 auto
expect "no commits" FAIL "$(repo i v0.2.0)" 0.2.0 auto
expect "feat inside a word" 0.2.1 "$(repo j v0.2.0 'docs: feature list')" 0.2.0 auto

echo "auto, 1.x"
expect "fix" 1.2.4 "$(repo k v1.2.3 'fix: bug')" 1.2.3 auto
expect "feat" 1.3.0 "$(repo l v1.2.3 'feat: thing')" 1.2.3 auto
expect "feat!" 2.0.0 "$(repo m v1.2.3 'feat!: drop')" 1.2.3 auto
expect "BREAKING-CHANGE body" 2.0.0 "$(repo n v1.2.3 $'fix: x\n\nBREAKING-CHANGE: y')" 1.2.3 auto

echo "history before the base tag is ignored"
expect "old feat! not counted" 0.2.1 "$(repo o v0.2.0 'fix: later')" 0.2.0 auto
dir="$(repo p '' 'feat!: ancient')"; git -C "$dir" tag v0.2.0; git -C "$dir" commit -q --allow-empty -m 'fix: later'
expect "breaking before tag" 0.2.1 "$dir" 0.2.0 auto

echo "no tag yet: whole history"
expect "untagged" 0.3.0 "$(repo q '' 'feat: first')" 0.2.0 auto

echo "manual"
dir="$(repo r v0.2.0)"
expect "patch" 0.2.1 "$dir" 0.2.0 patch
expect "minor" 0.3.0 "$dir" 0.2.0 minor
expect "major" 1.0.0 "$dir" 0.2.0 major
expect "bad bump" FAIL "$dir" 0.2.0 huge
expect "bad version" FAIL "$dir" v0.2 auto

if [ "$failed" -eq 0 ]; then
echo "all passed"
else
echo "FAILED"
exit 1
fi
87 changes: 87 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
toolchain: stable
components: rustfmt
- run: cargo fmt --all --check

clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
- run: cargo clippy --workspace --all-targets -- -D warnings

test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
- run: cargo test --workspace

# The script that picks the next release version from the commit history.
release-scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- run: shellcheck .github/scripts/*.sh
- name: Check the workflow files
run: |
bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/03d0035246f3e81f36aed592ffb4bebf33a03106/scripts/download-actionlint.bash) 1.7.7
./actionlint -color
- run: bash .github/scripts/test-next-version.sh

# The minimum Rust version declared in Cargo.toml (`rust-version`).
msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dtolnay/rust-toolchain@599c45df1a24c1c63ae1129a7e59f46654b8fb66 # 1.89.0
with:
toolchain: 1.89.0
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
- run: cargo check --workspace --all-targets

# Everything `cargo publish` would do, minus the upload: package both crates,
# verify them, and build `erbridge` against the packaged `erbridge-core`.
publish-dry-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2
- run: cargo publish --workspace --dry-run
Loading
Loading