Report build starts and successes, not just failures, to their own chat
Only failures were reported, so a push could reach the mirror with no way to know short of querying the database by hand. Release runs now report: - start: channel, arch, host, and the commit being built - published: the packages and versions that went out, duration, channel URL - no-changes: the run found nothing to build - promoted: what advance moved between channels, including the rc bootstrap and fast-ring replication - failed: unchanged, plus the commit context the other reports carry Release traffic goes to OMARCHY_RELEASE_CHATBOT_URL, falling back to BASECAMP_CHATBOT_URL, so build reports stop drowning the repository chat the sync workflows post to. bin/setup reports which destination is configured. The published list is captured after the build step because promote moves the files out of build-output, and is capped at 25 entries so a full rebuild does not produce an unreadable wall of chat.
This commit is contained in:
@@ -756,6 +756,31 @@ That cadence is only safe because of three guards:
|
|||||||
`bin/repo timers` reports all of this: schedules, last results, what is
|
`bin/repo timers` reports all of this: schedules, last results, what is
|
||||||
queued, what is failing and when it will retry, and whether the lock is held.
|
queued, what is failing and when it will retry, and whether the lock is held.
|
||||||
|
|
||||||
|
### Build reports
|
||||||
|
|
||||||
|
Every release run reports to Basecamp Campfire — not just failures, so a push
|
||||||
|
can be followed all the way to the mirror without watching the host:
|
||||||
|
|
||||||
|
| Event | Message |
|
||||||
|
|---|---|
|
||||||
|
| 🔨 Started | channel, arch, host, and the commit being built |
|
||||||
|
| ✅ Published | the packages and versions that went out, duration, and the channel URL |
|
||||||
|
| 📦 Promoted | what `advance` moved between channels (including the rc bootstrap and fast-ring replication) |
|
||||||
|
| 📦 No changes | the run found nothing to build |
|
||||||
|
| 🔴 Failed | which step failed, the commit, and the last 25 log lines |
|
||||||
|
|
||||||
|
Release traffic gets its own chat so it does not drown the repository chat
|
||||||
|
that the AUR/upstream sync workflows post to. Export the destination in
|
||||||
|
`/root/.omarchy/build-credentials`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OMARCHY_RELEASE_CHATBOT_URL="https://3.basecamp.com/<account>/integrations/<key>/buckets/<project>/chats/<chat>/lines"
|
||||||
|
```
|
||||||
|
|
||||||
|
Without it, release reports fall back to `BASECAMP_CHATBOT_URL`; with neither
|
||||||
|
set they are silently skipped. `bin/setup` reports which of the three cases
|
||||||
|
applies.
|
||||||
|
|
||||||
Check on all of it with `bin/repo timers` — schedule, each unit's last run and
|
Check on all of it with `bin/repo timers` — schedule, each unit's last run and
|
||||||
whether it succeeded, what is queued, whether a release is running right now,
|
whether it succeeded, what is queued, whether a release is running right now,
|
||||||
and any failed units. Like the other host commands it forwards over ssh, so
|
and any failed units. Like the other host commands it forwards over ssh, so
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ source "$BUILD_ROOT/helpers/message-helpers.sh"
|
|||||||
source "$BUILD_ROOT/helpers/paths.sh"
|
source "$BUILD_ROOT/helpers/paths.sh"
|
||||||
source "$BUILD_ROOT/helpers/lock-helpers.sh"
|
source "$BUILD_ROOT/helpers/lock-helpers.sh"
|
||||||
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
source "$BUILD_ROOT/helpers/package-metadata.sh"
|
||||||
|
source "$BUILD_ROOT/helpers/basecamp-notifier.sh"
|
||||||
|
|
||||||
FROM=""
|
FROM=""
|
||||||
TO=""
|
TO=""
|
||||||
@@ -223,6 +224,7 @@ package_eligible() {
|
|||||||
mkdir -p "$TARGET_DIR"
|
mkdir -p "$TARGET_DIR"
|
||||||
|
|
||||||
COPIED=0
|
COPIED=0
|
||||||
|
COPIED_FILES=""
|
||||||
PRESENT=0
|
PRESENT=0
|
||||||
SKIPPED=0
|
SKIPPED=0
|
||||||
MISSING_FILES=()
|
MISSING_FILES=()
|
||||||
@@ -277,6 +279,7 @@ while IFS=$'\t' read -r name base filename; do
|
|||||||
echo " copied: $filename (+ .sig)"
|
echo " copied: $filename (+ .sig)"
|
||||||
fi
|
fi
|
||||||
COPIED=$((COPIED + 1))
|
COPIED=$((COPIED + 1))
|
||||||
|
COPIED_FILES+="$filename"$'\n'
|
||||||
done < <(read_manifest)
|
done < <(read_manifest)
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -330,3 +333,23 @@ SYNC_ARGS=("--mirror" "$TO" "--arch" "$ARCH")
|
|||||||
}
|
}
|
||||||
|
|
||||||
print_success "Advance complete: $FROM -> $TO"
|
print_success "Advance complete: $FROM -> $TO"
|
||||||
|
|
||||||
|
# A promotion is how something reaches users, so say what moved and where.
|
||||||
|
if ((COPIED > 0)); then
|
||||||
|
moved=""
|
||||||
|
shown=0
|
||||||
|
while IFS= read -r moved_file; do
|
||||||
|
[[ -z "$moved_file" ]] && continue
|
||||||
|
((shown >= 25)) && break
|
||||||
|
moved+="<br>• $(package_file_label "$moved_file" | basecamp_html_escape)"
|
||||||
|
shown=$((shown + 1))
|
||||||
|
done <<<"$COPIED_FILES"
|
||||||
|
((COPIED > shown)) && moved+="<br>• …and $((COPIED - shown)) more"
|
||||||
|
|
||||||
|
label="Promoted $FROM → $TO"
|
||||||
|
[[ "$BOOTSTRAP" == true ]] && label="Bootstrapped the $TO channel from $FROM"
|
||||||
|
[[ "$FAST_RING_ONLY" == true ]] && label="Replicated fast-ring $FROM → $TO"
|
||||||
|
|
||||||
|
notify_info "$label" \
|
||||||
|
"Arch: $ARCH · $(hostname -s 2>/dev/null || echo host)<br><strong>$COPIED package(s) moved:</strong>$moved<br><br>Live at https://pkgs.omarchy.org/$TO/$ARCH/"
|
||||||
|
fi
|
||||||
|
|||||||
+44
-7
@@ -92,6 +92,24 @@ if [[ "$DRY_RUN" != true ]]; then
|
|||||||
acquire_release_lock || exit 1
|
acquire_release_lock || exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- reporting ---------------------------------------------------------------
|
||||||
|
|
||||||
|
RELEASE_STARTED_AT=$(date +%s)
|
||||||
|
RELEASE_COMMIT=$(git -C "$BUILD_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
|
RELEASE_COMMIT_SUBJECT=$(git -C "$BUILD_ROOT" log -1 --pretty=%s 2>/dev/null || echo "")
|
||||||
|
RELEASE_CONTEXT="Channel: <strong>$MIRROR</strong> · Arch: $ARCH · $(hostname -s 2>/dev/null || echo host)"
|
||||||
|
if [[ "$RELEASE_COMMIT" != "unknown" ]]; then
|
||||||
|
RELEASE_CONTEXT+="<br>Commit: <code>$RELEASE_COMMIT</code> $(basecamp_html_escape <<<"$RELEASE_COMMIT_SUBJECT")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Captured after the build step: promote MOVES these files out of build-output,
|
||||||
|
# so the list has to be taken while they are still there.
|
||||||
|
BUILT_FILES=""
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" != true ]]; then
|
||||||
|
notify_start "Release started: $MIRROR" "$RELEASE_CONTEXT"
|
||||||
|
fi
|
||||||
|
|
||||||
# Step 1: Build
|
# Step 1: Build
|
||||||
echo ""
|
echo ""
|
||||||
if [[ "$DRY_RUN" == true ]]; then
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
@@ -101,7 +119,7 @@ else
|
|||||||
fi
|
fi
|
||||||
"$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || {
|
"$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || {
|
||||||
print_error "Build failed"
|
print_error "Build failed"
|
||||||
notify_error "Release failed: Build step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Build step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,12 +129,17 @@ if [[ "$DRY_RUN" == true ]]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
BUILT_FILES=$(built_package_files "$BUILD_OUTPUT_DIR")
|
||||||
|
BUILT_COUNT=$(grep -c '' <<<"$BUILT_FILES")
|
||||||
|
[[ -z "$BUILT_FILES" ]] && BUILT_COUNT=0
|
||||||
|
print_info "Built $BUILT_COUNT package(s) this run"
|
||||||
|
|
||||||
# Step 2: Sign
|
# Step 2: Sign
|
||||||
echo ""
|
echo ""
|
||||||
print_info "Step 2/6: Signing packages..."
|
print_info "Step 2/6: Signing packages..."
|
||||||
"$BUILD_ROOT/bin/sign" --arch "$ARCH" --mirror "$MIRROR" || {
|
"$BUILD_ROOT/bin/sign" --arch "$ARCH" --mirror "$MIRROR" || {
|
||||||
print_error "Signing failed"
|
print_error "Signing failed"
|
||||||
notify_error "Release failed: Signing step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Signing step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +148,7 @@ echo ""
|
|||||||
print_info "Step 3/6: Promoting to production..."
|
print_info "Step 3/6: Promoting to production..."
|
||||||
"$BUILD_ROOT/bin/promote-build" --arch "$ARCH" --mirror "$MIRROR" || {
|
"$BUILD_ROOT/bin/promote-build" --arch "$ARCH" --mirror "$MIRROR" || {
|
||||||
print_error "Promotion failed"
|
print_error "Promotion failed"
|
||||||
notify_error "Release failed: Promotion step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Promotion step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +157,7 @@ echo ""
|
|||||||
print_info "Step 4/6: Cleaning old versions..."
|
print_info "Step 4/6: Cleaning old versions..."
|
||||||
"$BUILD_ROOT/bin/clean-repo" --arch "$ARCH" --mirror "$MIRROR" || {
|
"$BUILD_ROOT/bin/clean-repo" --arch "$ARCH" --mirror "$MIRROR" || {
|
||||||
print_error "Cleaning failed"
|
print_error "Cleaning failed"
|
||||||
notify_error "Release failed: Clean step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Clean step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +166,7 @@ echo ""
|
|||||||
print_info "Step 5/6: Updating repository database..."
|
print_info "Step 5/6: Updating repository database..."
|
||||||
"$BUILD_ROOT/bin/update-repo" --arch "$ARCH" --mirror "$MIRROR" || {
|
"$BUILD_ROOT/bin/update-repo" --arch "$ARCH" --mirror "$MIRROR" || {
|
||||||
print_error "Database update failed"
|
print_error "Database update failed"
|
||||||
notify_error "Release failed: Database update step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Database update step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +182,7 @@ if [[ "$SKIP_PROD_CHECK" == true ]]; then
|
|||||||
fi
|
fi
|
||||||
"$BUILD_ROOT/bin/sync-repo" "${SYNC_ARGS[@]}" || {
|
"$BUILD_ROOT/bin/sync-repo" "${SYNC_ARGS[@]}" || {
|
||||||
print_error "Sync failed"
|
print_error "Sync failed"
|
||||||
notify_error "Release failed: Sync step failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: Sync step failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +198,7 @@ if [[ "$MIRROR" == "stable" ]]; then
|
|||||||
[[ "$SKIP_PROD_CHECK" == true ]] && REPLICATE_ARGS+=(--skip-prod-check)
|
[[ "$SKIP_PROD_CHECK" == true ]] && REPLICATE_ARGS+=(--skip-prod-check)
|
||||||
"$BUILD_ROOT/bin/advance-channel" "${REPLICATE_ARGS[@]}" || {
|
"$BUILD_ROOT/bin/advance-channel" "${REPLICATE_ARGS[@]}" || {
|
||||||
print_error "Fast-ring replication to rc failed"
|
print_error "Fast-ring replication to rc failed"
|
||||||
notify_error "Release failed: rc parity replication failed" "Mirror: $MIRROR | Arch: $ARCH"
|
notify_error "Release failed: rc parity replication failed" "$RELEASE_CONTEXT"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -185,3 +208,17 @@ fi
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
print_success "Release workflow completed successfully!"
|
print_success "Release workflow completed successfully!"
|
||||||
|
|
||||||
|
duration=$(format_duration $(($(date +%s) - RELEASE_STARTED_AT)))
|
||||||
|
summary="$RELEASE_CONTEXT<br>Duration: $duration"
|
||||||
|
if ((BUILT_COUNT > 0)); then
|
||||||
|
summary+="<br><br><strong>$BUILT_COUNT package(s) published:</strong>"
|
||||||
|
summary+="$(format_package_list_html "$BUILT_FILES")"
|
||||||
|
summary+="<br><br>Live at https://pkgs.omarchy.org/$MIRROR/$ARCH/"
|
||||||
|
notify_success "Release published: $MIRROR" "$summary"
|
||||||
|
else
|
||||||
|
# Nothing to build usually means a queued run raced another that already did
|
||||||
|
# the work. Worth saying, but not worth dressing up as a publish.
|
||||||
|
summary+="<br>No packages needed building — the channel was already current."
|
||||||
|
notify_info "Release complete (no changes): $MIRROR" "$summary"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -274,6 +274,22 @@ else
|
|||||||
echo " services source it before signing."
|
echo " services source it before signing."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Release reporting is optional, but silence is indistinguishable from a
|
||||||
|
# broken hook, so say which chat (if any) will receive build reports.
|
||||||
|
if [[ -f "$CREDENTIALS" ]]; then
|
||||||
|
if grep -q 'OMARCHY_RELEASE_CHATBOT_URL' "$CREDENTIALS"; then
|
||||||
|
print_success "Release reports go to the dedicated release chat"
|
||||||
|
elif grep -q 'BASECAMP_CHATBOT_URL' "$CREDENTIALS"; then
|
||||||
|
print_warning "Release reports fall back to BASECAMP_CHATBOT_URL (the repository chat)"
|
||||||
|
echo " Export OMARCHY_RELEASE_CHATBOT_URL in $CREDENTIALS to send them"
|
||||||
|
echo " to their own chat instead."
|
||||||
|
else
|
||||||
|
print_warning "No chatbot URL in $CREDENTIALS — build reports are disabled"
|
||||||
|
echo " Export OMARCHY_RELEASE_CHATBOT_URL to receive start/publish/failure"
|
||||||
|
echo " reports in Basecamp."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if rclone listremotes 2>/dev/null | grep -q '^pkgs.omarchy.org:'; then
|
if rclone listremotes 2>/dev/null | grep -q '^pkgs.omarchy.org:'; then
|
||||||
print_success "rclone remote 'pkgs.omarchy.org' configured"
|
print_success "rclone remote 'pkgs.omarchy.org' configured"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,13 +1,24 @@
|
|||||||
# Send notifications to Basecamp Campfire via chatbot
|
# Send notifications to Basecamp Campfire via chatbot
|
||||||
#
|
#
|
||||||
# Requires BASECAMP_CHATBOT_URL environment variable to be set.
|
# Two destinations, so release traffic does not drown the repository chat:
|
||||||
# This is the full chatbot lines URL from Basecamp, e.g.:
|
#
|
||||||
|
# OMARCHY_RELEASE_CHATBOT_URL release activity (starts, publishes, failures)
|
||||||
|
# BASECAMP_CHATBOT_URL everything else, and the fallback when the
|
||||||
|
# release URL is unset
|
||||||
|
#
|
||||||
|
# Each is the full chatbot lines URL from Basecamp, e.g.
|
||||||
# https://3.basecamp.com/<account>/integrations/<key>/buckets/<project>/chats/<chat>/lines
|
# https://3.basecamp.com/<account>/integrations/<key>/buckets/<project>/chats/<chat>/lines
|
||||||
#
|
#
|
||||||
# If BASECAMP_CHATBOT_URL is not set, notifications are silently skipped.
|
# With neither set, notifications are silently skipped.
|
||||||
|
|
||||||
|
release_chatbot_url() {
|
||||||
|
echo "${OMARCHY_RELEASE_CHATBOT_URL:-${BASECAMP_CHATBOT_URL:-}}"
|
||||||
|
}
|
||||||
|
|
||||||
notify_basecamp() {
|
notify_basecamp() {
|
||||||
local content="$1"
|
local content="$1"
|
||||||
|
local BASECAMP_CHATBOT_URL
|
||||||
|
BASECAMP_CHATBOT_URL=$(release_chatbot_url)
|
||||||
|
|
||||||
if [[ -z "${BASECAMP_CHATBOT_URL:-}" ]]; then
|
if [[ -z "${BASECAMP_CHATBOT_URL:-}" ]]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -60,3 +71,89 @@ notify_error() {
|
|||||||
notify_basecamp "$content"
|
notify_basecamp "$content"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Human-friendly elapsed time: "8m 12s", "45s", "1h 4m".
|
||||||
|
format_duration() {
|
||||||
|
local seconds="${1:-0}"
|
||||||
|
if ((seconds >= 3600)); then
|
||||||
|
printf '%dh %dm' $((seconds / 3600)) $(((seconds % 3600) / 60))
|
||||||
|
elif ((seconds >= 60)); then
|
||||||
|
printf '%dm %ds' $((seconds / 60)) $((seconds % 60))
|
||||||
|
else
|
||||||
|
printf '%ds' "$seconds"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_start() {
|
||||||
|
local title="$1"
|
||||||
|
local details="${2:-}"
|
||||||
|
|
||||||
|
local content="🔨 <strong>${title}</strong>"
|
||||||
|
[[ -n "$details" ]] && content+="<br>${details}"
|
||||||
|
notify_basecamp "$content"
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_success() {
|
||||||
|
local title="$1"
|
||||||
|
local details="${2:-}"
|
||||||
|
|
||||||
|
local content="✅ <strong>${title}</strong>"
|
||||||
|
[[ -n "$details" ]] && content+="<br>${details}"
|
||||||
|
notify_basecamp "$content"
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_info() {
|
||||||
|
local title="$1"
|
||||||
|
local details="${2:-}"
|
||||||
|
|
||||||
|
local content="📦 <strong>${title}</strong>"
|
||||||
|
[[ -n "$details" ]] && content+="<br>${details}"
|
||||||
|
notify_basecamp "$content"
|
||||||
|
}
|
||||||
|
|
||||||
|
# "name-1.2.3-1-x86_64.pkg.tar.zst" -> "name 1.2.3-1". Package file names put
|
||||||
|
# the architecture last and the pkgrel before it, so peel from the right.
|
||||||
|
package_file_label() {
|
||||||
|
local file="$1" stem arch rest rel name_version version name
|
||||||
|
stem="${file%.pkg.tar.*}"
|
||||||
|
arch="${stem##*-}"
|
||||||
|
rest="${stem%-*}"
|
||||||
|
rel="${rest##*-}"
|
||||||
|
name_version="${rest%-*}"
|
||||||
|
version="${name_version##*-}"
|
||||||
|
name="${name_version%-*}"
|
||||||
|
printf '%s %s-%s' "$name" "$version" "$rel"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Packages sitting in a build-output directory, newest build first. Excludes
|
||||||
|
# signatures and the scratch database the builder keeps alongside them.
|
||||||
|
built_package_files() {
|
||||||
|
local dir="$1"
|
||||||
|
[[ -d "$dir" ]] || return 0
|
||||||
|
find "$dir" -maxdepth 1 -name '*.pkg.tar.*' ! -name '*.sig' -printf '%f\n' 2>/dev/null |
|
||||||
|
grep -v '^omarchy-build\.' | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# An HTML list of built packages, capped so a 60-package rebuild does not
|
||||||
|
# produce an unreadable wall of text in chat.
|
||||||
|
format_package_list_html() {
|
||||||
|
local files="$1"
|
||||||
|
local limit="${2:-25}"
|
||||||
|
local count shown=0 line out=""
|
||||||
|
|
||||||
|
count=$(grep -c '' <<<"$files")
|
||||||
|
[[ -z "$files" ]] && count=0
|
||||||
|
((count == 0)) && return 0
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
((shown >= limit)) && break
|
||||||
|
out+="<br>• $(package_file_label "$line" | basecamp_html_escape)"
|
||||||
|
shown=$((shown + 1))
|
||||||
|
done <<<"$files"
|
||||||
|
|
||||||
|
if ((count > shown)); then
|
||||||
|
out+="<br>• …and $((count - shown)) more"
|
||||||
|
fi
|
||||||
|
echo "$out"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user