#!/bin/bash # Jenkins Workspace Cleanup Script # Description: 安全清理Jenkins工作区,保留最近构建和重要文件 # Author: System Admin # Date: $(date +%Y-%m-%d) set -euo pipefail # 配置变量 - 根据实际情况修改 JENKINS_HOME="${JENKINS_HOME:-/usr/local/docker/jenkins/jenkins_home}" # Jenkins主目录 WORKSPACE_DIR="${JENKINS_HOME}/workspace" # 工作区目录 DRY_RUN=false # 干运行模式 RETENTION_DAYS=1 # 保留最近N天的构建 MIN_FREE_SPACE_GB=20 # 最小保留磁盘空间(GB) EXCLUDE_PATTERNS=(".*/builds/.*" ".*/lastStable.*" ".*/lastSuccessful.*" ".*/nextBuildNumber.*") # 排除模式 LOG_FILE="/var/log/jenkins-cleanup.log" # 日志文件 # 日志函数 log() { local message="$1" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[${timestamp}] ${message}" | tee -a "$LOG_FILE" } # 检查目录是否存在 check_directories() { if [ ! -d "$JENKINS_HOME" ]; then log "错误: Jenkins目录不存在: $JENKINS_HOME" exit 1 fi if [ ! -d "$WORKSPACE_DIR" ]; then log "错误: 工作区目录不存在: $WORKSPACE_DIR" exit 1 fi } # 检查磁盘空间 check_disk_space() { local available_space_gb=$(df -BG "$JENKINS_HOME" | awk 'NR==2 {print $4}' | sed 's/G//') if [ "$available_space_gb" -ge "$MIN_FREE_SPACE_GB" ]; then log "磁盘空间充足 (${available_space_gb}GB可用),跳过清理" return 1 fi log "磁盘空间不足 (${available_space_gb}GB可用),需要清理" return 0 } # 安全删除目录 safe_delete() { local dir_path="$1" if [ "$DRY_RUN" = true ]; then log "干运行: 将删除目录 - ${dir_path}" return 0 fi # 检查目录是否重要(包含排除模式) for pattern in "${EXCLUDE_PATTERNS[@]}"; do if [[ "$dir_path" =~ $pattern ]]; then log "跳过重要目录: ${dir_path}" return 0 fi done # 检查目录是否在最近修改时间内 local last_modified=$(stat -c %Y "$dir_path") local current_time=$(date +%s) local age_days=$(( (current_time - last_modified) / 86400 )) if [ "$age_days" -le "$RETENTION_DAYS" ]; then log "跳过最近修改的目录 (${age_days}天前): ${dir_path}" return 0 fi # 执行删除 if rm -rf "$dir_path" 2>/dev/null; then log "已删除: ${dir_path} (${age_days}天未修改)" return 0 else log "警告: 删除失败: ${dir_path}" return 1 fi } # 清理工作区 clean_workspace() { log "开始清理Jenkins工作区..." log "工作区目录: ${WORKSPACE_DIR}" log "保留最近 ${RETENTION_DAYS} 天的构建" log "最小保留空间: ${MIN_FREE_SPACE_GB}GB" local total_size_before=$(du -sh "$WORKSPACE_DIR" 2>/dev/null | cut -f1) log "清理前工作区大小: ${total_size_before}" # 查找并清理旧的工作区目录 local cleaned_count=0 local error_count=0 find "$WORKSPACE_DIR" -maxdepth 2 -type d -name "*" | while read -r dir_path; do # 跳过根目录和一级目录 if [ "$dir_path" = "$WORKSPACE_DIR" ] || [ "$(dirname "$dir_path")" = "$WORKSPACE_DIR" ]; then continue fi if safe_delete "$dir_path"; then ((cleaned_count++)) || true else ((error_count++)) || true fi done local total_size_after=$(du -sh "$WORKSPACE_DIR" 2>/dev/null | cut -f1) log "清理完成: 删除了 ${cleaned_count} 个目录, ${error_count} 个错误" log "清理后工作区大小: ${total_size_after}" log "释放空间: $(echo "$total_size_before -> $total_size_after")" } # 清理构建历史(可选) clean_builds() { local builds_dir="${JENKINS_HOME}/jobs" local max_builds_to_keep=20 # 每个作业保留的最大构建数 log "开始清理旧的构建历史..." find "$builds_dir" -name "builds" -type d 2>/dev/null | while read -r builds_path; do local job_dir=$(dirname "$builds_path") local job_name=$(basename "$job_dir") # 获取所有构建目录并按编号排序 local build_dirs=$(find "$builds_path" -maxdepth 1 -type d -name "[0-9]*" | sort -Vr) local build_count=$(echo "$build_dirs" | wc -l) if [ "$build_count" -gt "$max_builds_to_keep" ]; then local to_delete_count=$((build_count - max_builds_to_keep)) local deleted_count=0 echo "$build_dirs" | tail -n "$to_delete_count" | while read -r build_dir; do if safe_delete "$build_dir"; then ((deleted_count++)) || true fi done log "作业 ${job_name}: 保留 ${max_builds_to_keep} 个构建,删除 ${deleted_count} 个旧构建" fi done } # 显示帮助信息 show_help() { cat << EOF 使用方式: $0 [选项] 选项: -d, --dry-run 干运行模式,只显示将要执行的操作 -a, --all 清理工作区和构建历史 -w, --workspace 只清理工作区(默认) -b, --builds 只清理构建历史 -r DAYS, --retention-days DAYS 设置保留天数(默认: 7) -h, --help 显示此帮助信息 示例: $0 # 默认清理工作区 $0 --dry-run # 干运行模式 $0 --all # 清理工作区和构建历史 $0 --retention-days 30 # 保留最近30天的构建 EOF } # 解析命令行参数 MODE="workspace" while [[ $# -gt 0 ]]; do case $1 in -d|--dry-run) DRY_RUN=true shift ;; -a|--all) MODE="all" shift ;; -w|--workspace) MODE="workspace" shift ;; -b|--builds) MODE="builds" shift ;; -r|--retention-days) RETENTION_DAYS="$2" shift 2 ;; -h|--help) show_help exit 0 ;; *) log "错误: 未知选项 $1" show_help exit 1 ;; esac done # 主执行函数 main() { log "=== Jenkins 工作区清理开始 ===" log "模式: ${MODE}, 干运行: ${DRY_RUN}, 保留天数: ${RETENTION_DAYS}" check_directories # 检查磁盘空间,如果充足且不是强制清理,则跳过 if ! check_disk_space && [ "$MODE" != "all" ]; then log "磁盘空间充足,跳过清理" return 0 fi case "$MODE" in "workspace") clean_workspace ;; "builds") clean_builds ;; "all") clean_workspace clean_builds ;; esac log "=== Jenkins 工作区清理完成 ===" } # 执行主函数 main exit 0