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