일기 slecs

OMC 심볼릭 링크 스킬을 깃이그노어로 관리하도록 정리

목차

심볼릭 링크를 git에서 어떻게 다룰 것인지, 생각보다 선택지가 몇 가지 있다. 그냥 트래킹해도 되고, ignore 처리해도 되고, 아예 링크 대신 복사본을 repo에 두는 방법도 있다. OMC 쪽 스킬 디렉토리가 심볼릭 링크로 구성돼 있었는데, 이 상태로 계속 가면 환경마다 링크가 끊기거나 경로가 달라지는 문제가 생길 수 있어서 이번에 gitignore 쪽으로 깔끔하게 정리했다.

심볼릭 링크와 git이 만날 때 생기는 문제

git은 심볼릭 링크를 "파일 내용이 링크 경로인 특수 파일"로 취급한다. git status를 찍어보면 symlink -> /some/absolute/path 식으로 나오고, 커밋에도 그대로 들어간다. 문제는 링크 타겟이 절대 경로인 경우다. 로컬에서 /Users/me/shared/skills/deploy.sh로 잘 붙던 게 CI 서버나 다른 개발자 환경에선 그 경로가 없어서 dead link가 된다. 체크아웃 직후 스크립트 실행이 안 된다는 연락이 오는 건 시간문제.

OMC 스킬 파일들이 딱 이 케이스였다. 공유 스크립트 디렉토리를 각 환경마다 다른 위치에 두고, 거기서 심볼릭 링크를 만들어 프로젝트 내에서 쓰는 구조. 링크를 그대로 repo에 올리면 다른 환경에서 체크아웃했을 때 깨진 링크가 들어온다. 그렇다고 스크립트 본체를 repo에 복사해 넣으면 소스가 두 벌이 되고, 한 쪽 수정할 때 다른 쪽을 빠뜨리는 실수가 나온다.

해결책이 몇 가지 있었다.

  • 심볼릭 링크를 gitignore 처리하고, 환경 설정 스크립트에서 링크를 생성
  • 링크 대신 상대 경로로 스크립트 직접 추가
  • git submodule로 공유 스킬 디렉토리를 별도 관리

결국 gitignore + setup 스크립트 방식을 골랐다. 환경 초기화 스크립트를 이미 관리하고 있었고, 링크 생성 로직을 거기 넣는 게 제일 자연스러웠음. submodule은 관리 오버헤드가 있고, 공유 디렉토리 자체가 별도 repo로 관리되고 있는 상황이 아니었다.

실제 처리 방식

.gitignore에 스킬 파일들을 명시하는 건 간단하다.

# OMC skill symlinks - env-specific, created by setup.sh
skills/
*.skill.sh

여기서 끝내면 "이 파일들 어디서 나와요?" 질문이 반드시 생긴다. gitignore만 있고 생성 방법이 없으면 나중에 환경 세팅하는 사람이 막힌다. 그래서 setup 스크립트에 링크 생성 부분을 정리해 넣었다.

#!/bin/bash
# setup.sh - OMC 환경 초기 세팅

SKILL_SRC="${OMC_SKILL_DIR:-/opt/shared/omc-skills}"
SKILL_DST="$(pwd)/skills"

if [ ! -d "$SKILL_SRC" ]; then
  echo "[ERROR] 스킬 소스 디렉토리를 찾을 수 없음: $SKILL_SRC"
  echo "  OMC_SKILL_DIR 환경변수를 확인하거나 /opt/shared/omc-skills 를 확인하세요."
  exit 1
fi

mkdir -p "$SKILL_DST"

for skill_file in "$SKILL_SRC"/*.sh; do
  [ -f "$skill_file" ] || continue
  skill_name="$(basename "$skill_file")"
  ln -sf "$skill_file" "$SKILL_DST/$skill_name"
  echo "  linked: $skill_name"
done

echo "Done. Skills linked from $SKILL_SRC"

-sf 옵션으로 기존 링크가 있어도 덮어쓰게 했다. 환경변수 OMC_SKILL_DIR가 없으면 /opt/shared/omc-skills가 기본값. 소스 디렉토리 자체가 없으면 에러 메시지와 함께 exit 1 내고, 어디를 확인해야 하는지 출력하도록 했다. 새벽에 누군가 이 스크립트를 돌릴 때 그냥 조용히 실패하는 것보다 낫다.

이 구조의 트레이드오프

gitignore + setup.sh 방식과 링크를 직접 커밋하는 방식의 차이를 정리하면 이렇다.

구분 gitignore + setup.sh 링크 직접 커밋
체크아웃 후 실행 setup.sh 한 번 실행 필요 링크 타겟 경로가 맞으면 바로 가능
환경별 경로 차이 환경변수로 유연하게 처리 절대 경로 하드코딩 위험
링크 깨짐 복구 setup.sh 재실행으로 해결 git이 깨진 링크를 정상으로 인식
신규 스킬 추가 소스 디렉토리에 추가하면 자동 반영 repo에도 링크 추가 필요
온보딩 복잡도 setup.sh 실행 단계 추가됨 별도 작업 없음

링크를 직접 커밋하는 방식이 맞을 때도 있다. 링크 타겟이 repo 내부 상대 경로거나, 팀 전체가 동일한 환경 구성을 쓴다면 그쪽이 더 단순하다. 이번 케이스는 공유 스크립트 디렉토리 위치가 환경마다 달랐기 때문에 gitignore 방식이 맞았음.

주의할 점은, setup.sh 실행을 강제할 방법이 없다는 것. README에 명시하고, .env.exampleOMC_SKILL_DIR 항목도 추가해서 "이런 환경변수가 있다"는 걸 눈에 띄게 해뒀다. Makefile이 있는 프로젝트라면 make setup 타겟에 링크 생성을 넣어두는 것도 좋은 방법이다.

작업 회고

diff만 보면 거의 없는 작업이다. .gitignore 몇 줄, setup.sh 수정, .env.example 항목 추가가 전부. 코드 리뷰에서 "이게 다예요?" 소리 들을 법한 PR이기도 하다.

근데 이런 정리가 나중에 신규 합류자나 서버 이전 작업할 때 체감이 갈린다. gitignore 처리 없이 깨진 심볼릭 링크가 repo에 그대로 있으면, 체크아웃한 사람이 스크립트 실행 안 된다고 하고, 원인 파악하는 데 시간 쓰고, 결국 "원래 이런 거 아닌가요?" 물어보러 오는 루프가 생긴다. 처음 마주치는 사람 입장에선 링크가 왜 깨져있는지, 어디서 만들어지는 건지 알 방법이 없다.

이번 작업을 하면서 다시 생각한 건, 파일이 어디서 왔고 어떻게 만들어지는지를 코드로 표현해두는 것. 주석도 좋고, setup 스크립트도 좋고, .env.example도 좋다. 중요한 건 "나는 알고 있다"가 아니라 "코드 보면 알 수 있다"인가 여부다.

코드를 읽는 주체를 항상 세 가지 관점으로 생각하게 된다.

  • 6개월 후의 나: 이 구조를 왜 이렇게 했는지 읽으면 나오는가
  • 처음 보는 팀원: 질문 없이 환경 세팅이 가능한가
  • 새벽 장애 상황: 슬랙 뒤지거나 사람 깨우지 않고 setup.sh만 보면 복구 경로가 보이는가

세 질문 다 "그렇다"가 나오면 충분하다고 본다. 이번 작업은 그 기준으로 정리했고, 당장 눈에 안 띄는 작업이 나중에 조용히 시간을 아껴준다는 걸 다시 확인했다.

댓글 0

첫 댓글 달아줘.