Files
sam-manage/claudedocs/reference/data-sync-server-to-local.md

5.9 KiB

사례: 서버 → 로컬 데이터 동기화

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)

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)

# 서버 public/에 임시 PHP 작성
ssh pro@114.203.209.83 "cat > /home/webservice/mng/public/_export_pdf.php << 'SCRIPT'
<?php
require __DIR__.'/../vendor/autoload.php';
\$app = require_once __DIR__.'/../bootstrap/app.php';
\$kernel = \$app->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
"
# 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

# PDF를 Docker 컨테이너 안으로 복사
docker cp /tmp/server_template.pdf sam-mng-1:/tmp/server_template.pdf
# 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. 임시 파일 정리

# 서버 임시 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 curlbase64 -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 타임아웃 주의