Repository navigation
Expand file tree
/
Copy pathbuild_omnibioai_ecosystem.sh
More file actions
168 lines (141 loc) · 7.42 KB
/
Copy pathbuild_omnibioai_ecosystem.sh
File metadata and controls
168 lines (141 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env bash
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ecosystem_path.sh"
# utils/build_omnibioai_ecosystem.sh
#
# Orchestrates a full ecosystem build:
# 1. Verify all repos are clean and on main (via ecosystem_status.sh)
# 2. Run coverage host
# 3. Generate report
# 4. Build and (re)start omnibioai-studio via docker compose
#
# Usage: bash omnibioai-utils/build_omnibioai_ecosystem.sh [root_dir]
# Default root: "$OMNIBIOAI_ROOT"
set -euo pipefail
ROOT="${1:-$OMNIBIOAI_ROOT}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
RESET='\033[0m'
# Resolve this script's directory so we can find sibling scripts regardless
# of where the caller invokes it from.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DISCORD_EVENT_CLIENT="${ROOT}/omnibioai-control-center/scripts/publish_discord_event.py"
step() {
echo ""
echo -e "${BOLD}==> $1${RESET}"
}
# Publish a sanitized Discord alert through the central router only. Failure
# is returned to the caller for a generic warning; it never changes the
# build's original failure exit status. Returns 2 when the router is not
# configured.
discord_alert() {
local message="$1" commit_id event_hash event_id approval_arg=()
commit_id="$(git -C "$ROOT/omnibioai-control-center" rev-parse --short=12 HEAD 2>/dev/null || echo unknown)"
event_hash="$(printf '%s' "$message" | sha256sum | cut -c1-16)"
event_id="ecosystem-build:${commit_id}:${event_hash}"
if [ "${DISCORD_INFRA_EVENTS_PREAPPROVED:-false}" = "true" ]; then
approval_arg=(--approved)
fi
if [ -x "$DISCORD_EVENT_CLIENT" ] && [ -n "${DISCORD_EVENT_ROUTER_URL:-}" ] && [ -n "${DISCORD_EVENT_SOURCE_SECRET:-}" ]; then
if "$DISCORD_EVENT_CLIENT" \
--event-id "$event_id" \
--event-type infrastructure.alert \
--source infrastructure \
--title "OmniBioAI ecosystem build failed" \
--message "A platform build failed. Authorized operators should inspect the internal build logs." \
--correlation-id "$event_id" \
--color error "${approval_arg[@]}" >/dev/null 2>&1; then
return 0
fi
# A timeout may occur after the router durably accepted the event;
# do not retry and risk duplicate posting.
return 1
fi
return 2
}
# Pass "docker" as $2 when the failure happened around a docker compose call
# so the current (possibly mixed) container state lands in the log right
# away, instead of requiring someone to SSH in and check.
fail() {
echo -e "${RED}${BOLD}✗ $1${RESET}"
if [ "${2:-}" = "docker" ]; then
echo -e "${YELLOW}${BOLD}--- docker compose ps (current state) ---${RESET}"
(cd "$STUDIO_DIR" && docker compose ps) 2>&1 || true
fi
if ! discord_alert "$1"; then
echo -e "${YELLOW}Discord alert was not delivered or is not configured; inspect the notification configuration.${RESET}" >&2
fi
exit 1
}
ok() {
echo -e "${GREEN}$1${RESET}"
}
# ---------------------------------------------------------------------------
# Step 1: Ecosystem status — must be all clean and all on main
# ---------------------------------------------------------------------------
step "Step 1/4: Checking ecosystem git status (${SCRIPT_DIR}/ecosystem_status.sh)"
STATUS_OUTPUT="$(bash "$SCRIPT_DIR/ecosystem_status.sh" "$ROOT" 2>&1)"
# Print it so the user sees the full table, but strip ANSI color codes first
# so the terminal doesn't show raw escape sequences if something goes odd.
echo "$STATUS_OUTPUT" | sed -E 's/\x1B\[[0-9;]*[a-zA-Z]//g'
PLAIN_OUTPUT="$(echo "$STATUS_OUTPUT" | sed -E 's/\x1B\[[0-9;]*[a-zA-Z]//g')"
DIRTY_COUNT="$(echo "$PLAIN_OUTPUT" | grep -E '^\s*Dirty\s*:' | grep -oE '[0-9]+' | head -1)"
NONMAIN_COUNT="$(echo "$PLAIN_OUTPUT" | grep -E '^\s*Non-main branch\s*:' | grep -oE '[0-9]+' | head -1)"
DIRTY_COUNT="${DIRTY_COUNT:-0}"
NONMAIN_COUNT="${NONMAIN_COUNT:-0}"
if [ "$DIRTY_COUNT" -ne 0 ] || [ "$NONMAIN_COUNT" -ne 0 ]; then
echo ""
fail "Ecosystem is not ready: ${DIRTY_COUNT} dirty repo(s), ${NONMAIN_COUNT} repo(s) not on main. Fix these before building."
fi
ok "✓ All repos clean and on main. Proceeding to Step 2."
# ---------------------------------------------------------------------------
# Step 2: Run coverage host
# ---------------------------------------------------------------------------
step "Step 2/4: Running coverage host (omnibioai-control-center/scripts/run_coverage_host.py)"
if ! python "$ROOT/omnibioai-control-center/scripts/run_coverage_host.py" --root "$ROOT"; then
fail "run_coverage_host.py failed. Aborting before generating report."
fi
ok "✓ Coverage host completed. Proceeding to Step 3."
# ---------------------------------------------------------------------------
# Step 3: Generate report
# ---------------------------------------------------------------------------
step "Step 3/4: Generating report (omnibioai-control-center/scripts/generate_report.py)"
if ! python "$ROOT/omnibioai-control-center/scripts/generate_report.py"; then
fail "generate_report.py failed. Aborting before docker build."
fi
ok "✓ Report generated with no errors. Proceeding to Step 4."
# ---------------------------------------------------------------------------
# Step 4: Build and start omnibioai-studio (changed services only)
# ---------------------------------------------------------------------------
step "Step 4/4: Building and starting omnibioai-studio (docker compose)"
STUDIO_DIR="$ROOT/omnibioai-studio"
[ -d "$STUDIO_DIR" ] || fail "Studio directory not found: $STUDIO_DIR"
# redis-acl-init's host mounts are launcher-supplied, not .env settings --
# same defaults as omnibioai-studio/scripts/start.sh (and electron/main.js
# in dev mode), all relative to the Studio checkout. Caller overrides win.
export REDIS_ACL_CREDENTIAL_DIR="${REDIS_ACL_CREDENTIAL_DIR:-$STUDIO_DIR/.secrets/redis-acl}"
export REDIS_ACL_SCRIPT_PATH="${REDIS_ACL_SCRIPT_PATH:-$STUDIO_DIR/scripts/redis_acl_bootstrap.py}"
export REDIS_ACL_POLICY_PATH="${REDIS_ACL_POLICY_PATH:-$STUDIO_DIR/config/redis/acl-policy.json}"
# Fail before touching compose if any mount source is missing: docker would
# otherwise silently create it as an empty root-owned directory on the host.
# The credential dir is populated only by the Studio launcher / the protected
# ACL adoption workflow (omnibioai-studio/docs/security/redis_acl_bootstrap.md),
# never by this script.
[ -f "$REDIS_ACL_SCRIPT_PATH" ] || fail "Redis ACL bootstrap script not found: $REDIS_ACL_SCRIPT_PATH"
[ -f "$REDIS_ACL_POLICY_PATH" ] || fail "Redis ACL policy not found: $REDIS_ACL_POLICY_PATH"
[ -d "$REDIS_ACL_CREDENTIAL_DIR" ] || fail "Redis ACL credential directory not found: $REDIS_ACL_CREDENTIAL_DIR (create it via the Studio launcher / ACL adoption workflow, not this script)"
# Plain `up -d` recreates only services whose image or config changed, so
# unchanged stateful services (redis, mysql, ...) keep running. nginx-router
# is the one exception: it caches upstream IPs at startup, so any recreated
# backend would 502 behind it (docs/DEPLOYMENT.md). It's stateless, so it
# alone is force-recreated, without touching its dependencies.
(
cd "$STUDIO_DIR"
docker compose build
docker compose up -d
docker compose up -d --no-deps --force-recreate nginx-router
) || fail "Docker compose build/up failed for omnibioai-studio." docker
ok "✓ omnibioai-studio built and running."
echo ""
echo -e "${BOLD}${GREEN}All 4 steps completed successfully.${RESET}"