## 2026-05-02 — 웹 UI 비번 셋업/변경 + Windows .exe 빌드 파이프라인 + 사이드바 터널 단축버튼

### 배경

Electron 데스크탑 앱(.exe) 배포가 시작되면서 기존 CLI TTY 비밀번호 프롬프트 흐름이 어색해졌다. 사용자 입장에서 자연스러운 시나리오는 "프로그램 실행 → 브라우저 셋업 화면 → 비번 설정 → 외부 공유". 이 흐름과 일관되도록 npm CLI / Electron 양쪽의 부팅 게이트를 정리하고, 동시에 plaintext로 저장되던 비밀번호를 `node:crypto.scrypt` 해시로 격상. 부수적으로 Windows .exe 빌드를 더블클릭으로 끝낼 수 있는 `build-electron.bat`을 만들면서 만난 함정들(distutils 제거, prebuilt ABI 미스매치, winCodeSign symlink 권한, 외부 폰트 fetch 등)을 일괄 해결. 마지막으로 외부 공유 URL을 한 번에 받아 클립보드에 복사하는 사이드바 단축버튼 추가.

### 주요 변경

#### 1. 웹 UI 비번 셋업 + 변경 + scrypt 해시 격상 (`07e5a9d`, `6a90ef0`)

**보안 모델**: 로컬 프로세스를 띄운 사람이 곧 첫 사용자라는 점을 활용해 별도 setup token 없이도 안전. 단, **셋업 완료 전엔 Cloudflare 터널 자동 시작을 차단**해서 외부 노출이 가로채지 못하게 함.

- **`src/server/utils/password.ts`** (신규) — `scrypt$N=16384,r=8,p=1$<salt-b64>$<hash-b64>` self-describing 포맷. `hashPassword`/`verifyPassword` API. native deps 추가 없이 표준 `node:crypto`.
- **`src/server/index.ts`** 부팅 게이트 교체 — 기존 `if (!AUTH_PASSWORD) process.exit(1)` → setupMode 플래그. (a) DB hash 있음 → 정상 (b) hash 없고 env 있음 → 1회 마이그레이션 + `.password-migrated` flag 작성 + env 메모리 삭제 (c) 둘 다 없음 → setupMode=true. 라인 188 터널 가드에 `&& !setupMode` 추가.
- **`src/server/routes/auth.ts`** — `POST /login`을 plaintext compare → `verifyPassword`로 교체 + `req.session.createdAt` 세팅. `POST /setup` 신규 (hash 없을 때만, 409 가드). `PUT /password` 신규 (oldPassword verify, 본인 세션 keep-alive). `GET /status`에 `setupRequired` 필드 추가. 모든 인증 엔드포인트에 `authLimiter` 공유.
- **`src/server/middleware/auth.ts`** — `auth.password_changed_at`보다 오래된 `req.session.createdAt`을 가진 세션은 destroy 후 401. 비번 변경 시 다른 디바이스/탭은 다음 요청에 자동 로그아웃.
- **`src/server/types/session.d.ts`** — SessionData에 `createdAt?: number` 추가.
- **`bin/clitrigger.js`** — TTY 프롬프트 제거. config.json 없으면 `{port:3000, tunnel:true}`만 생성. legacy `config.password`가 있으면 한 번만 env로 forward → 서버가 hash 변환 후 `.password-migrated` flag → 다음 부팅에서 launcher가 plaintext 키 cleanup. `config password` 서브커맨드는 안내 문구로 deprecate.
- **`electron/main.cjs`** — random hex 비번 자동 생성 **제거** (기존엔 첫 부팅 시 `crypto.randomBytes(16).toString('hex')`로 박아 사용자가 알 수 없는 hex로 갇힘). `config.password`가 있을 때만 env forward. Help 메뉴의 "Show login password" 항목 제거(이젠 plaintext 보관 안 함). userData 위치(`%APPDATA%/clitrigger`)에서 `.password-migrated` flag 처리도 launcher와 동일 패턴.
- **클라이언트**:
  - `api/auth.ts` — `setupRequired` 응답 타입 + `setupPassword`/`changePassword` wrapper
  - `useAuth.ts` — `setupRequired` state + `setup()` handler
  - `App.tsx` — `if (setupRequired) <SetupPage>` 분기를 `!authenticated` 분기 위에 추가
  - `SetupPage.tsx` (신규) — LoginPage 시각 톤 재사용. 비번/확인 입력, 8자 이상 + 일치 검증, 셋업 완료 전엔 터널 자동 시작 안 됨 안내
  - `SettingsModal.tsx` (신규) — 좌측 탭(계정/터널) + 우측 패널 구조
  - `TunnelSettings.tsx` — 본문을 `TunnelSettingsPanel` named export로 분할. 기존 모달 wrapper는 default export로 호환 유지. 버튼 라벨 동적 — status가 `running`이면 "터널 재시작", 그 외엔 "터널 시작"
  - `PasswordSettingsPanel.tsx` (신규) — 현재 비번 verify + 새 비번 + 확인 + Save
  - `Sidebar.tsx` — 톱니가 SettingsModal 열도록 변경 (기존 TunnelSettings 직접 연결 → 통합 모달)
  - `i18n.tsx` — `setup.*`, `account.*`, `settings.*` prefix (en/ko)
- **`.env.example`** — `AUTH_PASSWORD=changeme  # 필수` → 비워두기 + optional 코멘트 (1회 마이그레이션 소스로만 사용)

#### 2. Windows .exe 빌드 파이프라인 + 아이콘 자동화 + ABI 안전장치 (`9a232bf`, `c3f0f7d`, `add9932`, `5774d44`, `6da9960`, `f0eddbe`)

**`scripts/build-electron.bat`** — 더블클릭만으로 6단계 자동화:
1. Node deps 자동 보장 (root + src/client, `node_modules\@electron\rebuild` 부재 시 `npm install`)
2. Python distutils 가드 — `setuptools` shim 자동 설치 (Python 3.12+ 대응)
3. `npm run electron:rebuild` — Electron ABI로 native 모듈 재컴파일
4. 아이콘 자동 생성 (`node scripts/generate-icons.cjs`)
5. 서버/클라이언트 빌드
6. `npx electron-builder --win`으로 NSIS + portable .exe 패키징

`@echo off` + `setlocal` + `chcp 65001` (UTF-8 codepage)로 한국어 Windows에서 ASCII 외 문자가 cmd parser를 깨지 않게 보호. `:error`/`:end` 라벨 + `pause`로 더블클릭 시 명령창이 즉시 닫히는 케이스 방지.

**`scripts/generate-icons.cjs`** (신규) — `src/client/public/logo-icon.svg` → `build/icon.png`(1024×1024) + `build/icon.ico`(16/32/48/64/128/256 멀티사이즈). `sharp` SVG 라스터라이저 + `png-to-ico` 멀티사이즈 묶기(v3 ESM이라 dynamic import). 두 파일 중 이미 존재하는 건 skip해서 사용자 손글씨 교체본 보존. electron-builder는 `build/`를 빌드 리소스 디렉토리로 자동 인식해서 .exe / NSIS 설치파일 / 타이틀바 / 작업 표시줄에 모두 동일 아이콘 임베드.

**`package.json` electron:rebuild에 `--force --build-from-source` 추가** — better-sqlite3 11.x의 prebuilt가 Electron 33 ABI(NODE_MODULE_VERSION 130)와 어긋나 ABI 127짜리(.node)가 패키징되는 문제(`The module ... was compiled against a different Node.js version using NODE_MODULE_VERSION 127. This version of Node.js requires NODE_MODULE_VERSION 130`). source build를 강제해서 매번 정확한 ABI로 재컴파일. 첫 source 빌드는 1~2분 추가되지만 ABI mismatch 재발 차단. dev 모드 복귀 시에는 `npm rebuild better-sqlite3` 한 번 실행하면 일반 Node ABI로 복원되며, bat의 success 메시지에도 안내 한 줄 추가.

**`electron/main.cjs` BrowserWindow.icon** — dev 모드에서만 `build/icon.png` 명시 (packaged 환경은 .exe 임베드 아이콘이 자동 적용되므로 가드).

**`package.json` devDeps 추가**: `sharp`(^0.33), `png-to-ico`(^2 → 실제 v3.0.1 설치).

**알려진 한계**: `logo-icon.svg`의 `@import url('Google Fonts JetBrains Mono')`는 sharp가 fetch 안 해서 ">_" 텍스트가 시스템 monospace로 fallback 렌더. 큰 사이즈에선 시각 차이 미미. 마음에 안 들면 `build/icon.png`를 사용자가 직접 교체 후 `del build\icon.ico` 하고 재빌드 → ICO만 새 PNG에서 재생성.

#### 3. 사이드바 Cloud 단축버튼 — 외부 URL 즉시 발급/복사 (`7880f1a`)

기존엔 외부 URL 받으려면 톱니 → 설정 → 터널 탭 → "터널 재시작" 클릭 → URL 노출의 4스텝. 이걸 사이드바 좌하단 컨트롤 행에 Cloud 아이콘 한 개로 압축.

- **`Sidebar.tsx`** — 톱니 직전에 Cloud 버튼 추가. 마운트 시 `tunnelApi.getTunnelStatus()` 1회 호출로 색상 판단 (running=accent, 그 외=tertiary). 클릭 동작:
  - `running` → 기존 URL을 `navigator.clipboard.writeText`로 복사 + 토스트
  - `stopped`/`error` → `tunnelApi.startTunnel()` → 발급된 URL 자동 클립보드 복사 + 토스트
  - busy 중엔 disabled
- tooltip은 running일 때 발급된 URL 자체, 아니면 "터널 시작"
- **i18n**: `tunnel.start`, `tunnel.starting`, `tunnel.urlCopied` (en/ko)

#### 4. 문서 정리 (`d656e62`)

- `README.md` / `README_KR.md` — 데스크탑/CLI 첫 실행 안내 문구를 웹 셋업 화면으로 일관 교체. `clitrigger config password` 예시 제거 + 웹 UI 안내. `.env`의 AUTH_PASSWORD를 optional로 명시.
- `docs/SETUP.md` — 첫 실행 콘솔 출력 예시를 새 흐름으로 교체. CLI 서브커맨드 표에서 `config password` 제거, `config tunnel hostname` 예시 추가. `.env` 항목 코멘트 정정.
- `CLAUDE.md` — Auth 섹션 전면 재작성(scrypt 포맷, setup mode 게이트, password_changed_at 세션 무효화, 4개 라우트). `bin/` 설명에 마이그레이션 흐름 + flag. Environment 섹션의 AUTH_PASSWORD optional 명시.

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/server/utils/password.ts` | NEW — scrypt hash/verify (self-describing 포맷) |
| `src/server/routes/auth.ts` | login → hash verify, setup/password 신규, status에 setupRequired |
| `src/server/middleware/auth.ts` | password_changed_at 보다 오래된 세션 자동 destroy |
| `src/server/index.ts` | 부팅 게이트 교체 + setupMode 플래그 + 터널 가드 + 마이그레이션 flag |
| `src/server/types/session.d.ts` | SessionData.createdAt? 추가 |
| `bin/clitrigger.js` | TTY 프롬프트 제거 + flag 기반 cleanup + config password deprecate |
| `electron/main.cjs` | random hex 자동생성 제거 + Help 메뉴 정리 + flag cleanup + dev icon prop |
| `src/client/src/api/auth.ts` | setupPassword/changePassword + setupRequired 타입 |
| `src/client/src/hooks/useAuth.ts` | setupRequired state + setup() handler |
| `src/client/src/App.tsx` | setupRequired 분기를 LoginPage 위에 |
| `src/client/src/components/SetupPage.tsx` | NEW — 첫 셋업 화면 |
| `src/client/src/components/SettingsModal.tsx` | NEW — 좌측 탭(계정/터널) + 우측 패널 |
| `src/client/src/components/PasswordSettingsPanel.tsx` | NEW — 비번 변경 폼 |
| `src/client/src/components/TunnelSettings.tsx` | TunnelSettingsPanel named export로 분할 + 라벨 동적 |
| `src/client/src/components/Sidebar.tsx` | 톱니 → SettingsModal + Cloud 단축버튼 추가 |
| `src/client/src/i18n.tsx` | setup.* / account.* / settings.* / tunnel.{start,starting,urlCopied} |
| `scripts/build-electron.bat` | NEW — 6단계 자동 빌드 (deps/python/rebuild/icons/build/package) |
| `scripts/generate-icons.cjs` | NEW — SVG → PNG/ICO 자동 생성 (skip-if-exists) |
| `package.json` | sharp + png-to-ico devDeps + electron:rebuild --force --build-from-source |
| `build/icon.png`, `build/icon.ico` | NEW — 자동 생성된 브랜드 아이콘 |
| `.env.example` | AUTH_PASSWORD optional 코멘트 |
| `README.md`, `README_KR.md`, `docs/SETUP.md`, `CLAUDE.md` | 비번 흐름 안내 일관 정리 |

### 아키텍처 결정

1. **scrypt vs bcrypt**: native 의존 추가 없는 표준 `node:crypto.scrypt` 채택. better-sqlite3/node-pty의 electron-rebuild 매트릭스에 추가 native 부담을 안 만듦. self-describing 포맷(`scrypt$N=...,r=...,p=...$salt$hash`) 덕에 향후 파라미터 변경 시 prefix 보고 lazy 마이그레이션 가능.
2. **셋업 모드 = 터널 가드**: 별도 setup token 도입 대신 "비번 hash 없으면 터널 자동 시작 차단"으로 외부 노출 가로채기 방지. 데스크탑 앱처럼 로컬 프로세스가 첫 사용자임이 자명한 컨텍스트에서 가장 가벼운 보호.
3. **비번 변경 시 본인 세션 keep-alive**: `req.session.createdAt`을 새로 박고 `password_changed_at` 갱신 → 미들웨어가 timestamp 비교로 다른 세션만 reject. 본인을 강제 로그아웃 안 시키는 UX 우선 + 다른 디바이스는 다음 요청에 자동 로그아웃.
4. **마이그레이션 flag 방식**: bin/Electron이 server에 plaintext password를 1회만 forward → 서버가 hash 변환 + `.password-migrated` flag 작성 → launcher가 다음 부팅에서 보고 plaintext 키 cleanup. server가 config 파일을 직접 만지지 않게 책임 경계 유지.
5. **build-from-source 강제 vs prebuilt 캐시**: better-sqlite3 11.x prebuilt는 Electron 33 ABI를 정확히 매치 못함. 매번 1~2분 추가되더라도 ABI mismatch 재발 차단이 더 중요. dev 모드는 `npm rebuild better-sqlite3` 한 번으로 일반 Node ABI 복원.
6. **사이드바 Cloud 버튼 vs 모달 단축**: 외부 URL은 빈도가 높고 시간 민감(공유 직전에 받음)이라 한 번 클릭 + 자동 클립보드가 모달 4스텝보다 압도적으로 빠름. 톱니 모달의 Tunnel 탭은 설정 변경(이름/도메인)용으로 역할 분리.

---
