Skip to content

Commit 2c0b599

Browse files
committed
fix: updated script to run on system directories
1 parent 1a36baa commit 2c0b599

1 file changed

Lines changed: 224 additions & 68 deletions

File tree

update-all-repos.sh

Lines changed: 224 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,45 @@
55
# Based on git-secrets update-all-repos.sh
66

77
# Usage examples:
8-
# ./update-all-repos.sh # Updates all repos in current directory
9-
# ./update-all-repos.sh ~/Projects # Updates all repos in ~/Projects
8+
# ./update-all-repos.sh # Updates all repos in current directory (recursively)
9+
# ./update-all-repos.sh ~/Projects # Updates all repos in ~/Projects (recursively)
1010
# ./update-all-repos.sh ~/Sites ~/Projects # Updates repos in multiple directories
11+
# sudo ./update-all-repos.sh /var # Updates repos in system directories (requires root)
12+
#
13+
# Environment variables:
14+
# MAX_DEPTH=3 ./update-all-repos.sh ~/ # Limit recursion depth (default: unlimited)
15+
#
16+
# WARNING: Scanning large directories like ~ or / can take a very long time!
17+
# It's better to specify specific project directories.
1118

1219
HIGHLIGHT="\e[01;34m"
1320
SUCCESS="\e[01;32m"
1421
ERROR="\e[01;31m"
1522
WARNING="\e[01;33m"
1623
NORMAL='\e[00m'
1724

25+
# Configuration
26+
MAX_DEPTH="${MAX_DEPTH:-}" # Default: unlimited depth
27+
28+
# Temporary files for tracking stats across subshells
29+
STATS_DIR=$(mktemp -d)
30+
trap "rm -rf $STATS_DIR" EXIT
31+
32+
touch "$STATS_DIR/found"
33+
touch "$STATS_DIR/updated"
34+
touch "$STATS_DIR/failed"
35+
touch "$STATS_DIR/skipped"
36+
37+
function increment_stat {
38+
local stat_file="$STATS_DIR/$1"
39+
echo "1" >> "$stat_file"
40+
}
41+
42+
function get_stat {
43+
local stat_file="$STATS_DIR/$1"
44+
wc -l < "$stat_file" 2>/dev/null | tr -d ' ' || echo "0"
45+
}
46+
1847
# Check if gitleaks is installed
1948
if ! command -v gitleaks &> /dev/null; then
2049
echo -e "${ERROR}Error: gitleaks is not installed${NORMAL}"
@@ -27,8 +56,14 @@ fi
2756

2857
# Check if global template is set up
2958
TEMPLATE_DIR="$HOME/.git-template"
59+
# Handle case when running with sudo - use the actual user's home
60+
if [ -n "$SUDO_USER" ]; then
61+
TEMPLATE_DIR=$(eval echo ~$SUDO_USER)/.git-template
62+
fi
63+
3064
if [ ! -d "$TEMPLATE_DIR/hooks" ]; then
3165
echo -e "${ERROR}Error: Git template directory not found${NORMAL}"
66+
echo "Expected location: $TEMPLATE_DIR/hooks"
3267
echo "Please run ./install-gitleaks-global.sh first"
3368
exit 1
3469
fi
@@ -224,111 +259,232 @@ function install_native_hooks {
224259
return 0
225260
}
226261

227-
function update_repo {
228-
local d="$1"
262+
# Function to process a single git repository
263+
function process_repo {
264+
local repodir="$1"
229265

230-
# Skip if not a directory or is a symbolic link
231-
if [ ! -d "$d" ] || [ -L "$d" ]; then
232-
return 0
233-
fi
266+
cd "$repodir" 2>/dev/null || {
267+
echo -e "${WARNING}${NORMAL} Cannot access repository: $repodir (permission denied)"
268+
increment_stat "skipped"
269+
return 1
270+
}
234271

235-
# Try to enter directory, return if fails
236-
cd "$d" > /dev/null 2>&1 || return 0
272+
increment_stat "found"
273+
printf "%b\n" "${HIGHLIGHT}Installing gitleaks hooks in $(pwd)${NORMAL}"
237274

238-
if [ -d ".git" ]; then
239-
printf "%b\n" "${HIGHLIGHT}Installing gitleaks hooks in $(pwd)${NORMAL}"
240-
241-
# CRITICAL: Check if core.hooksPath is configured but directory doesn't exist
242-
HOOKS_PATH=$(git config core.hooksPath 2>/dev/null || echo "")
243-
if [ -n "$HOOKS_PATH" ] && [ ! -d "$HOOKS_PATH" ]; then
244-
echo -e " ${ERROR}🚨 CRITICAL${NORMAL}: Git is configured to use hooks from '$HOOKS_PATH' but directory doesn't exist!"
245-
echo -e " ${WARNING}${NORMAL} This means NO hooks are running - security bypass!"
246-
echo -e " ${HIGHLIGHT}${NORMAL} Fixing: Unsetting core.hooksPath and installing native hooks"
247-
git config --unset core.hooksPath
248-
install_native_hooks
275+
# Check if .git directory is writable
276+
if [ ! -w ".git" ]; then
277+
echo -e " ${ERROR}${NORMAL} Cannot write to .git directory (permission denied)"
278+
echo -e " ${WARNING}${NORMAL} Repository owned by: $(stat -c '%U:%G' .git 2>/dev/null || echo 'unknown')"
279+
echo -e " ${WARNING}${NORMAL} Current user: $(whoami)"
280+
if [ "$EUID" -ne 0 ]; then
281+
echo -e " ${HIGHLIGHT}${NORMAL} Tip: Run script with sudo to update system repositories"
282+
fi
283+
increment_stat "failed"
284+
return 1
285+
fi
286+
287+
# CRITICAL: Check if core.hooksPath is configured but directory doesn't exist
288+
HOOKS_PATH=$(git config core.hooksPath 2>/dev/null || echo "")
289+
if [ -n "$HOOKS_PATH" ] && [ ! -d "$HOOKS_PATH" ]; then
290+
echo -e " ${ERROR}🚨 CRITICAL${NORMAL}: Git is configured to use hooks from '$HOOKS_PATH' but directory doesn't exist!"
291+
echo -e " ${WARNING}${NORMAL} This means NO hooks are running - security bypass!"
292+
echo -e " ${HIGHLIGHT}${NORMAL} Fixing: Unsetting core.hooksPath and installing native hooks"
293+
git config --unset core.hooksPath
294+
if install_native_hooks; then
295+
increment_stat "updated"
249296
return 0
297+
else
298+
increment_stat "failed"
299+
return 1
250300
fi
301+
fi
302+
303+
# Detect if this repo uses Husky
304+
if [ -d ".husky" ]; then
305+
echo -e " ${HIGHLIGHT}${NORMAL} Detected Husky repository"
251306

252-
# Detect if this repo uses Husky
253-
if [ -d ".husky" ]; then
254-
echo -e " ${HIGHLIGHT}${NORMAL} Detected Husky repository"
255-
256-
# Check if pre-commit exists
257-
if [ -f ".husky/pre-commit" ]; then
258-
inject_gitleaks_husky ".husky/pre-commit"
307+
# Check if pre-commit exists
308+
if [ -f ".husky/pre-commit" ]; then
309+
if inject_gitleaks_husky ".husky/pre-commit"; then
310+
increment_stat "updated"
311+
return 0
259312
else
260-
# Check if husky.sh exists to confirm it's a valid Husky setup
261-
if [ -f ".husky/_/husky.sh" ] || [ -f ".husky/husky.sh" ]; then
262-
create_husky_precommit ".husky/pre-commit"
263-
else
264-
echo -e " ${WARNING}${NORMAL} Husky directory exists but appears incomplete"
265-
echo -e " ${HIGHLIGHT}${NORMAL} Installing native Git hooks as fallback"
266-
install_native_hooks
267-
fi
313+
increment_stat "failed"
314+
return 1
268315
fi
269316
else
270-
# Check if core.hooksPath points to .husky but .husky doesn't exist
271-
if [ "$HOOKS_PATH" = ".husky/_" ] || [ "$HOOKS_PATH" = ".husky" ]; then
272-
echo -e " ${WARNING}${NORMAL} Repo was using Husky but .husky/ is missing"
273-
echo -e " ${HIGHLIGHT}${NORMAL} Unsetting core.hooksPath and using native hooks"
274-
git config --unset core.hooksPath
317+
# Check if husky.sh exists to confirm it's a valid Husky setup
318+
if [ -f ".husky/_/husky.sh" ] || [ -f ".husky/husky.sh" ]; then
319+
if create_husky_precommit ".husky/pre-commit"; then
320+
increment_stat "updated"
321+
return 0
322+
else
323+
increment_stat "failed"
324+
return 1
325+
fi
326+
else
327+
echo -e " ${WARNING}${NORMAL} Husky directory exists but appears incomplete"
328+
echo -e " ${HIGHLIGHT}${NORMAL} Installing native Git hooks as fallback"
329+
if install_native_hooks; then
330+
increment_stat "updated"
331+
return 0
332+
else
333+
increment_stat "failed"
334+
return 1
335+
fi
275336
fi
276-
277-
# Standard git hooks installation
278-
echo -e " ${HIGHLIGHT}${NORMAL} Using native Git hooks"
279-
install_native_hooks
280337
fi
281338
else
282-
# Not a git repo, scan subdirectories (but with protection)
283-
scan_dirs * 2>/dev/null || true
284-
fi
285-
286-
cd .. > /dev/null 2>&1 || true
287-
}
288-
289-
function scan_dirs {
290-
for x in "$@"; do
291-
# Skip hidden directories and problematic directories
292-
if [[ "$x" == .* ]] || [[ "$x" == "__MACOSX" ]] || [[ "$x" == "node_modules" ]]; then
293-
continue
339+
# Check if core.hooksPath points to .husky but .husky doesn't exist
340+
if [ "$HOOKS_PATH" = ".husky/_" ] || [ "$HOOKS_PATH" = ".husky" ]; then
341+
echo -e " ${WARNING}${NORMAL} Repo was using Husky but .husky/ is missing"
342+
echo -e " ${HIGHLIGHT}${NORMAL} Unsetting core.hooksPath and using native hooks"
343+
git config --unset core.hooksPath
294344
fi
295-
update_repo "$x" || true # Continue even if update_repo fails
296-
done
345+
346+
# Standard git hooks installation
347+
echo -e " ${HIGHLIGHT}${NORMAL} Using native Git hooks"
348+
if install_native_hooks; then
349+
increment_stat "updated"
350+
return 0
351+
else
352+
increment_stat "failed"
353+
return 1
354+
fi
355+
fi
297356
}
298357

299358
function update_directory {
300-
if [ "$1" != "" ]; then
301-
cd "$1" > /dev/null 2>&1 || {
302-
echo -e "${ERROR}${NORMAL} Cannot access directory: $1"
359+
local target_dir="$1"
360+
361+
# Use current directory if none specified
362+
if [ -z "$target_dir" ]; then
363+
target_dir="$PWD"
364+
fi
365+
366+
# Convert to absolute path
367+
if [[ "$target_dir" != /* ]]; then
368+
target_dir="$PWD/$target_dir"
369+
fi
370+
371+
# Check if directory exists and is accessible
372+
if [ ! -d "$target_dir" ]; then
373+
echo -e "${ERROR}${NORMAL} Directory does not exist: $target_dir"
374+
return 1
375+
fi
376+
377+
if [ ! -r "$target_dir" ]; then
378+
echo -e "${ERROR}${NORMAL} Cannot read directory: $target_dir (permission denied)"
379+
if [ "$EUID" -ne 0 ]; then
380+
echo -e "${HIGHLIGHT}${NORMAL} Try running with sudo: sudo ./update-all-repos.sh $target_dir"
381+
fi
382+
return 1
383+
fi
384+
385+
# Warn if trying to update system directories
386+
if [[ "$target_dir" == "/var"* ]] || [[ "$target_dir" == "/etc"* ]] || [[ "$target_dir" == "/sys"* ]] || [[ "$target_dir" == "/proc"* ]]; then
387+
echo -e "${WARNING}${NORMAL} WARNING: Scanning system directory: ${target_dir}"
388+
if [ "$EUID" -ne 0 ]; then
389+
echo -e "${ERROR}${NORMAL} ERROR: System directories require root privileges"
390+
echo -e "${HIGHLIGHT}${NORMAL} Please run with sudo: sudo ./update-all-repos.sh \"$target_dir\""
303391
return 1
304-
}
392+
fi
393+
echo -e "${SUCCESS}${NORMAL} Running with root privileges"
305394
fi
306-
printf "%b\n" "${HIGHLIGHT}Scanning ${PWD} for git repositories...${NORMAL}\n"
307-
scan_dirs * 2>/dev/null || true
395+
396+
printf "%b\n" "${HIGHLIGHT}Scanning ${target_dir} recursively for git repositories...${NORMAL}"
397+
398+
# Build find command with optional depth limit
399+
local find_cmd="find \"$target_dir\""
400+
if [ -n "$MAX_DEPTH" ]; then
401+
find_cmd="$find_cmd -maxdepth $MAX_DEPTH"
402+
echo -e "${HIGHLIGHT}${NORMAL} Maximum depth: $MAX_DEPTH levels"
403+
else
404+
echo -e "${WARNING}${NORMAL} WARNING: Unlimited depth - this may take a very long time for large directories!"
405+
echo -e "${HIGHLIGHT}${NORMAL} Tip: Set MAX_DEPTH to limit recursion (e.g., MAX_DEPTH=3 ./update-all-repos.sh ~)"
406+
fi
407+
echo -e "${HIGHLIGHT}${NORMAL} Press Ctrl+C to cancel if this takes too long"
408+
echo ""
409+
410+
# Use find to recursively locate all .git directories
411+
# Exclude common large directories to speed up search
412+
# The -print0 and read -d '' handle filenames with spaces and special characters
413+
local count=0
414+
while IFS= read -r -d '' gitdir; do
415+
local repodir=$(dirname "$gitdir")
416+
417+
# Skip submodules (git repos inside .git directories)
418+
if [[ "$repodir" == *"/.git/"* ]] || [[ "$repodir" == *"/.git" ]]; then
419+
continue
420+
fi
421+
422+
count=$((count + 1))
423+
echo -e "${HIGHLIGHT}[$count]${NORMAL} Found: $repodir"
424+
425+
# Process this repository in current shell (not subshell) to track stats
426+
(process_repo "$repodir")
427+
echo ""
428+
429+
done < <(find "$target_dir" \
430+
${MAX_DEPTH:+-maxdepth $MAX_DEPTH} \
431+
-type d \
432+
\( -name "node_modules" -o -name ".npm" -o -name ".cache" -o -name "__pycache__" -o -name ".venv" -o -name "venv" -o -name ".local" -o -name ".cargo" -o -name ".rustup" -o -name ".m2" -o -name ".gradle" -o -name "target" -o -name "build" -o -name "dist" -o -name "vendor" -o -name ".bundle" \) -prune -o \
433+
-type d -name ".git" -print0 2>/dev/null)
308434
}
309435

310436
# Main execution
311437
echo -e "${HIGHLIGHT}========================================${NORMAL}"
312438
echo -e "${HIGHLIGHT}Gitleaks Hook Installer${NORMAL}"
313439
echo -e "${HIGHLIGHT}========================================${NORMAL}\n"
314440

315-
if [ "$1" == "" ]; then
316-
update_directory
441+
if [ "$EUID" -eq 0 ]; then
442+
echo -e "${WARNING}${NORMAL} Running as root (sudo)"
443+
echo -e "${HIGHLIGHT}${NORMAL} Will be able to update system-owned repositories"
444+
echo ""
445+
fi
446+
447+
if [ "$#" -eq 0 ]; then
448+
update_directory "$PWD"
317449
else
318450
for dir in "$@"; do
319451
update_directory "$dir"
452+
echo ""
320453
done
321454
fi
322455

456+
# Get final statistics
457+
REPOS_FOUND=$(get_stat "found")
458+
REPOS_UPDATED=$(get_stat "updated")
459+
REPOS_FAILED=$(get_stat "failed")
460+
REPOS_SKIPPED=$(get_stat "skipped")
461+
323462
echo -e "\n${SUCCESS}========================================${NORMAL}"
324463
echo -e "${SUCCESS}Update Complete!${NORMAL}"
325464
echo -e "${SUCCESS}========================================${NORMAL}\n"
326465

327466
echo -e "${HIGHLIGHT}Summary:${NORMAL}"
328-
echo " • Gitleaks pre-commit hooks have been installed in all git repositories"
467+
echo " • Git repositories found: $REPOS_FOUND"
468+
echo " • Successfully updated: $REPOS_UPDATED"
469+
echo " • Failed (permission denied): $REPOS_FAILED"
470+
echo " • Skipped (inaccessible): $REPOS_SKIPPED"
329471
echo " • Both Husky and native Git hooks are supported"
330472
echo " • Future commits will be scanned for blockchain private keys and secrets"
331473
echo ""
474+
475+
# Show warning if there were permission failures
476+
if [ "$REPOS_FAILED" -gt 0 ] || [ "$REPOS_SKIPPED" -gt 0 ]; then
477+
echo -e "${WARNING}${NORMAL} ${WARNING}WARNING: Some repositories could not be updated due to permission issues${NORMAL}"
478+
if [ "$EUID" -ne 0 ]; then
479+
echo -e "${HIGHLIGHT}${NORMAL} To update system repositories (in /var, /etc, etc.), run with sudo:"
480+
echo -e " ${HIGHLIGHT}sudo ./update-all-repos.sh /var${NORMAL}"
481+
else
482+
echo -e "${HIGHLIGHT}${NORMAL} Some repositories may have additional access restrictions"
483+
echo -e " Check ownership and permissions of failed repositories"
484+
fi
485+
echo ""
486+
fi
487+
332488
echo -e "${HIGHLIGHT}Test the hooks:${NORMAL}"
333489
echo " cd /path/to/any/repo"
334490
echo " echo 'const key = \"abc\"' > test.js"

0 commit comments

Comments
 (0)