-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbootstrap.sh
More file actions
executable file
·353 lines (321 loc) · 18.5 KB
/
Copy pathbootstrap.sh
File metadata and controls
executable file
·353 lines (321 loc) · 18.5 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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#!/usr/bin/env bash
#
# Codyssey 교육용 iMac 부트스트랩
#
# 이 레포를 클론한 뒤 실행하면, 개인 맥에서 쓰던 개발 환경(zsh, git, iTerm2 설정,
# macOS 단축키 등)을 이 계정에 재현한다. 교육용 iMac은 자리가 랜덤 배정되고
# MaximumUsers=1 정책 때문에 다른 학생이 로그인하는 순간 이 계정의 홈 디렉토리가
# 통째로 삭제되므로, "한 번 설치해두는" 게 아니라 매번(로그인할 때마다) 이 스크립트를
# 수동으로 다시 실행하는 것을 전제로 한다. 학생 계정은 관리자 권한이 없어 brew
# 신규 설치나 /Applications 쓰기가 안 되므로, 전부 홈 디렉토리 스코프로만 동작한다.
#
# Rectangle은 포함하지 않는다: 창 이동 단축키가 동작하려면 손쉬운 사용 권한이 필요한데,
# 그 화면의 자물쇠를 풀 관리자 암호가 없어 이 계정에서는 애초에 켤 방법이 없다. 대신
# macOS 15(Sequoia) 내장 창 정리 단축키(System Settings > Keyboard > 키보드 단축키 >
# 윈도우: 이분면 Option+화살표, 사분면 Ctrl+Option+U/I/J/K)를 쓴다. 이건 윈도우 서버가
# 직접 처리해서 앱 메뉴 구성과 무관하게 크롬 포함 모든 앱에서 동작한다(앱 메뉴 이름에
# 단축키를 거는 System Settings > App Shortcuts 방식은 크롬처럼 메뉴에 해당 항목이 없는
# 앱에서는 안 먹혀서 폐기함 -- 아래 6단계 참고).
#
# git push 인증은 SSH 키 대신 GitHub Personal Access Token(HTTPS)을 쓴다: 초기화될
# 때마다 새 키를 만들어 GitHub에 재등록하는 것보다, 한 번 만든 토큰을 재입력하는 쪽이
# 덜 번거롭다. 이 스크립트 7단계에서 직접 물어보고 macOS Keychain에 저장한다
# (레포나 디스크의 다른 어떤 파일에도 토큰이 남지 않는다).
#
# 사용법: ./bootstrap.sh
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
TOTAL_STEPS=9
log() { echo -e "\n== [$1/$TOTAL_STEPS] $2 =="; }
backup_if_exists() {
local f="$1"
if [ -e "$f" ] && [ ! -L "$f" ]; then
mv "$f" "${f}.bak.${TIMESTAMP}"
echo " 기존 $f -> ${f}.bak.${TIMESTAMP} 로 백업"
fi
}
# ~/.vscode가 "생기는 시점"과 root LaunchAgent(setup_vscode_extensions.sh)의 확장
# 설치가 "끝나는 시점" 사이엔 수 분의 간극이 있다 -- 그 데몬은 mkdir -p로 디렉토리부터
# 만들고 나서야 code --install-extension을 돌리기 때문에, 디렉토리 존재가 아니라
# "설치 프로세스가 더 이상 안 떠 있음" + "알려진 확장 5종이 실제로 다 깔림" 두 가지를
# 함께 확인해야 안전하다(레이스 컨디션 경위는 DECISIONS.md 참고).
wait_for_vscode_extensions_daemon() {
local known_exts=(
ms-python.python
ms-python.vscode-pylance
eamodio.gitlens
ms-azuretools.vscode-docker
ms-ceintl.vscode-language-pack-ko
)
local elapsed=0
# 1) ~/.vscode 자체가 생기는 것부터 대기 (이 데몬이 없는 계정 이미지일 수도
# 있으니 짧게만 기다리고, 안 생기면 포기하고 그냥 넘어간다)
while [ ! -d "$HOME/.vscode" ] && [ "$elapsed" -lt 15 ]; do
sleep 1
elapsed=$((elapsed + 1))
done
if [ ! -d "$HOME/.vscode" ]; then
return 0
fi
# 2) 설치 프로세스가 아직 떠 있거나, 알려진 확장 5종이 아직 다 안 깔렸으면
# 최대 3분까지 계속 대기한다(보통 1~2분 안에 끝남). 3분을 넘기면 포기하고
# 경고만 남긴 뒤 진행 -- 무한 대기로 스크립트가 멈춰있는 것보다 낫다.
local max_wait=180
while [ "$elapsed" -lt "$max_wait" ]; do
local still_running=0
if pgrep -f "setup_vscode_extensions.sh" >/dev/null 2>&1; then
still_running=1
elif pgrep -f "code --install-extension" >/dev/null 2>&1; then
still_running=1
fi
local all_installed=1
local ext
for ext in "${known_exts[@]}"; do
if ! compgen -G "$HOME/.vscode/extensions/${ext}-*" >/dev/null 2>&1; then
all_installed=0
break
fi
done
if [ "$still_running" -eq 0 ] && [ "$all_installed" -eq 1 ]; then
return 0
fi
sleep 2
elapsed=$((elapsed + 2))
done
echo " 경고: ${max_wait}초 넘게 기다렸는데도 데몬 확장 설치 완료를 확인 못 함"
echo " -> 그래도 초기화는 진행하지만, 이후 데몬이 뒤늦게 확장을 다시"
echo " 채워 넣었을 수 있음. Settings Sync 켜기 전에 'ls ~/.vscode/extensions'로"
echo " 데몬 기본 확장(Python/Pylance/GitLens/Docker/한국어팩) 여부를 직접 확인할 것"
}
# ---------------------------------------------------------------------------
log 1 "dotfiles 적용 (.zshrc, git 설정)"
backup_if_exists "$HOME/.zshrc"
cp "$REPO_DIR/dotfiles/.zshrc" "$HOME/.zshrc"
echo " ~/.zshrc 적용"
backup_if_exists "$HOME/.gitconfig"
cp "$REPO_DIR/git/gitconfig" "$HOME/.gitconfig"
backup_if_exists "$HOME/.gitignore_global"
cp "$REPO_DIR/dotfiles/.gitignore_global" "$HOME/.gitignore_global"
echo " ~/.gitconfig, ~/.gitignore_global 적용"
# ---------------------------------------------------------------------------
log 2 "oh-my-zsh 설치"
if [ -d "$HOME/.oh-my-zsh" ]; then
echo " 이미 설치되어 있음"
else
RUNZSH=no CHSH=no KEEP_ZSHRC=yes \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
fi
# ---------------------------------------------------------------------------
log 3 "zsh-syntax-highlighting 설치 (~/.zsh, 관리자 권한 불필요)"
ZSH_SYNTAX_DIR="$HOME/.zsh/zsh-syntax-highlighting"
if [ -d "$ZSH_SYNTAX_DIR" ]; then
echo " 이미 설치되어 있음 ($ZSH_SYNTAX_DIR)"
else
mkdir -p "$HOME/.zsh"
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$ZSH_SYNTAX_DIR"
echo " git clone 완료 ($ZSH_SYNTAX_DIR)"
fi
# ---------------------------------------------------------------------------
log 4 "Powerline 폰트 설치 (agnoster 테마 / iTerm2 프로필이 요구하는 Fira Mono for Powerline)"
FONT_DIR="$HOME/Library/Fonts"
mkdir -p "$FONT_DIR"
for weight in Regular Bold Medium; do
font_file="$FONT_DIR/FuraMono-${weight} Powerline.otf"
if [ -f "$font_file" ]; then
echo " 이미 설치됨 (${weight})"
continue
fi
# 최종 경로에 바로 받으면, 다운로드 도중 실패해도 curl이 부분 파일을 지우지 않아
# 다음 재실행 때 존재 체크(-f)가 이걸 "이미 설치됨"으로 오인해 손상된 폰트가 그대로
# 남을 수 있다. 임시 파일에 받아서 성공했을 때만 최종 경로로 옮긴다.
tmp_font="$(mktemp)"
curl -f#L -o "$tmp_font" \
"https://raw.githubusercontent.com/powerline/fonts/master/FiraMono/FuraMono-${weight}%20Powerline.otf"
mv "$tmp_font" "$font_file"
echo " Fira Mono for Powerline (${weight}) 설치 완료"
done
# ---------------------------------------------------------------------------
log 5 "iTerm2 설치 및 프로필 복원"
if [ -w "/Applications" ]; then
APP_DIR="/Applications"
else
APP_DIR="$HOME/Applications"
mkdir -p "$APP_DIR"
echo " /Applications 쓰기 권한 없음 -> $APP_DIR 사용"
fi
if [ -d "$APP_DIR/iTerm.app" ]; then
echo " iTerm2 이미 설치되어 있음 ($APP_DIR/iTerm.app)"
else
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
zip="$tmp/iTerm2.zip"
curl -f#L -o "$zip" "https://iterm2.com/downloads/stable/latest"
ditto -x -k "$zip" "$tmp/extracted"
cp -R "$tmp/extracted/iTerm.app" "$APP_DIR/"
rm -rf "$tmp"
trap - EXIT
echo " iTerm.app -> $APP_DIR 설치 완료"
fi
# 앱 전역 설정(defaults import) 대신 프로필만 Dynamic Profile로 배치한다.
# 이유: com.googlecode.iterm2 도메인 전체를 import하면 색/폰트뿐 아니라 AI 기능 API
# 설정, 설치 ID 같은 이 맥에만 의미있는 값까지 같이 덮어써진다. Dynamic Profile은
# 프로필(색상/폰트/Hotkey Window 등)만 깨끗하게 반영하고 iTerm2 재시작도 필요 없다.
ITERM_DYN_DIR="$HOME/Library/Application Support/iTerm2/DynamicProfiles"
mkdir -p "$ITERM_DYN_DIR"
cp "$REPO_DIR/iterm2/codyssey-profiles.json" "$ITERM_DYN_DIR/codyssey-profiles.json"
echo " iTerm2 프로필(Default, Hotkey Window: Ctrl+\` 로 드롭다운 터미널) 적용 완료"
# Dynamic Profile로 추가되는 것만으로는 Cmd+N 새 창이 자동으로 이 프로필을 쓰지
# 않는다 -- iTerm2는 "Default Bookmark Guid"라는 별도 포인터가 가리키는 프로필을
# 새 창에 쓰는데, 이 값은 iTerm2 최초 설치 시 자동 생성된 공장 기본 프로필의 Guid로
# 고정되어 있어서 그대로 두면 테마가 하나도 안 먹은 것처럼 보인다(Hotkey Window는
# 프로필 자체에 단축키가 내장돼 있어 이 포인터와 무관하게 동작하므로 정상으로 보임).
# com.googlecode.iterm2 도메인 전체가 아니라 이 키 하나만 바꾸므로 AI 기능 API 설정,
# 설치 ID 등은 그대로 안전하다.
DEFAULT_PROFILE_GUID="$(python3 -c "
import json
with open('$ITERM_DYN_DIR/codyssey-profiles.json') as f:
data = json.load(f)
for p in data.get('Profiles', []):
if p.get('Name') == 'Default':
print(p['Guid'])
break
")"
if [ -n "$DEFAULT_PROFILE_GUID" ]; then
defaults write com.googlecode.iterm2 "Default Bookmark Guid" -string "$DEFAULT_PROFILE_GUID"
echo " 새 창 기본 프로필을 Codyssey Default($DEFAULT_PROFILE_GUID)로 지정 완료 (iTerm2 재시작 후 반영)"
fi
# ---------------------------------------------------------------------------
log 6 "macOS 시스템 설정 (키보드 단축키 전체, Dock 자동 숨김/앱 아이콘, 스크롤 방향)"
# macos/com.apple.symbolichotkeys.plist 는 이 계정에서 직접 설정한 커스텀 키보드 단축키를
# 통째로 export 해둔 것 -- 창 타일링(이분면 Option+화살표, 사분면 Ctrl+Option+U/I/J/K,
# System Settings > 키보드 단축키 > 윈도우에서 GUI로 설정한 값)도 여기 포함되어 있다.
# 예전엔 이 부분만 별도로 App Shortcuts(NSUserKeyEquivalents, 메뉴 이름 -> 단축키) 방식으로
# 흉내냈는데, 크롬처럼 메뉴에 해당 항목 자체가 없는 앱에서는 안 먹혀서 폐기했다. 심볼릭
# 핫키 ID 번호를 코드에서 직접 다루지 않고(macOS 버전마다 바뀔 수 있음) GUI에서 설정한
# 결과를 그대로 export해서 쓰기 때문에 안전하다.
defaults import com.apple.symbolichotkeys "$REPO_DIR/macos/com.apple.symbolichotkeys.plist"
echo " 키보드 단축키 전체 복원 완료 (창 타일링: 이분면 Option+화살표, 사분면 Ctrl+Option+U/I/J/K — 로그아웃 후 재로그인해야 완전히 반영될 수 있음)"
defaults write com.apple.dock autohide -bool true
killall Dock >/dev/null 2>&1 || true
echo " Dock 자동 가리기/보기 적용 완료"
# Dock 아이콘: 기존에 붙어있는 항목(교육맥 기본 앱들, 나중에 손으로 추가한 것 포함)은
# 그대로 두고, 크롬/iTerm2/VSCode/OrbStack/Discord 5개만 맨 뒤에 이 순서로 고정한다.
# 재실행 시 중복이 쌓이지 않도록 이 5개 이름과 일치하는 기존 항목은 먼저 제거한 뒤 다시
# 맨 뒤에 붙인다 -- 그 외 항목의 순서/구성은 절대 안 건드림. 설치 안 된 앱(이 교육맥엔
# 없는 앱)은 조용히 스킵. iTerm2는 5단계에서 계산한 $APP_DIR(/Applications 또는
# ~/Applications)를 그대로 재사용.
#
# 경로 문자열 비교 시 주의: Dock에 한 번 반영되고 나면 macOS가 경로를
# "file:///Applications/Google%20Chrome.app/" 처럼 URL 인코딩해서 저장하므로,
# 다음 실행에서 이름을 비교할 때 unquote 없이 비교하면 매번 새로 추가되어
# 중복이 쌓인다(unquote로 정규화해서 비교).
APP_DIR="${APP_DIR:-/Applications}"
python3 <<PYEOF
import plistlib, subprocess, os
from urllib.parse import unquote
WANTED = [
"/Applications/Google Chrome.app",
"$APP_DIR/iTerm.app",
"/Applications/Visual Studio Code.app",
"/Applications/OrbStack.app",
"/Applications/Discord.app",
]
wanted_names = {p.rstrip('/').split('/')[-1] for p in WANTED}
def entry_name(a):
url = a.get('tile-data', {}).get('file-data', {}).get('_CFURLString', '')
return unquote(url.rstrip('/').split('/')[-1])
out = subprocess.run(['defaults', 'export', 'com.apple.dock', '-'], capture_output=True, check=True)
data = plistlib.loads(out.stdout)
apps = data.get('persistent-apps', [])
kept = [a for a in apps if entry_name(a) not in wanted_names]
for path in WANTED:
if not os.path.isdir(path):
continue
kept.append({'tile-data': {'file-data': {'_CFURLString': path + '/', '_CFURLStringType': 0}}})
data['persistent-apps'] = kept
xml = plistlib.dumps(data, fmt=plistlib.FMT_XML)
subprocess.run(['defaults', 'import', 'com.apple.dock', '-'], input=xml, check=True)
PYEOF
killall Dock >/dev/null 2>&1 || true
echo " Dock 앱 아이콘(기존 뒤에 크롬/iTerm2/VSCode/OrbStack/Discord 순서로 고정) 적용 완료"
defaults write -g com.apple.swipescrolldirection -bool false
echo " 스크롤 방향(자연스러운 스크롤 끄기) 적용 완료 (열려있던 앱은 재시작해야 반영)"
# 창 타일링은 위에서(defaults import com.apple.symbolichotkeys) 이미 복원됨. 예전엔 여기서
# 앱 메뉴(NSUserKeyEquivalents) 방식으로 따로 등록했으나 크롬처럼 해당 메뉴 항목이 없는
# 앱에서는 동작하지 않아 폐기함 -- 자세한 경위는 위 6단계 시작 부분 주석 참고.
# ---------------------------------------------------------------------------
log 7 "GitHub Push 인증 (Personal Access Token)"
# credential.helper=osxkeychain은 git/gitconfig에 명시해뒀음(1단계에서 이미 ~/.gitconfig로
# 복사됨) -- 이 값은 macOS 어디서나 기본으로 켜져있는 게 아니라서, 명시하지 않으면 helper
# 바이너리가 있어도 아래 git credential approve가 저장할 곳이 없어 조용히 아무 일도 안 함
# (자세한 경위는 DECISIONS.md 참고).
if GIT_TERMINAL_PROMPT=0 git credential fill <<< $'protocol=https\nhost=github.com\n' 2>/dev/null | grep -q '^password='; then
echo " 이미 인증정보가 Keychain에 저장되어 있음"
else
echo " GitHub Fine-grained PAT가 필요함 (Repository access: 이 레포 선택, Contents: Read and write)"
echo " 발급: https://github.com/settings/personal-access-tokens/new"
read -rp " GitHub 아이디 [FickleBoBo]: " GH_USER
GH_USER="${GH_USER:-FickleBoBo}"
read -rsp " PAT 붙여넣기 (화면에 안 보임): " GH_PAT
echo
if [ -z "$GH_PAT" ]; then
echo " PAT 입력 안 함 -> 이 단계 스킵 (나중에 git push 시 자격증명 프롬프트가 뜸)"
else
# 검증 없이 바로 저장하면 오타/만료/권한 부족인 토큰도 "저장 완료"로 보여서,
# 실제로 틀렸다는 건 한참 뒤 첫 git push 때가 돼서야 드러난다. 저장 전에
# GitHub API로 토큰이 실제로 동작하는지 먼저 확인한다(URL이 아니라 헤더로
# 넘기므로 토큰 값 자체는 로그/에러 메시지에 노출되지 않음).
if curl -fsS -H "Authorization: token $GH_PAT" https://api.github.com/user >/dev/null 2>&1; then
git credential approve <<EOF
protocol=https
host=github.com
username=$GH_USER
password=$GH_PAT
EOF
echo " 토큰 확인 완료 -> Keychain에 저장 완료 (이후 git push는 별도 입력 없이 동작함)"
else
echo " PAT 확인 실패(토큰이 잘못됐거나 네트워크 문제) -> Keychain에 저장 안 함. bootstrap.sh를 다시 실행해서 재입력하세요."
fi
fi
fi
# ---------------------------------------------------------------------------
log 8 "Claude Code 설치"
# 공식 네이티브 설치 스크립트: 관리자 권한/Node.js/npm 없이 ~/.local/bin/claude 에
# 설치됨(설치 스크립트 자체가 심볼릭 링크를 그 경로로 관리). dotfiles/.zshrc가 맨 위에서
# 이미 $HOME/.local/bin 을 PATH에 넣어두므로 별도 PATH 조작이 필요 없다. 네이티브 설치는
# 백그라운드에서 자동 업데이트되므로 이미 설치돼 있으면 재실행하지 않고 스킵한다.
if [ -x "$HOME/.local/bin/claude" ]; then
echo " 이미 설치되어 있음 ($("$HOME/.local/bin/claude" --version 2>/dev/null || echo 버전 확인 실패))"
else
curl -fsSL https://claude.ai/install.sh | bash
echo " Claude Code 설치 완료 -> 새 터미널에서 'claude' 실행 후 브라우저로 로그인"
fi
# ---------------------------------------------------------------------------
log 9 "VSCode 기본 확장 초기화 (Settings Sync 오염 방지)"
# root LaunchAgent(com.codyssey.vscode.setup, 로그인 시 실행 + 10초 대기)가 ~/.vscode가
# 없을 때만 Python/Pylance/GitLens/Docker/한국어 팩 5종 확장을 자동 설치한다(research/에서
# 스크립트 원문 확인 -- settings.json/keybindings.json은 건드리지 않고 완전 기본값으로
# 남아있는 것도 함께 확인함, 그래서 여기선 확장만 다룬다). 이 상태 그대로 GitHub/Microsoft
# 계정으로 Settings Sync를 켜고 "병합"을 선택하면 이 5개가 내 클라우드 프로필에 영구적으로
# 섞여 들어가 개인 맥까지 오염될 수 있어, 로그인 후 직접 Sync를 켜기 전에 여기서 비워둔다.
#
# 데몬 설치가 실제로 끝날 때까지 기다린다 -- wait_for_vscode_extensions_daemon() 정의부
# 주석 참고(디렉토리 존재만으로 판단하면 안 되는 이유와 실기기 재현 경위).
wait_for_vscode_extensions_daemon
# bootstrap.sh는 같은 로그인 세션에서 여러 번 실행돼도 안전해야 하는데(멱등 원칙),
# 이미 Settings Sync로 로그인해서 내 확장을 깔아둔 뒤 재실행하면 그것까지 지워버리게
# 되므로 한 번 비운 뒤엔 마커 파일로 스킵한다. 다음 로그인엔 홈 디렉토리 자체가
# 삭제되므로 마커도 같이 사라져 자연스럽게 다시 초기화된다.
VSCODE_RESET_MARKER="$HOME/.codyssey_vscode_reset_done"
if [ -f "$VSCODE_RESET_MARKER" ]; then
echo " 이미 이번 로그인 세션에서 초기화함 -> 스킵"
else
pkill -f "Visual Studio Code.app" >/dev/null 2>&1 || true
sleep 1
backup_if_exists "$HOME/.vscode"
touch "$VSCODE_RESET_MARKER"
echo " 기본 확장(Python/Pylance/GitLens/Docker/한국어 팩) 백업 후 초기화 완료 (해당 없으면 스킵) -> 이제 VSCode를 열고 GitHub/Microsoft로 로그인해서 Settings Sync를 켜면 내 프로필만 그대로 내려받힘"
fi
echo
echo "부트스트랩 완료. 새 터미널을 열거나 'exec zsh' 로 zsh 설정을 다시 불러오세요."