Repository navigation
Expand file tree
/
Copy pathsetup.sh
More file actions
executable file
·332 lines (275 loc) · 11 KB
/
Copy pathsetup.sh
File metadata and controls
executable file
·332 lines (275 loc) · 11 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/bin/bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Detect if running via curl | bash (no local setup assets available)
if [[ ! -f "$SCRIPT_DIR/scripts/git-safety.sh" ]]; then
echo "Installer running in standalone/curl mode. Fetching setup assets..."
TEMP_SETUP_DIR=$(mktemp -d)
# Fetch and extract only the latest main branch archive
curl -fsSL "https://github.com/bcgov/copilot-instructions/archive/refs/heads/main.tar.gz" \
| tar -xz -C "$TEMP_SETUP_DIR" --strip-components=1
SCRIPT_DIR="$TEMP_SETUP_DIR"
trap 'rm -rf "$TEMP_SETUP_DIR"' EXIT
fi
HOOKS_DIR="$HOME/.githooks"
BIN_DIR="$HOME/.local/bin"
# Locate the real git binary, bypassing ~/.local/bin/git wrapper
_real_git() {
local git_bin=""
while IFS= read -r dir; do
[[ "$dir" == "$BIN_DIR" ]] && continue
if [[ -x "$dir/git" ]]; then
git_bin="$dir/git"
break
fi
done < <(printf '%s\n' "$PATH" | tr ':' '\n')
if [[ -z "${git_bin:-}" ]]; then
echo "ERROR: Real git binary not found in PATH." >&2
exit 127
fi
"$git_bin" "$@"
}
install_gitleaks() {
mkdir -p "$BIN_DIR"
local os
local arch
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m)
case "$arch" in
x86_64|amd64)
arch="x64"
;;
arm64|aarch64)
arch="arm64"
;;
*)
echo "ERROR: Unsupported architecture: $arch" >&2
exit 1
;;
esac
case "$os" in
linux|darwin)
;;
*)
echo "ERROR: Unsupported OS: $os" >&2
exit 1
;;
esac
local latest_url=""
# Temporarily disable pipefail and set -e to handle network issues gracefully
set +e +o pipefail
local json_data
json_data=$(curl -fsSL --connect-timeout 5 "https://api.github.com/repos/gitleaks/gitleaks/releases/latest" 2>/dev/null)
local curl_status=$?
set -e -o pipefail
if [[ $curl_status -eq 0 ]] && [[ -n "$json_data" ]]; then
latest_url=$(echo "$json_data" \
| grep -Eo '"browser_download_url"\s*:\s*"[^"]+"' \
| cut -d '"' -f 4 \
| grep "gitleaks_.*_${os}_${arch}\.tar\.gz" \
| head -n 1 || true)
fi
if [[ -n "${latest_url:-}" ]]; then
echo "Downloading and installing latest gitleaks to $BIN_DIR..."
set +e
curl -fsSL --connect-timeout 10 "$latest_url" | tar -xz -C "$BIN_DIR" gitleaks 2>/dev/null
local download_status=$?
set -e
if [[ $download_status -ne 0 ]]; then
echo "WARNING: Failed to download or extract gitleaks. Skipping." >&2
fi
else
if command -v gitleaks >/dev/null 2>&1; then
echo "WARNING: Offline or unable to connect to api.github.com. Keeping existing gitleaks installation." >&2
else
echo "WARNING: Offline or unable to connect to api.github.com. Skipping gitleaks installation." >&2
fi
fi
if ! command -v gitleaks >/dev/null 2>&1; then
echo "NOTE: Ensure $BIN_DIR is on your PATH to use gitleaks." >&2
fi
}
install_hooks() {
mkdir -p "$HOOKS_DIR"
if [[ ! -f "$SCRIPT_DIR/scripts/hooks/pre-commit" ]]; then
echo "ERROR: Hook file not found: $SCRIPT_DIR/scripts/hooks/pre-commit" >&2
exit 1
fi
if [[ ! -f "$SCRIPT_DIR/scripts/hooks/pre-push" ]]; then
echo "ERROR: Hook file not found: $SCRIPT_DIR/scripts/hooks/pre-push" >&2
exit 1
fi
# Backup existing hooks if they exist
local timestamp
timestamp=$(date +%s)
if [[ -f "$HOOKS_DIR/pre-commit" ]]; then
mv "$HOOKS_DIR/pre-commit" "$HOOKS_DIR/pre-commit.bak.$timestamp"
echo "NOTE: Existing pre-commit hook backed up to $HOOKS_DIR/pre-commit.bak.$timestamp" >&2
fi
if [[ -f "$HOOKS_DIR/pre-push" ]]; then
mv "$HOOKS_DIR/pre-push" "$HOOKS_DIR/pre-push.bak.$timestamp"
echo "NOTE: Existing pre-push hook backed up to $HOOKS_DIR/pre-push.bak.$timestamp" >&2
fi
cp "$SCRIPT_DIR/scripts/hooks/pre-commit" "$HOOKS_DIR/pre-commit"
cp "$SCRIPT_DIR/scripts/hooks/pre-push" "$HOOKS_DIR/pre-push"
chmod +x "$HOOKS_DIR/pre-commit" "$HOOKS_DIR/pre-push"
# Check and warn about existing git config
local current_hooks_path
current_hooks_path=$(_real_git config --global --get core.hooksPath 2>/dev/null || true)
if [[ -n "${current_hooks_path:-}" ]] && [[ "$current_hooks_path" != "$HOOKS_DIR" ]]; then
echo "WARNING: Your global git core.hooksPath is currently set to: $current_hooks_path" >&2
echo "This script will change it to: $HOOKS_DIR" >&2
read -r -p "Do you want to proceed? [y/N]: " answer
if [[ "${answer:-n}" != "y" ]] && [[ "${answer:-n}" != "Y" ]]; then
echo "Skipped updating global core.hooksPath. Hooks were copied to $HOOKS_DIR." >&2
return 0
fi
fi
_real_git config --global core.hooksPath "$HOOKS_DIR"
}
# Remove the old bashrc-injection approach (bash function + export -f).
# This cleans up installs from before the PATH-based wrapper migration.
cleanup_old_bashrc_safety() {
local bashrc="$HOME/.bashrc"
if [[ -f "$bashrc" ]]; then
echo "Cleaning up old safety wrapper definitions from ~/.bashrc..."
if command -v python3 >/dev/null 2>&1; then
python3 -c '
import sys, re
path = sys.argv[1]
with open(path, "r") as f:
content = f.read()
# 1. Clean up the new marked block first if it exists
content = re.sub(r"# >>> bcgov/copilot-instructions safety block >>>.*?# <<< bcgov/copilot-instructions safety block <<<\n*", "", content, flags=re.DOTALL)
# 2. Clean up legacy/historical unmarked blocks safely (matching up to their specific exports)
content = re.sub(r"# Git Safety.*?\nexport -f git\n*", "", content, flags=re.DOTALL)
content = re.sub(r"# GitHub CLI Safety.*?\nexport -f gh\n*", "", content, flags=re.DOTALL)
# Make sure we do not leave orphaned functions by matching through the entire old AI policy block
content = re.sub(r"# AI POLICY \(bcgov/copilot-instructions\).*?\nexport -f (gh|npx)\n*", "", content, flags=re.DOTALL)
# Remove trailing empty lines or excessive newlines
content = re.sub(r"\n{3,}", "\n\n", content)
with open(path, "w") as f:
f.write(content)
' "$bashrc"
else
echo "WARNING: python3 is not available. Skipping deep regex cleanup of old safety blocks." >&2
echo " Any existing marked safety block will be cleaned up using basic sed." >&2
# Securely strip marked block using sed fallback if present
if grep -q "# >>> bcgov/copilot-instructions safety block >>>" "$bashrc"; then
local temp_file
temp_file=$(mktemp)
sed '/# >>> bcgov\/copilot-instructions safety block >>>/,/# <<< bcgov\/copilot-instructions safety block <<</d' "$bashrc" > "$temp_file"
cat "$temp_file" > "$bashrc"
rm -f "$temp_file"
fi
fi
fi
}
# Install safety functions to ~/.bashrc wrapped in explicit BEGIN/END markers (skipping shebang).
install_safety_functions() {
local bashrc="$HOME/.bashrc"
local git_safety="$SCRIPT_DIR/scripts/git-safety.sh"
local timestamp
timestamp=$(date +%s)
# 1. Pre-flight check: Verify source file exists before modifying anything
if [[ ! -f "$git_safety" ]]; then
echo "ERROR: Could not find safety source script at $git_safety" >&2
return 1
fi
# 2. Pre-flight check: Create a backup of ~/.bashrc first
if [[ -f "$bashrc" ]]; then
cp "$bashrc" "$bashrc.bak.$timestamp"
echo "NOTE: Created backup of ~/.bashrc at $bashrc.bak.$timestamp" >&2
fi
# 3. Purge old blocks safely
cleanup_old_bashrc_safety
# 4. Copy the safety script to $HOOKS_DIR/git-safety.sh
mkdir -p "$HOOKS_DIR"
cp "$git_safety" "$HOOKS_DIR/git-safety.sh"
chmod +x "$HOOKS_DIR/git-safety.sh"
echo "Copied safety script to $HOOKS_DIR/git-safety.sh"
# 5. Append the safety loader block wrapped in explicit BEGIN/END markers
{
echo ""
echo "# >>> bcgov/copilot-instructions safety block >>>"
echo "# AI POLICY (bcgov/copilot-instructions)"
echo "if [ -f \"\$HOME/.githooks/git-safety.sh\" ]; then"
echo " . \"\$HOME/.githooks/git-safety.sh\""
echo "fi"
echo "# <<< bcgov/copilot-instructions safety block <<<"
} >> "$bashrc"
echo "Added safety function loader to ~/.bashrc"
}
bundle_instructions() {
local bundle_script="$SCRIPT_DIR/scripts/bundle.sh"
local profile_id="${1:-}"
if [[ -f "$bundle_script" ]]; then
echo "Bundling global instructions..."
bash "$bundle_script" ${profile_id:+"$profile_id"}
else
echo "WARNING: Bundle script not found at $bundle_script. Skipping instructions bundling." >&2
fi
}
configure_antigravity() {
if [[ "${ANTIGRAVITY:-}" == "true" || "${ANTIGRAVITY:-}" == "1" ]]; then
echo "Configuring Google Antigravity integration..."
mkdir -p "$HOME/.gemini/config"
mkdir -p "$HOME/.gemini/antigravity"
local global_prompt_file="$HOME/.config/Code/User/prompts/global.instructions.md"
# GEMINI.md symlink
if [[ -L "$HOME/.gemini/GEMINI.md" ]] || [[ ! -f "$HOME/.gemini/GEMINI.md" ]]; then
ln -sf "$global_prompt_file" "$HOME/.gemini/GEMINI.md"
echo "✓ Symlinked ~/.gemini/GEMINI.md -> $global_prompt_file"
fi
fi
}
configure_cursor() {
if [[ "${CURSOR:-}" == "true" || "${CURSOR:-}" == "1" ]]; then
echo "Configuring Cursor integration..."
local global_prompt_file="$HOME/.config/Code/User/prompts/global.instructions.md"
mkdir -p "$HOME/.config/Cursor/User/prompts"
if [[ -L "$HOME/.config/Cursor/User/prompts/global.instructions.md" ]] || [[ ! -f "$HOME/.config/Cursor/User/prompts/global.instructions.md" ]]; then
ln -sf "$global_prompt_file" "$HOME/.config/Cursor/User/prompts/global.instructions.md"
echo "✓ Symlinked ~/.config/Cursor/User/prompts/global.instructions.md -> $global_prompt_file"
fi
fi
}
install_gitleaks
install_hooks
bundle_instructions "${1:-}"
configure_antigravity
configure_cursor
cleanup_old_bashrc_safety
# Delete any stale wrapper scripts installed in ~/.local/bin/
# Safety: Only remove files that positively match our former wrapper signature
echo "Cleaning up any stale wrappers in $BIN_DIR..."
for cmd in git gh npm npx kubectl oc; do
wrapper="$BIN_DIR/$cmd"
if [[ -f "$wrapper" ]] \
&& head -n 5 "$wrapper" | grep -q '^#!/bin/bash' \
&& grep -q 'bcgov/copilot-instructions' "$wrapper" \
&& grep -q 'safety wrapper' "$wrapper"; then
echo "Removing wrapper script: $wrapper"
rm -f "$wrapper"
fi
done
if ! install_safety_functions; then
echo "ERROR: Failed to install safety functions." >&2
exit 1
fi
echo ""
echo "✅ Setup complete!"
echo "Git hooks: Secrets blocked (Gitleaks) + main/master push blocked"
echo "Instructions: Bundled to ~/.config/Code/User/prompts/global.instructions.md"
if [[ "${ANTIGRAVITY:-}" == "true" || "${ANTIGRAVITY:-}" == "1" ]]; then
echo "Antigravity: Symlinked instructions successfully"
fi
if [[ "${CURSOR:-}" == "true" || "${CURSOR:-}" == "1" ]]; then
echo "Cursor: Symlinked prompts directory successfully"
fi
echo "Safety functions: Installed to ~/.bashrc (git, gh, npm, npx)"
echo " Clean, non-exported shell functions (no export -f)"
echo "Git config: All git config commands blocked (use 'command git config' to bypass)"
echo "Kubernetes/OpenShift: Natively executed without safety blocks"
echo ""
echo "Restart your terminal or run: source ~/.bashrc"