-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathself-update.sh
More file actions
executable file
·133 lines (108 loc) · 4.67 KB
/
self-update.sh
File metadata and controls
executable file
·133 lines (108 loc) · 4.67 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
#!/usr/bin/env bash
# claude-alloy self-update checker
# Called from activate.sh in a subshell — failures here never block activation.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLAUDE_DIR="${HOME}/.claude"
NO_UPDATE_FILE="${CLAUDE_DIR}/.alloy-no-update"
STATE_DIR="${CLAUDE_DIR}/.alloy-state"
LAST_CHECK_FILE="${STATE_DIR}/last-update-check"
BLUE='\033[0;34m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${BLUE}[ALLOY]${NC} $1"; }
success() { echo -e "${GREEN}[ALLOY]${NC} $1"; }
warn() { echo -e "${YELLOW}[ALLOY]${NC} $1"; }
# --- Opt-out checks ---
# Persistent file-based opt-out
[ -f "$NO_UPDATE_FILE" ] && exit 0
# Env var opt-out
[ "${ALLOY_AUTO_UPDATE:-}" = "0" ] && exit 0
# CLI flag opt-out (--skip-update passed from activate.sh)
for arg in "$@"; do
[ "$arg" = "--skip-update" ] && exit 0
done
# CLI flag / env bypass for the rate-limit gate below.
# Users who explicitly ask for an immediate check should not be forced
# to wait for the 7-day window.
FORCE_CHECK=false
if [ "${ALLOY_FORCE_UPDATE:-}" = "1" ]; then
FORCE_CHECK=true
fi
for arg in "$@"; do
[ "$arg" = "--force-update" ] && FORCE_CHECK=true
done
# --- Git repo checks ---
# Not a git repo (tarball install) — skip silently
[ -d "$SCRIPT_DIR/.git" ] || exit 0
cd "$SCRIPT_DIR" || exit 0
# Not on main branch — skip silently
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
[ "$BRANCH" = "main" ] || exit 0
# Remote URL safety check — only fetch from known origin
REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")
# Exact match (not substring) — prevents e.g. github.com/OMARVII/claude-alloy-evil from passing
NORMALIZED_REMOTE="$(echo "$REMOTE_URL" | sed -E 's#^(https://)?(git@)?github\.com[:/]##; s#\.git$##')"
if [ "$NORMALIZED_REMOTE" != "OMARVII/claude-alloy" ]; then
exit 0
fi
# --- Rate limit: at most one network check every 7 days ---
# The state file's mtime records the last successful fetch attempt. If it is
# less than 7 days old, skip silently so activation stays fast and we honor
# the "at most once every 7 days" commitment in PRIVACY.md. Users who want to
# bypass this can pass --force-update or set ALLOY_FORCE_UPDATE=1; the earlier
# opt-outs (ALLOY_AUTO_UPDATE=0, --skip-update, ~/.claude/.alloy-no-update)
# continue to short-circuit before this gate.
if [ "$FORCE_CHECK" != "true" ] && [ -f "$LAST_CHECK_FILE" ]; then
# `find -mtime -7` matches files modified within the last 7 days.
# Portable across BSD (macOS) and GNU find on bash 3.2+.
if [ -n "$(find "$LAST_CHECK_FILE" -mtime -7 -print 2>/dev/null)" ]; then
exit 0
fi
fi
# --- Fetch with timeout ---
GIT_HTTP_LOW_SPEED_LIMIT=1000 GIT_HTTP_LOW_SPEED_TIME=3 \
git fetch --quiet origin main 2>/dev/null || exit 0
# Record that a fetch completed. Done after the fetch so a transient network
# failure does not suppress the next day's attempt.
mkdir -p "$STATE_DIR" 2>/dev/null
touch "$LAST_CHECK_FILE" 2>/dev/null || true
# --- Compare versions ---
LOCAL_SHA=$(git rev-parse HEAD 2>/dev/null || echo "")
REMOTE_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
# Already up to date
[ "$LOCAL_SHA" = "$REMOTE_SHA" ] && exit 0
# Count commits behind
BEHIND=$(git rev-list --count HEAD..origin/main 2>/dev/null || echo "0")
[ "$BEHIND" = "0" ] && exit 0
LOCAL_VER=$(git describe --tags --always 2>/dev/null || echo "unknown")
REMOTE_VER=$(git describe --tags --always origin/main 2>/dev/null || echo "unknown")
# --- Check if user wants to apply ---
FORCE_UPDATE=false
for arg in "$@"; do
[ "$arg" = "--update" ] && FORCE_UPDATE=true
done
if [ "$FORCE_UPDATE" = true ] || [ "${ALLOY_AUTO_UPDATE:-}" = "1" ]; then
info "Pulling latest from origin/main..."
if git pull --ff-only origin main 2>/dev/null; then
# Read install mode from metadata
META_FILE="${CLAUDE_DIR}/.alloy-meta"
CURRENT_MODE="copy"
if [ -f "$META_FILE" ] && command -v jq &>/dev/null; then
CURRENT_MODE=$(jq -r '.install_mode // "copy"' "$META_FILE" 2>/dev/null || echo "copy")
fi
if [ "$CURRENT_MODE" = "symlink" ]; then
success "Updated ${LOCAL_VER} → ${REMOTE_VER} (${BEHIND} new commits). Changes are live immediately."
else
success "Updated ${LOCAL_VER} → ${REMOTE_VER} (${BEHIND} new commits). Run 'alloy' to apply the update."
fi
else
warn "Update available (${LOCAL_VER} → ${REMOTE_VER}) but your local repo has diverged."
warn "Fix: cd ${SCRIPT_DIR} && git pull --rebase origin main"
fi
else
echo ""
info "Update available: ${LOCAL_VER} → ${REMOTE_VER} (${BEHIND} commits behind)"
info "Run ${GREEN}alloy --update${NC} to apply."
echo ""
fi