# 사례: 로컬 → 서버 데이터 동기화 > 2026-02-20 실제 수행한 E-Sign 템플릿 마이그레이션 기록 ## 상황 로컬(Docker sam-mng-1)에서 만든 E-Sign 템플릿 3개를 서버(mng.codebridge-x.com)에 옮겨야 했다. 서버에는 템플릿이 0개, 로컬에는 3개 + PDF 파일 3개 + 필드 아이템 56개가 있었다. ## 환경 제약 | 제약 | 이유 | |------|------| | 서버 storage에 SCP 불가 | `esign/` 디렉토리가 `www-data:0700` → SSH 사용자(pro) 접근 불가 | | 서버에서 tinker로 파일 쓰기 불가 | tinker는 pro 사용자로 실행, www-data 디렉토리에 쓰기 권한 없음 | | 시더 사용 금지 | CLAUDE.md 규칙 | ## 실행 절차 ### Step 1. 로컬 데이터 추출 ```bash # 로컬 Docker에서 템플릿 데이터 확인 docker exec sam-mng-1 php artisan tinker --execute=" use App\Models\ESign\EsignFieldTemplate; \$templates = EsignFieldTemplate::where('is_active', true)->get(); foreach (\$templates as \$t) { echo 'ID: ' . \$t->id . ' | ' . \$t->name . ' | File: ' . \$t->file_path . PHP_EOL; } " ``` **결과:** ``` ID: 4 | 비밀유지서약서 | esign/1/templates/q9gGUPT4pCRUhsKXV9GwbymaqIwsM8bYf6QSYQT0.pdf ID: 5 | 영업파트너 계약서 | esign/1/templates/20260214171217_copy.pdf ID: 6 | 고객 서비스이용 계약서 | esign/1/templates/Zf5zGDl9Cj6l76DakqPyyu2JpDGIa7mwWBHSuVXm.pdf ``` ### Step 2. PDF 파일을 Docker → WSL 호스트로 꺼내기 ```bash mkdir -p /tmp/esign-templates docker cp sam-mng-1:/var/www/mng/storage/app/private/esign/1/templates/q9gGUPT4pCRUhsKXV9GwbymaqIwsM8bYf6QSYQT0.pdf /tmp/esign-templates/ docker cp sam-mng-1:/var/www/mng/storage/app/private/esign/1/templates/20260214171217_copy.pdf /tmp/esign-templates/ docker cp sam-mng-1:/var/www/mng/storage/app/private/esign/1/templates/Zf5zGDl9Cj6l76DakqPyyu2JpDGIa7mwWBHSuVXm.pdf /tmp/esign-templates/ ``` ### Step 3. WSL → 서버 /tmp에 base64로 전송 ```bash # SCP로 storage에 직접 넣을 수 없으므로, base64로 /tmp에 전송 base64 /tmp/esign-templates/q9gGUPT4pCRUhsKXV9GwbymaqIwsM8bYf6QSYQT0.pdf | ssh pro@114.203.209.83 "cat > /tmp/template_nda.b64" base64 /tmp/esign-templates/20260214171217_copy.pdf | ssh pro@114.203.209.83 "cat > /tmp/template_partner.b64" base64 /tmp/esign-templates/Zf5zGDl9Cj6l76DakqPyyu2JpDGIa7mwWBHSuVXm.pdf | ssh pro@114.203.209.83 "cat > /tmp/template_customer.b64" ``` ### Step 4. 서버에 임시 PHP 스크립트 작성 → curl로 실행 **핵심: `public/`에 PHP를 놓으면 nginx가 php-fpm(www-data)으로 실행하므로 storage 접근 가능** #### 4-1. PDF 저장 스크립트 (`migrate_templates.php`) ```php make(Illuminate\Contracts\Http\Kernel::class); $kernel->handle($request = Illuminate\Http\Request::capture()); use Illuminate\Support\Facades\Storage; header('Content-Type: text/plain; charset=utf-8'); $files = [ 'q9gGUPT4pCRUhsKXV9GwbymaqIwsM8bYf6QSYQT0.pdf' => '/tmp/template_nda.b64', '20260214171217_copy.pdf' => '/tmp/template_partner.b64', 'Zf5zGDl9Cj6l76DakqPyyu2JpDGIa7mwWBHSuVXm.pdf' => '/tmp/template_customer.b64', ]; foreach ($files as $name => $b64path) { $data = base64_decode(file_get_contents($b64path)); Storage::disk('local')->put('esign/1/templates/' . $name, $data); echo "Saved: $name (" . strlen($data) . " bytes)\n"; } ``` ```bash # 실행 curl -sk "https://mng.codebridge-x.com/migrate_templates.php" ``` **결과:** ``` Saved: q9gGUPT4pCRUhsKXV9GwbymaqIwsM8bYf6QSYQT0.pdf (215836 bytes) Saved: 20260214171217_copy.pdf (286772 bytes) Saved: Zf5zGDl9Cj6l76DakqPyyu2JpDGIa7mwWBHSuVXm.pdf (500493 bytes) ``` #### 4-2. DB INSERT 스크립트 (`migrate_templates_db.php`) 로컬에서 `/tmp/migrate_templates_db.php`로 작성 → SCP로 서버 `public/`에 업로드 → curl 실행 ```bash # 로컬에서 PHP 파일 작성 후 SCP 업로드 (public/ 디렉토리는 pro 사용자 쓰기 가능) scp /tmp/migrate_templates_db.php pro@114.203.209.83:/home/webservice/mng/public/ # 실행 curl -sk "https://mng.codebridge-x.com/migrate_templates_db.php" ``` **DB INSERT 스크립트 구조:** ```php Items: 26 Created: 영업파트너 계약서 (ID: 2) -> Items: 13 Created: 고객 서비스이용 계약서 (ID: 3) -> Items: 17 === All 3 templates migrated successfully! === ``` ### Step 5. 임시 파일 정리 ```bash # 서버 ssh pro@114.203.209.83 "rm -f /home/webservice/mng/public/migrate_templates.php \ /home/webservice/mng/public/migrate_templates_db.php \ /tmp/template_nda.b64 /tmp/template_partner.b64 /tmp/template_customer.b64" # 로컬 rm -rf /tmp/esign-templates /tmp/migrate_templates_db.php ``` ## 전체 흐름 요약 ``` [로컬 Docker] [WSL 호스트] [서버] 1. tinker로 데이터 확인 2. docker cp로 PDF 꺼냄 ──→ /tmp/에 PDF 파일들 3. base64 인코딩 + SSH ──→ /tmp/*.b64 도착 4. PHP 스크립트 SCP ──→ public/*.php 배치 5. curl 실행 ──→ www-data로 실행 ├─ base64 → Storage::put (PDF) └─ DB::beginTransaction ├─ Template::create └─ TemplateItem::create 6. 임시 파일 삭제 ──→ public/*.php, /tmp/*.b64 삭제 ``` ## 주의사항 - heredoc(`<< 'EOF'`)으로 서버에 PHP를 직접 쓰면 `$` 이스케이프 문제 발생 → **로컬에서 파일 작성 후 SCP 업로드** 권장 - `public/`에 놓은 PHP는 **누구나 접근 가능** → 작업 즉시 삭제 필수 - 서버 DB의 auto_increment ID는 로컬과 다를 수 있음 → FK는 새로 생성된 ID 기준으로 연결 - 트랜잭션으로 감싸서 중간 실패 시 롤백 보장