#!/usr/bin/env bash # # Publish a new CRX when master moves. Run by a systemd timer. # # Bash rather than Node, matching the autotrader stack next door: nothing on # this host runs outside Docker, so there is no Node to depend on. # # Only a change under extension/ or deploy/ is worth acting on — the dashboard # and the clicker are pulled by each instance itself, and repacking for a README # edit would publish a byte-identical CRX every few minutes. # # ./auto-publish.sh # normal # ./auto-publish.sh --dry-run # report only set -euo pipefail cd "$(dirname "$0")/.." ROOT="$(pwd)" BRANCH="${AUTOPUBLISH_BRANCH:-master}" LOG="$ROOT/deploy/publish.log" LAST_CHECK="$ROOT/deploy/.last-check" LOCK="$ROOT/deploy/.publish.lock" PUBLISHED_XML="${UPDATES_DIR:-/opt/autofirmer-updates}/public/updates.xml" STALE_LOCK_SECS=3600 LOG_MAX_BYTES=262144 LOG_KEEP_LINES=500 DRY_RUN=0 [ "${1:-}" = "--dry-run" ] && DRY_RUN=1 log() { printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" | tee -a "$LOG"; } record() { printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" > "$LAST_CHECK" 2>/dev/null || true; } # Trimmed once per run, before anything is written: the no-change path writes # nothing, so this is the only moment the file can have grown. if [ -f "$LOG" ] && [ "$(stat -c%s "$LOG")" -gt "$LOG_MAX_BYTES" ]; then tail -n "$LOG_KEEP_LINES" "$LOG" > "$LOG.tmp" && mv "$LOG.tmp" "$LOG" fi if [ "$DRY_RUN" -eq 0 ] && [ -f "$LOCK" ]; then LOCK_AGE=$(( $(date +%s) - $(cat "$LOCK" 2>/dev/null || echo 0) )) if [ "$LOCK_AGE" -lt "$STALE_LOCK_SECS" ]; then exit 0 # already publishing; say nothing fi log "clearing a stale lock (${LOCK_AGE}s old)" fi [ "$DRY_RUN" -eq 0 ] && date +%s > "$LOCK" OUTCOME="interrupted" cleanup() { [ "$DRY_RUN" -eq 0 ] && { rm -f "$LOCK"; record "$OUTCOME"; }; } trap cleanup EXIT git fetch origin "$BRANCH" --quiet LOCAL=$(git rev-parse HEAD) REMOTE=$(git rev-parse "origin/$BRANCH") if [ "$LOCAL" = "$REMOTE" ]; then OUTCOME="up to date at ${LOCAL:0:8}" exit 0 # the common path: silent fi log "update available: ${LOCAL:0:8} -> ${REMOTE:0:8}" CHANGED=$(git diff --name-only "$LOCAL" "$REMOTE") TOUCHES_RELEASE=0 echo "$CHANGED" | grep -qE '^(extension|deploy)/' && TOUCHES_RELEASE=1 if [ "$DRY_RUN" -eq 1 ]; then log "dry run - would pull; release-relevant changes: $TOUCHES_RELEASE" exit 0 fi git pull --ff-only origin "$BRANCH" --quiet if [ "$TOUCHES_RELEASE" -eq 0 ]; then # Pulled so the checkout stays current, but nothing that ships changed. OUTCOME="pulled ${REMOTE:0:8}, nothing to publish" log "$OUTCOME" exit 0 fi # The guard worth having. Chrome ignores a CRX that is not strictly newer than # what it already has, so republishing at the same version is a silent no-op — # the kind you discover days later, when a fix everyone believed had shipped # turns out never to have left the building. VERSION=$(python3 -c 'import json;print(json.load(open("extension/manifest.json"))["version"])') LIVE=$(sed -n 's/.*[^a-z]version="\([0-9][^"]*\)".*/\1/p' "$PUBLISHED_XML" 2>/dev/null | head -1) if echo "$CHANGED" | grep -qE '^extension/' && [ "$VERSION" = "$LIVE" ]; then log "REFUSED: extension/ changed but manifest.json is still $VERSION, which is" log " already published. Chrome would ignore the new CRX. Bump the" log " version and push again." OUTCOME="refused - version $VERSION not bumped" exit 1 fi if ./deploy/deploy.sh >> "$LOG" 2>&1; then OUTCOME="published $VERSION at ${REMOTE:0:8}" log "$OUTCOME" else log "ABORTED: deploy.sh failed - the previous CRX is still being served" OUTCOME="publish failed - not switched" exit 1 fi