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

6.3 KiB

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

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. 로컬 데이터 추출

# 로컬 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 호스트로 꺼내기

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로 전송

# 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
// /home/webservice/mng/public/migrate_templates.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;
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";
}
# 실행
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 실행

# 로컬에서 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
// Laravel 부트스트랩 + DB::beginTransaction()
// EsignFieldTemplate::create([...])    ← 템플릿 레코드
// EsignFieldTemplateItem::create([...]) ← 필드 아이템 (FK: template_id)
// DB::commit()

결과:

Created: 비밀유지서약서 (ID: 1)        -> Items: 26
Created: 영업파트너 계약서 (ID: 2)      -> Items: 13
Created: 고객 서비스이용 계약서 (ID: 3)  -> Items: 17
=== All 3 templates migrated successfully! ===

Step 5. 임시 파일 정리

# 서버
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 기준으로 연결
  • 트랜잭션으로 감싸서 중간 실패 시 롤백 보장