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
313 changes: 131 additions & 182 deletions .github/workflows/release-on-main.yml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ Broker mode also emits best-effort context usage telemetry in inbox pull `meta`

### Release Updater / Rollback (CLI env overrides)

Baudbot release versioning is driven by the root `package.json.version`. Runtime and release metadata record both semver and git SHA, while on-disk release snapshots remain SHA-addressed. Semver tags are published manually via the **Release on main** GitHub Actions workflow.

These are **command-time overrides** for `baudbot update` / `baudbot rollback` (or the underlying scripts). They are not required in `~/.config/.env`.

| Variable | Description | Default |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ See [SECURITY.md](SECURITY.md) for full threat model, trust boundaries, and know
- [docs/linux-runtime.md](docs/linux-runtime.md) — Linux execution model, tools, and constraints
- [docs/operations.md](docs/operations.md) — day-2 operations (start/stop/update/rollback/audit)
- [docs/architecture.md](docs/architecture.md) — source/runtime/release architecture
- [docs/releases.md](docs/releases.md) — semver policy and manual release workflow
- [CONFIGURATION.md](CONFIGURATION.md) — full env var reference
- [SECURITY.md](SECURITY.md) — deep security model and vulnerability reporting
- [CONTRIBUTING.md](CONTRIBUTING.md) — contribution workflow
Expand Down
19 changes: 19 additions & 0 deletions bin/baudbot
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ if [ -f "$RUNTIME_NODE_HELPER" ]; then
source "$RUNTIME_NODE_HELPER"
fi

JSON_COMMON_HELPER="$BAUDBOT_ROOT/bin/lib/json-common.sh"
if [ -f "$JSON_COMMON_HELPER" ]; then
# shellcheck source=bin/lib/json-common.sh
source "$JSON_COMMON_HELPER"
fi

VERSION_COMMON_HELPER="$BAUDBOT_ROOT/bin/lib/version-common.sh"
if [ -f "$VERSION_COMMON_HELPER" ]; then
# shellcheck source=bin/lib/version-common.sh
source "$VERSION_COMMON_HELPER"
fi

json_get_string_or_empty() {
local file="$1"
local key="$2"
Expand Down Expand Up @@ -63,6 +75,13 @@ else
fi

version() {
if [ -n "${VERSION_COMMON_HELPER:-}" ] && [ -f "$VERSION_COMMON_HELPER" ]; then
bb_package_version_or_unknown "$BAUDBOT_ROOT"
return 0
fi

# Fallback for partially copied/degraded installations where the shared
# version helper is missing but package.json is still present.
local package_json="$BAUDBOT_ROOT/package.json"
local pkg_version=""

Expand Down
18 changes: 17 additions & 1 deletion bin/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ source "$SCRIPT_DIR/lib/json-common.sh"
source "$SCRIPT_DIR/lib/deploy-common.sh"
# shellcheck source=bin/lib/runtime-node.sh
source "$SCRIPT_DIR/lib/runtime-node.sh"
# shellcheck source=bin/lib/version-common.sh
source "$SCRIPT_DIR/lib/version-common.sh"
bb_enable_strict_mode
bb_init_paths

Expand Down Expand Up @@ -422,25 +424,39 @@ if [ "$DRY_RUN" -eq 0 ]; then
GIT_SHA=""
GIT_SHA_SHORT=""
GIT_BRANCH=""
RELEASE_VERSION=""
RELEASE_TAG=""

if (cd "$BAUDBOT_SRC" && git rev-parse HEAD >/dev/null 2>&1); then
GIT_SHA=$(cd "$BAUDBOT_SRC" && git rev-parse HEAD 2>/dev/null || echo "unknown")
GIT_SHA_SHORT=$(cd "$BAUDBOT_SRC" && git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_BRANCH=$(cd "$BAUDBOT_SRC" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
RELEASE_VERSION="$(bb_package_version_or_unknown "$BAUDBOT_SRC")"
if [ "$RELEASE_VERSION" != "unknown" ]; then
RELEASE_TAG="$(bb_release_tag_for_version "$RELEASE_VERSION")"
fi
elif [ -f "$RELEASE_META_FILE" ]; then
GIT_SHA="$(json_get_string_or_empty "$RELEASE_META_FILE" "sha")"
GIT_SHA_SHORT="$(json_get_string_or_empty "$RELEASE_META_FILE" "short")"
GIT_BRANCH="$(json_get_string_or_empty "$RELEASE_META_FILE" "branch")"
RELEASE_VERSION="$(json_get_string_or_empty "$RELEASE_META_FILE" "version")"
RELEASE_TAG="$(json_get_string_or_empty "$RELEASE_META_FILE" "tag")"
fi

[ -n "$GIT_SHA" ] || GIT_SHA="unknown"
[ -n "$GIT_SHA_SHORT" ] || GIT_SHA_SHORT="unknown"
[ -n "$GIT_BRANCH" ] || GIT_BRANCH="unknown"
[ -n "$RELEASE_VERSION" ] || RELEASE_VERSION="unknown"
if [ -z "$RELEASE_TAG" ] && [ "$RELEASE_VERSION" != "unknown" ]; then
RELEASE_TAG="$(bb_release_tag_for_version "$RELEASE_VERSION")"
fi
DEPLOY_TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Write version file via agent
as_agent bash -c "cat > '$VERSION_FILE'" <<VEOF
{
"version": "$RELEASE_VERSION",
"tag": "$RELEASE_TAG",
"sha": "$GIT_SHA",
"short": "$GIT_SHA_SHORT",
"branch": "$GIT_BRANCH",
Expand All @@ -449,7 +465,7 @@ if [ "$DRY_RUN" -eq 0 ]; then
}
VEOF
Comment on lines 453 to 466
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "vunknown" written into metadata JSON when version is unavailable

When neither the git repo nor an existing $RELEASE_META_FILE provides a version (e.g., a freshly checked-out archive with no package.json), RELEASE_VERSION is set to "unknown". The subsequent bb_release_tag_for_version "unknown" call then writes "tag": "vunknown" into baudbot-version.json. Tools or operators parsing the tag field for semver validity would receive an unexpected value. Guarding the bb_release_tag_for_version call to only run when RELEASE_VERSION is not "unknown" would be more consistent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/deploy.sh
Line: 449-462

Comment:
**`"vunknown"` written into metadata JSON when version is unavailable**

When neither the git repo nor an existing `$RELEASE_META_FILE` provides a version (e.g., a freshly checked-out archive with no `package.json`), `RELEASE_VERSION` is set to `"unknown"`. The subsequent `bb_release_tag_for_version "unknown"` call then writes `"tag": "vunknown"` into `baudbot-version.json`. Tools or operators parsing the tag field for semver validity would receive an unexpected value. Guarding the `bb_release_tag_for_version` call to only run when `RELEASE_VERSION` is not `"unknown"` would be more consistent.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. deploy.sh now only derives a release tag when the package version is known, so unknown-version metadata no longer becomes vunknown.

This comment was generated by Pi using OpenAI GPT-5

as_agent chmod 644 "$VERSION_FILE"
log "✓ baudbot-version.json ($GIT_SHA_SHORT @ $GIT_BRANCH)"
log "✓ baudbot-version.json ($RELEASE_VERSION, $GIT_SHA_SHORT @ $GIT_BRANCH)"

# Generate sha256 manifest of all deployed files (excluding node_modules)
# Agent reads its own files to compute hashes
Expand Down
16 changes: 12 additions & 4 deletions bin/lib/baudbot-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ has_systemd() {
print_deployed_version() {
local agent_user="${BAUDBOT_AGENT_USER:-baudbot_agent}"
local version_file="/home/$agent_user/.pi/agent/baudbot-version.json"
local version=""
local tag=""
local short=""
local sha=""
local branch=""
local deployed_at=""
local line=""

if [ -r "$version_file" ]; then
version="$(json_get_string_or_empty "$version_file" "version")"
tag="$(json_get_string_or_empty "$version_file" "tag")"
short="$(json_get_string_or_empty "$version_file" "short")"
sha="$(json_get_string_or_empty "$version_file" "sha")"
branch="$(json_get_string_or_empty "$version_file" "branch")"
Expand All @@ -28,21 +32,23 @@ print_deployed_version() {
local version_json=""
version_json="$(sudo -u "$agent_user" sh -c "cat '$version_file' 2>/dev/null" || true)"
if [ -n "$version_json" ]; then
version="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "version" 2>/dev/null || true)"
tag="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "tag" 2>/dev/null || true)"
short="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "short" 2>/dev/null || true)"
sha="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "sha" 2>/dev/null || true)"
branch="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "branch" 2>/dev/null || true)"
deployed_at="$(printf '%s' "$version_json" | json_get_string_stdin_or_empty "deployed_at" 2>/dev/null || true)"
fi
fi

if [ -z "$short" ] && [ -z "$sha" ] && [ -z "$branch" ] && [ -z "$deployed_at" ]; then
if [ -z "$version" ] && [ -z "$short" ] && [ -z "$sha" ] && [ -z "$branch" ] && [ -z "$deployed_at" ]; then
local release_target=""
local release_sha=""

release_target="$(readlink -f /opt/baudbot/current 2>/dev/null || true)"
if printf '%s\n' "$release_target" | grep -Eq '/releases/[0-9a-f]{7,40}$'; then
release_sha="${release_target##*/}"
echo -e "${BOLD}deployed version:${RESET} ${release_sha:0:7} sha: $release_sha (from /opt/baudbot/current)"
echo -e "${BOLD}deployed version:${RESET} unknown (${release_sha:0:7}) sha: $release_sha (from /opt/baudbot/current)"
else
echo -e "${BOLD}deployed version:${RESET} unavailable"
fi
Expand All @@ -53,8 +59,10 @@ print_deployed_version() {
short="${sha:0:7}"
fi

line="${short:-unknown}"
[ -n "$branch" ] && line="$line (branch: $branch)"
line="${version:-unknown}"
[ -n "$short" ] && line="$line ($short)"
[ -n "$tag" ] && line="$line tag: $tag"
[ -n "$branch" ] && line="$line branch: $branch"
[ -n "$deployed_at" ] && line="$line deployed: $deployed_at"
[ -n "$sha" ] && line="$line sha: $sha"

Expand Down
8 changes: 7 additions & 1 deletion bin/lib/release-runtime-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ bb_verify_deployed_release_sha() {

local version_file="$BAUDBOT_AGENT_HOME/.pi/agent/baudbot-version.json"
local deployed_sha
local deployed_version

deployed_sha="$(sudo -u "$BAUDBOT_AGENT_USER" sh -c "cat '$version_file' 2>/dev/null" | json_get_string_stdin "sha" 2>/dev/null || true)"
deployed_version="$(sudo -u "$BAUDBOT_AGENT_USER" sh -c "cat '$version_file' 2>/dev/null" | json_get_string_stdin "version" 2>/dev/null || true)"

if [ -z "$deployed_sha" ]; then
die "deployed version file missing or unreadable: $version_file"
Expand All @@ -71,6 +73,10 @@ bb_verify_deployed_release_sha() {
fi

if [ -n "$verified_label" ]; then
log "deployed version verified: $verified_label"
if [ -n "$deployed_version" ]; then
log "deployed version verified: $deployed_version ($verified_label)"
else
log "deployed version verified: $verified_label"
fi
fi
}
37 changes: 37 additions & 0 deletions bin/lib/version-common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
# Shared version helpers for Baudbot shell scripts.
# Prerequisite: callers must source bin/lib/json-common.sh before this file.

bb_package_json_path() {
local root="${1:?repo root required}"
echo "$root/package.json"
}

bb_package_lock_json_path() {
local root="${1:?repo root required}"
echo "$root/package-lock.json"
}

bb_package_version() {
local root="${1:?repo root required}"
local package_json=""

package_json="$(bb_package_json_path "$root")"
[ -r "$package_json" ] || return 1
if ! command -v json_get_string >/dev/null 2>&1; then
echo "json_get_string unavailable; source bin/lib/json-common.sh before version-common.sh" >&2
return 1
fi

json_get_string "$package_json" "version"
}
Comment on lines +15 to +27
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Undocumented dependency on json_get_string from json-common.sh

bb_package_version calls json_get_string, which is defined in bin/lib/json-common.sh and not in this file. All current callers (deploy.sh, update-release.sh, baudbot, the test file) correctly source json-common.sh first, but there is no declaration of this requirement in version-common.sh itself. A sourcing guard or a comment at the top noting the prerequisite would prevent subtle breakage if this helper is ever sourced in a new context without the dependency.

Prompt To Fix With AI
This is a comment left during a code review.
Path: bin/lib/version-common.sh
Line: 14-22

Comment:
**Undocumented dependency on `json_get_string` from `json-common.sh`**

`bb_package_version` calls `json_get_string`, which is defined in `bin/lib/json-common.sh` and not in this file. All current callers (`deploy.sh`, `update-release.sh`, `baudbot`, the test file) correctly source `json-common.sh` first, but there is no declaration of this requirement in `version-common.sh` itself. A sourcing guard or a comment at the top noting the prerequisite would prevent subtle breakage if this helper is ever sourced in a new context without the dependency.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by documenting the json-common.sh prerequisite and adding a runtime guard in bb_package_version so a missing json_get_string fails clearly instead of breaking later.

This comment was generated by Pi using OpenAI GPT-5


bb_package_version_or_unknown() {
local root="${1:?repo root required}"
bb_package_version "$root" 2>/dev/null || echo "unknown"
}

bb_release_tag_for_version() {
local version="${1:?version required}"
echo "v$version"
}
66 changes: 66 additions & 0 deletions bin/lib/version-common.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/bash
# Tests for bin/lib/version-common.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=bin/lib/json-common.sh
source "$SCRIPT_DIR/json-common.sh"
# shellcheck source=bin/lib/version-common.sh
source "$SCRIPT_DIR/version-common.sh"

TOTAL=0
PASSED=0
FAILED=0

run_test() {
local name="$1"
shift
local out

TOTAL=$((TOTAL + 1))
printf " %-45s " "$name"

out="$(mktemp /tmp/baudbot-version-common-test-output.XXXXXX)"
if "$@" >"$out" 2>&1; then
echo "✓"
PASSED=$((PASSED + 1))
else
echo "✗ FAILED"
tail -40 "$out" | sed 's/^/ /'
FAILED=$((FAILED + 1))
fi
rm -f "$out"
}

test_reads_package_version() {
(
set -euo pipefail
local tmp
tmp="$(mktemp -d /tmp/baudbot-version-common.XXXXXX)"
trap 'rm -rf "$tmp"' EXIT

printf '{"version":"1.2.3"}\n' > "$tmp/package.json"
[ "$(bb_package_version "$tmp")" = "1.2.3" ]
)
}

test_formats_release_tag() {
(
set -euo pipefail
[ "$(bb_release_tag_for_version "2.3.4")" = "v2.3.4" ]
)
}

echo "=== version-common tests ==="
echo ""

run_test "reads package.json version" test_reads_package_version
run_test "formats release tag" test_formats_release_tag

echo ""
echo "=== $PASSED/$TOTAL passed, $FAILED failed ==="

if [ "$FAILED" -gt 0 ]; then
exit 1
fi
1 change: 1 addition & 0 deletions bin/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ run_shell_tests() {
run "doctor lib helpers" bash bin/lib/doctor-common.test.sh
run "update release flow" bash bin/update-release.test.sh
run "rollback release" bash bin/rollback-release.test.sh
run "version common" bash bin/lib/version-common.test.sh
echo ""
}

Expand Down
6 changes: 6 additions & 0 deletions bin/update-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ source "$SCRIPT_DIR/lib/release-common.sh"
source "$SCRIPT_DIR/lib/release-runtime-common.sh"
# shellcheck source=bin/lib/json-common.sh
source "$SCRIPT_DIR/lib/json-common.sh"
# shellcheck source=bin/lib/version-common.sh
source "$SCRIPT_DIR/lib/version-common.sh"

# ---------------------------------------------------------------------------
# Resolve the full path to npm. This script runs as root (sudo) where the
Expand Down Expand Up @@ -239,12 +241,16 @@ write_release_metadata() {
local branch="$3"
local deployed_by
local built_at
local release_version=""

deployed_by="${SUDO_USER:-$(whoami)}"
built_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
release_version="$(bb_package_version_or_unknown "$release_dir")"

cat > "$release_dir/baudbot-release.json" <<EOF
{
"version": "$release_version",
"tag": "$(bb_release_tag_for_version "$release_version")",
"sha": "$TARGET_SHA",
"short": "$TARGET_SHORT",
"branch": "$branch",
Expand Down
2 changes: 2 additions & 0 deletions bin/update-release.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ test_publish_git_free_release() {

[ "$current_target" = "$release_root/releases/$sha" ]
[ -f "$current_target/baudbot-release.json" ]
grep -q '"version": "0.1.0"' "$current_target/baudbot-release.json"
grep -q '"tag": "v0.1.0"' "$current_target/baudbot-release.json"
# Release root must be traversable so /usr/local/bin/baudbot is discoverable.
[ "$(stat -c '%a' "$current_target")" = "755" ]
assert_no_git_dirs "$release_root/releases"
Expand Down
9 changes: 5 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Baudbot runs live operations from release snapshots under `/opt/baudbot`, with a
```text
root-managed releases
├── /opt/baudbot/
│ ├── releases/<sha>/ # immutable, git-free snapshots
│ ├── releases/<sha>/ # immutable, git-free snapshots with semver metadata
│ ├── current -> releases/<sha>
│ └── previous -> releases/<sha>

Expand All @@ -26,10 +26,11 @@ baudbot_agent user
1. Update is initiated from a target ref/repo.
2. Deploy/update scripts build a staged snapshot.
3. Snapshot is published to `/opt/baudbot/releases/<sha>`.
4. Runtime files are deployed for `baudbot_agent`.
5. Symlink switch (`current`) is updated atomically on success.
4. Release metadata records both semver (`package.json.version`) and git SHA provenance.
5. Runtime files are deployed for `baudbot_agent`.
6. Symlink switch (`current`) is updated atomically on success.

This allows reproducible releases and fast rollback.
This allows reproducible releases, semver-based operator visibility, and fast rollback.

## Agent topology

Expand Down
2 changes: 2 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ sudo baudbot update
sudo baudbot rollback previous
```

Release versions are driven by `package.json.version`, while production snapshots remain SHA-addressed under `/opt/baudbot/releases/<sha>` for immutability and rollback safety. Normal PR merges do not cut versions automatically; run the **Release on main** GitHub Actions workflow when you want to publish a semver tag/release.

Provision with a pinned pi version (optional):

```bash
Expand Down
Loading
Loading