반자동 디스크 정리 스크립트, Claude와 단계별로 검증하며 만들었다

NCP에서 /home 파티션 사용률 90% 경고가 왔다. 처음 한 번은 수동으로 정리했다. 2시간이 걸렸다. 두 번 다 하고 싶지 않았다.

백업하고, 무결성 확인하고, 로컬에 내려받고, NAS에 올리고, 원본을 지우는 순서는 매번 똑같다. 리스크는 크고 절차는 반복된다. 이걸 셸 스크립트로 묶기로 했다

왜 완전 자동화가 아니라 반자동이었나

처음에는 NAS 업로드까지 포함한 완전 자동화를 고민했다. 그런데 생각을 바꿨다

NAS 자격증명을 서버에 두고 싶지 않았다. 그리고 어차피 가끔 하는 작업이라 매월 내 눈으로 한 번 확인하는 과정 자체가 안전 마진이 된다. 완전 자동화는 오히려 과했다

최종 구조는 간단하다. backup.sh를 실행하면 백업까지 자동으로 끝난다. NAS에 올리는 건 내가 직접 한다. 확인하면 delete.sh를 실행한다. 이 정도면 충분했다

첫 스크립트를 다른 세션에서 검증했다

backup.shdelete.sh 초안을 Claude와 함께 작성했다. 130줄 정도가 나왔다. 그런데 내가 작성에 참여한 세션에서는 맹점이 보이지 않을 수 있다. 그래서 새 세션을 열고, Linux·셸 스크립트 전문가 롤을 부여한 뒤 “문제없이 잘 돌아갈 수 있는지 의견을 달라”고 물었다

11가지 문제가 돌아왔다. 그중 치명적인 것이 다섯 가지였다

  • set -euo pipefail 누락: 중간에 명령이 실패해도 다음으로 그냥 진행된다. 디스크 정리 스크립트에서 이건 위험하다
  • 빈 백업 함정: find가 파일을 0개 찾아도 tar는 빈 .tar.gz를 정상으로 만든다. gzip -t는 그걸 통과시킨다. “백업 성공” 메시지가 뜨고 삭제 단계로 넘어간다. 경로 오타가 있었다면 원본은 멀쩡한데 백업했다고 착각하는 상황이 된다
  • 백업과 삭제 기준의 시간차: 백업은 15:00에 했는데 NAS 업로드까지 30분이 걸렸다. 삭제는 15:30에 find를 다시 실행한다. 그 사이 생성된 파일이 끼어들 수 있다
  • 날짜 변수가 빈 문자열이면 모든 파일 삭제: DATE_4M이 어떤 이유로 비어있다면, find ... -not -newermt "" -delete는 해당 경로의 모든 파일을 지운다. set -u로는 빈 문자열을 못 잡는다. 명시적 검증이 별도로 필요하다
  • 디스크 여유 공간 사전 검사 없음: 디스크가 꽉 찬 상황에서 백업을 /home에 만들면 백업이 디스크를 더 채워 시스템이 죽는다

스크립트를 직접 작성하고 검토까지 같은 컨텍스트에서 진행했다면 이 중 몇 개는 놓쳤을 것이다. 세션을 분리한 게 효과가 있었다

DRY-RUN에서 버그를 하나 더 잡았다

위 다섯 가지를 수정하고 실제 서버에 배치했다. 그리고 DRY-RUN을 돌렸다. syntax error가 터졌다. 원인은 grep -c .였다

매칭 라인이 0개일 때 grep -c0을 출력하고 exit code 1을 반환한다. || echo 0이 발동되어 또 0을 출력했고, 결과적으로 file_count 변수에 두 줄짜리 0이 들어갔다. [[ "${file_count}" -eq 0 ]] 비교가 거기서 깨졌다

수정은 간단했다. grep -c 대신 wc -l을 쓰는 것이다. wc -l은 빈 파일에도 항상 exit 0과 깔끔한 숫자 하나를 반환한다. 실제 백업 단계에서 이걸 만났다면 부분 백업과 메타데이터 손상이 생길 수 있었다. DRY-RUN이 있어서 잡혔다

DRY-RUN은 선택이 아니다. 실제 환경에서 실행 전 반드시 거쳐야 하는 단계다

핵심 안전 메커니즘 – 파일 목록을 저장하고 그 목록으로 삭제한다

11가지 문제를 반영하면서 구조 자체를 바꿨다. 백업 시점에 find가 찾은 파일 목록을 NUL 구분자로 파일에 저장해둔다. 삭제할 때는 find를 다시 실행하지 않는다. 저장해둔 목록의 파일만 정확히 지운다

# 백업 시: 목록 저장
LIST_FILE="/tmp/catchtable_delete_list_${TODAY}.txt"
find "${path}" -type f -not -newermt "${date}" -print0 > "${LIST_FILE}"
tar -czf "${backup_file}" --null -T "${LIST_FILE}"

# 삭제 시: 같은 목록 사용
xargs -0 -a "${LIST_FILE}" rm -f

이렇게 하면 백업한 그 파일만 정확히 삭제된다는 게 보장된다. 시간차 문제가 사라진다. 검증은 백업 시 두 번, 삭제 시 두 번, 총 네 번 자동으로 수행된다

결과

서버 A는 이미 응급 수동 정리가 끝난 상태였다. 사용률 91% → 64%. 그래서 인프라만 배치하고 익월 정기 운영 대기로 마무리했다. 서버 B는 1년 동안 한 번도 정리되지 않은 폴더가 있었다. DRY-RUN 결과 1,037개 파일 약 1.4GB가 처리 대상이었다. 디스크 여유 49GB 대비 안전한 규모였다. 스크립트 배치와 DRY-RUN 검증까지 완료한 상태다. 매번 2시간 걸리던 작업이 10~20분짜리 절차로 바뀌었다

마무리

한 줄로 요약하면, 가끔 하는 위험한 반복 작업일수록 스크립트 설계에 시간을 쓰는 게 맞다

작성에 참여한 세션과 검증 세션을 분리하는 것, DRY-RUN을 실환경에서 반드시 돌려보는 것, 그리고 백업과 삭제의 기준을 동일한 파일 목록으로 고정하는 것. 이 세 가지가 이번 작업에서 실제로 효과가 있었