# 사례: 서버 → 로컬 데이터 동기화 > 2026-02-20 실제 수행한 E-Sign 템플릿 역동기화 기록 ## 상황 서버(mng.codebridge-x.com)에서 새로 만든 "영업파트너 계약서(단체용)" 템플릿 1개를 로컬(Docker sam-mng-1)에 가져와야 했다. ## 환경 제약 | 제약 | 이유 | |------|------| | 서버 storage에서 SCP 다운로드 불가 | `esign/` 디렉토리가 `www-data:0700` → SSH(pro) 읽기 불가 | | 서버 tinker는 pro 사용자 | storage 파일 읽기 불가, DB 조회만 가능 | ## 실행 절차 ### Step 1. 서버 데이터 추출 (DB) ```bash ssh pro@114.203.209.83 "cd /home/webservice/mng && php artisan tinker --execute=\" use App\Models\ESign\EsignFieldTemplate; use App\Models\ESign\EsignFieldTemplateItem; \\\$t = EsignFieldTemplate::where('name', 'like', '%단체%')->first(); echo 'Name: ' . \\\$t->name . PHP_EOL; echo 'File Path: ' . \\\$t->file_path . PHP_EOL; echo 'Variables: ' . json_encode(\\\$t->variables, JSON_UNESCAPED_UNICODE) . PHP_EOL; # ... 필드 아이템도 JSON으로 추출 \"" ``` **결과:** ``` Name: 영업파트너 계약서(단체용) File Path: esign/1/templates/7Vi374wZwcRg5bjlXPJiASkizxLSJlCNDJhaMBeo.pdf Variables: [{"key":"partnername","type":"text","label":"파트너명"}, ...] Items: 13개 (JSON) ``` **포인트:** tinker는 `pro` 사용자지만 DB 읽기는 가능. 파일 읽기만 불가. ### Step 2. 서버 PDF 파일 추출 (임시 PHP → base64) ```bash # 서버 public/에 임시 PHP 작성 ssh pro@114.203.209.83 "cat > /home/webservice/mng/public/_export_pdf.php << 'SCRIPT' make(Illuminate\Contracts\Http\Kernel::class); \$kernel->handle(\$request = Illuminate\Http\Request::capture()); use Illuminate\Support\Facades\Storage; \$data = Storage::disk('local')->get('esign/1/templates/7Vi374wZwcRg5bjlXPJiASkizxLSJlCNDJhaMBeo.pdf'); header('Content-Type: text/plain'); echo base64_encode(\$data); SCRIPT " ``` ```bash # curl로 base64 다운로드 → 디코딩 curl -sk "https://mng.codebridge-x.com/_export_pdf.php" | base64 -d > /tmp/server_template.pdf ``` **결과:** ``` -rw-r--r-- 1 aweso aweso 274929 Feb 20 08:37 /tmp/server_template.pdf ``` **핵심: curl → nginx → php-fpm(www-data) → Storage::get() 가능** ### Step 3. 로컬 Docker에 파일 + DB INSERT ```bash # PDF를 Docker 컨테이너 안으로 복사 docker cp /tmp/server_template.pdf sam-mng-1:/tmp/server_template.pdf ``` ```bash # tinker로 파일 저장 + DB INSERT (로컬 Docker는 www-data로 실행되므로 Storage 접근 가능) docker exec sam-mng-1 php artisan tinker --execute=" use Illuminate\Support\Facades\Storage; use App\Models\ESign\EsignFieldTemplate; use App\Models\ESign\EsignFieldTemplateItem; use Illuminate\Support\Facades\DB; // PDF 저장 \$data = file_get_contents('/tmp/server_template.pdf'); Storage::disk('local')->put('esign/1/templates/7Vi374wZwcRg5bjlXPJiASkizxLSJlCNDJhaMBeo.pdf', \$data); // DB INSERT DB::beginTransaction(); \$t = EsignFieldTemplate::create([ 'tenant_id' => 1, 'name' => '영업파트너 계약서(단체용)', 'description' => '30%, 3%의 요율이 적용됩니다.', 'category' => '영업파트너', 'signer_count' => 2, 'file_path' => 'esign/1/templates/7Vi374wZwcRg5bjlXPJiASkizxLSJlCNDJhaMBeo.pdf', 'file_name' => '영업파트너 위촉계약서(단체용).pdf', 'file_hash' => 'dcf4309399b4161f2b58f7e86688026935c616a41217243c3b7d020d91529b12', 'file_size' => 274929, 'variables' => [...], // Step 1에서 추출한 JSON 'is_active' => true, 'created_by' => 1, ]); // 필드 아이템 INSERT (Step 1에서 추출한 JSON 사용) foreach (\$items as \$item) { \$item['template_id'] = \$t->id; // 새로 생성된 ID로 FK 연결 EsignFieldTemplateItem::create(\$item); } DB::commit(); " ``` **결과:** ``` PDF saved: 274929 bytes Template created: ID 7 Items created: 13 Done! ``` ### Step 4. 임시 파일 정리 ```bash # 서버 임시 PHP 삭제 ssh pro@114.203.209.83 "rm -f /home/webservice/mng/public/_export_pdf.php" # 로컬 임시 파일 삭제 rm -f /tmp/server_template.pdf docker exec sam-mng-1 rm -f /tmp/server_template.pdf ``` ## 전체 흐름 요약 ``` [서버] [WSL 호스트] [로컬 Docker] 1. tinker로 DB 데이터 추출 (JSON) 2. public/에 임시 PHP 배치 (Storage::get → base64) 3. curl로 base64 다운 ←── _export_pdf.php 실행 base64 -d > /tmp/pdf 4. docker cp ──→ /tmp/에 PDF 도착 5. tinker로 실행 ├─ Storage::put (PDF) └─ DB INSERT (Template + Items) 6. 임시 파일 삭제 ``` ## 로컬 → 서버와의 차이점 | 단계 | 로컬 → 서버 | 서버 → 로컬 | |------|-------------|-------------| | DB 추출 | `docker exec tinker` | `ssh tinker` | | 파일 추출 | `docker cp` → WSL | `public/` PHP → `curl` → WSL | | 파일 전송 | `base64` → SSH → `/tmp` | `curl` → `base64 -d` → `/tmp` | | 파일 저장 | `public/` PHP (www-data) | `docker exec tinker` (www-data) | | DB INSERT | `public/` PHP (www-data) | `docker exec tinker` (www-data) | **핵심 차이:** - **서버에 쓸 때**: SSH로 storage 접근 불가 → `public/` PHP + curl 필수 - **로컬에 쓸 때**: Docker tinker가 이미 www-data → tinker로 직접 가능 (PHP 스크립트 불필요) ## 주의사항 - 서버 export PHP는 **인증 없이 접근 가능** → 파일 경로 노출 위험 → 즉시 삭제 - 서버 template ID와 로컬 template ID는 다름 (auto_increment) → FK는 새 ID 사용 - 대용량 파일(수십 MB)은 base64로 약 33% 증가 → curl 타임아웃 주의