Files
sam-manage/서버작업이력.md
2026-01-30 16:09:24 +09:00

13 KiB

서버 작업 이력

서버: mng.codebridge-x.com (114.203.209.83) 접속 계정: pro / sampass


2026-01-30

1. Nginx client_max_body_size 설정 (413 오류 해결)

문제: 명함 이미지 업로드 시 413 Content Too Large 오류 발생

작업 내용:

sudo nano /etc/nginx/sites-available/codebridge-x

mng.codebridge-x.com 서버 블록에 추가:

server {
    server_name mng.codebridge-x.com;
    client_max_body_size 20M;  # 추가됨
    ...
}
sudo nginx -t
sudo systemctl reload nginx

2. shared-storage 디렉토리 생성

문제: Unable to create a directory at /var/www/shared-storage/tenants 오류 발생

  • 서버 환경은 Docker가 아니므로 /var/www/ 경로가 존재하지 않음

작업 내용:

sudo mkdir -p /home/webservice/shared-storage/tenants
sudo chown -R www-data:www-data /home/webservice/shared-storage
sudo chmod -R 775 /home/webservice/shared-storage

결과 확인:

ls -la /home/webservice/shared-storage/
# drwxrwsr-x  3 www-data www-data  4096 Jan 30 14:19 .
# drwxrwsr-x  2 www-data www-data  4096 Jan 30 14:19 tenants

3. MNG .env 파일에 TENANT_STORAGE_PATH 추가

작업 내용:

# .env 파일에 추가
echo 'TENANT_STORAGE_PATH=/home/webservice/shared-storage/tenants' >> /home/webservice/mng/.env

설정 확인:

grep TENANT_STORAGE /home/webservice/mng/.env
# TENANT_STORAGE_PATH=/home/webservice/shared-storage/tenants

4. Laravel 캐시 클리어 및 PHP-FPM 재시작

작업 내용:

cd /home/webservice/mng
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear

sudo systemctl restart php8.4-fpm

설정 반영 확인:

php artisan tinker --execute="echo config('filesystems.disks.tenant.root');"
# /home/webservice/shared-storage/tenants

5. AI 설정 메뉴 추가

문제: 로컬에는 있던 "AI 설정" 메뉴가 서버에 없음

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\Commons\Menu;
$s = Menu::where("tenant_id", 1)->where("name", "시스템")->first();
if ($s) {
    $a = Menu::where("tenant_id", 1)->where("parent_id", $s->id)->where("url", "/system/ai-config")->first();
    if ($a) {
        echo "AI menu exists: " . $a->id;
    } else {
        $n = Menu::create([
            "tenant_id" => 1,
            "parent_id" => $s->id,
            "name" => "AI 설정",
            "url" => "/system/ai-config",
            "is_active" => true,
            "sort_order" => 3,
            "hidden" => false,
            "icon" => "cpu"
        ]);
        echo "Created: " . $n->id;
    }
}
'

6. AI Config 데이터 추가 (Gemini, GCS)

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\System\AiConfig;

$configs = [
    [
        "name" => "Gemini for Google Cloud API",
        "provider" => "gemini",
        "api_key" => "vertex_ai_service_account",
        "model" => "gemini-2.0-flash",
        "base_url" => "https://generativelanguage.googleapis.com/v1beta",
        "description" => null,
        "is_active" => true,
        "options" => [
            "region" => "us-central1",
            "auth_type" => "vertex_ai",
            "project_id" => "codebridge-chatbot",
            "service_account_path" => "/home/webservice/sales/apikey/google_service_account.json"
        ],
    ],
    [
        "name" => "Production GCS",
        "provider" => "gcs",
        "api_key" => "gcs_service_account",
        "model" => "-",
        "base_url" => "https://storage.googleapis.com",
        "description" => "음성 녹음 파일 백업용 (본사 연구)",
        "is_active" => true,
        "options" => [
            "bucket_name" => "codebridge-speech-audio-files",
            "service_account_path" => "/home/webservice/sales/apikey/google_service_account.json"
        ],
    ],
];

foreach ($configs as $config) {
    $existing = AiConfig::where("name", $config["name"])->first();
    if ($existing) {
        echo "Already exists: " . $config["name"] . "\n";
    } else {
        AiConfig::create($config);
        echo "Created: " . $config["name"] . "\n";
    }
}
'

7. AI Config service_account_path 경로 수정

문제: Docker 환경 경로 /var/www/sales/apikey/...가 서버에서 작동하지 않음

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\System\AiConfig;

$configs = AiConfig::all();
foreach ($configs as $config) {
    $options = $config->options ?? [];
    if (isset($options["service_account_path"])) {
        $oldPath = $options["service_account_path"];
        $newPath = str_replace("/var/www/sales/", "/home/webservice/sales/", $oldPath);
        if ($oldPath !== $newPath) {
            $options["service_account_path"] = $newPath;
            $config->options = $options;
            $config->save();
            echo "Updated " . $config->name . ": " . $newPath . "\n";
        }
    }
}
echo "Done.\n";
'

8. SalesRoleSeeder 실행

문제: 서버에 영업 관련 역할(권한)이 없음

작업 내용:

cd /home/webservice/mng
php artisan db:seed --class=SalesRoleSeeder

9. tenant-storage 심볼릭 링크 수정

문제: 명함 이미지가 저장되었지만 브라우저에서 로드되지 않음

  • 기존 심볼릭 링크가 잘못된 경로를 가리킴: /var/www/api/storage/app/tenants (존재하지 않음)

작업 내용:

cd /home/webservice/mng/public
rm tenant-storage
ln -s /home/webservice/shared-storage/tenants tenant-storage

결과 확인:

ls -la tenant-storage
# lrwxrwxrwx 1 pro develop 39 Jan 30 14:35 tenant-storage -> /home/webservice/shared-storage/tenants

10. sales_scenario_checklists 테이블 스키마 동기화

문제: 로컬과 서버의 테이블 구조 불일치 (컬럼 및 인덱스 누락)

추가된 컬럼:

ALTER TABLE sales_scenario_checklists ADD COLUMN scenario_type ENUM('sales','manager') NOT NULL DEFAULT 'sales' COMMENT '시나리오 유형' AFTER tenant_id;
ALTER TABLE sales_scenario_checklists ADD COLUMN checkpoint_id VARCHAR(50) DEFAULT NULL COMMENT '체크포인트 ID' AFTER step_id;
ALTER TABLE sales_scenario_checklists ADD COLUMN checked_at TIMESTAMP NULL DEFAULT NULL COMMENT '체크 일시' AFTER is_checked;
ALTER TABLE sales_scenario_checklists ADD COLUMN checked_by BIGINT UNSIGNED DEFAULT NULL COMMENT '체크한 사용자 ID' AFTER checked_at;
ALTER TABLE sales_scenario_checklists ADD COLUMN memo TEXT COMMENT '메모' AFTER checked_by;

추가된 인덱스:

ALTER TABLE sales_scenario_checklists ADD UNIQUE KEY sales_scenario_checkpoint_unique (tenant_id, scenario_type, step_id, checkpoint_id);
ALTER TABLE sales_scenario_checklists ADD INDEX sales_scenario_checklists_tenant_id_scenario_type_index (tenant_id, scenario_type);

작업 방법 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute="DB::statement('ALTER TABLE ...');"

11. 마이그레이션 파일 수정 (서버 호환성)

문제: 서버에서 git push 후 자동 마이그레이션 실행 시 여러 오류 발생

수정된 마이그레이션 파일들:

1) 2026_01_29_090000_fix_sales_scenario_checklists_unique_key.php

  • 오류: Can't DROP 'sales_scenario_unique'; check that column/key exists
  • 원인: 서버에 해당 인덱스가 없음 (이미 수동으로 다른 이름으로 생성됨)
  • 해결: 인덱스 삭제/생성 전 SHOW INDEX 쿼리로 존재 여부 확인
// 수정 전
$table->dropUnique('sales_scenario_unique');

// 수정 후
$indexes = DB::select("SHOW INDEX FROM sales_scenario_checklists WHERE Key_name = 'sales_scenario_unique'");
if (count($indexes) > 0) {
    $table->dropUnique('sales_scenario_unique');
}

2) 2026_01_29_093000_add_gcs_uri_to_sales_consultations.php

  • 오류: Table 'sam.sales_consultations' doesn't exist
  • 원인: 테이블 생성 마이그레이션보다 먼저 실행됨
  • 해결: Schema::hasTable() 체크 추가
public function up(): void
{
    if (!Schema::hasTable('sales_consultations')) {
        return;
    }
    // ... 기존 로직
}

3) 2026_01_29_100200_create_sales_scenario_checklists_table.php

  • 오류: Table 'sales_scenario_checklists' already exists
  • 원인: 서버에 이미 테이블이 존재 (수동 생성됨)
  • 해결: Schema::hasTable() 체크 추가
public function up(): void
{
    if (Schema::hasTable('sales_scenario_checklists')) {
        return;
    }
    Schema::create('sales_scenario_checklists', function (Blueprint $table) {
        // ...
    });
}

4) 2026_01_30_150000_add_missing_columns_to_sales_scenario_checklists_table.php

  • 오류: Method getDoctrineSchemaManager does not exist
  • 원인: Laravel 12에서 Doctrine DBAL 제거됨
  • 해결: DB::select("SHOW INDEX ...") 쿼리로 대체
// 수정 전 (Laravel 11 이하)
$sm = Schema::getConnection()->getDoctrineSchemaManager();
$indexes = $sm->listTableIndexes('sales_scenario_checklists');

// 수정 후 (Laravel 12 호환)
$uniqueExists = DB::select("SHOW INDEX FROM sales_scenario_checklists WHERE Key_name = 'sales_scenario_checkpoint_unique'");
if (empty($uniqueExists)) {
    // 인덱스 추가
}

참고: 마이그레이션 작성 가이드 (Laravel 12)

안전한 마이그레이션 패턴

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

// 1. 테이블 존재 여부 확인
if (Schema::hasTable('table_name')) { ... }

// 2. 컬럼 존재 여부 확인
if (Schema::hasColumn('table_name', 'column_name')) { ... }

// 3. 인덱스 존재 여부 확인 (Laravel 12)
$indexes = DB::select("SHOW INDEX FROM table_name WHERE Key_name = 'index_name'");
if (empty($indexes)) { ... }

주의사항

  • Laravel 12에서 getDoctrineSchemaManager() 사용 불가
  • 테이블/컬럼 생성 전 항상 존재 여부 체크
  • 인덱스 삭제 전 항상 존재 여부 체크

12. 배포 스크립트 수정 (심볼릭 링크 자동 복원)

문제: git push 후 자동 배포 시 git checkout . 명령으로 인해 심볼릭 링크가 원래 값으로 덮어씌워짐

수정 파일: /home/webservice/script/pull_mng.sh

추가된 코드:

# 심볼릭 링크 수정 (서버 경로에 맞게)
echo "심볼릭 링크 수정 중..." >> $LOGFILE
cd /home/webservice/mng/public
rm -f tenant-storage
ln -s /home/webservice/shared-storage/tenants tenant-storage
echo "심볼릭 링크 수정 완료" >> $LOGFILE

효과: 이제 git push 후에도 tenant-storage 심볼릭 링크가 자동으로 /home/webservice/shared-storage/tenants를 가리키게 됨


13. 영업관리 - 대시보드 메뉴 추가

문제: 로컬에는 있는 "영업관리 - 대시보드" 메뉴가 서버에 없음

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\Commons\Menu;
$sales = Menu::where("tenant_id", 1)->where("name", "영업관리")->first();
if ($sales) {
    $m = Menu::create([
        "tenant_id" => 1,
        "parent_id" => $sales->id,
        "name" => "대시보드",
        "url" => "/sales/salesmanagement/dashboard",
        "is_active" => true,
        "sort_order" => 0,
        "hidden" => false,
        "icon" => "chart-bar"
    ]);
    echo "Created: " . $m->id;
}
'

참고: Docker vs 서버 경로 차이

항목 Docker (로컬) 서버
웹 루트 /var/www/ /home/webservice/
MNG 앱 /var/www/mng/ /home/webservice/mng/
API 앱 /var/www/api/ /home/webservice/api/
Sales 앱 /var/www/sales/ /home/webservice/sales/
공유 스토리지 /var/www/shared-storage/ /home/webservice/shared-storage/

서버에 새로운 설정을 추가할 때는 경로 차이를 반드시 확인해야 합니다.


14. 영업관리 - 대시보드 메뉴 정렬 순서 수정

문제: 대시보드 메뉴가 "영업담당자 관리" 아래에 표시됨 (가장 위에 있어야 함)

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\Commons\Menu;

// 대시보드를 sort_order -1로 설정 (가장 위)
Menu::where("tenant_id", 1)->where("parent_id", 15385)->where("url", "/sales/salesmanagement/dashboard")->update(["sort_order" => -1]);

// 결과 확인
$children = Menu::where("tenant_id", 1)->where("parent_id", 15385)->orderBy("sort_order")->get();
foreach ($children as $c) {
    echo $c->name . " | sort:" . $c->sort_order . PHP_EOL;
}
'

결과:

대시보드 | sort:-1
영업담당자 관리 | sort:0
가망고객 관리 | sort:1
영업실적 관리 | sort:2

15. 영업관리 - 상품관리 메뉴 추가

문제: 로컬에는 있는 "영업관리 - 상품관리" 메뉴가 서버에 없음

작업 내용 (tinker 사용):

cd /home/webservice/mng
php artisan tinker --execute='
use App\Models\Commons\Menu;

$parentId = 15385;
$m = Menu::create([
    "tenant_id" => 1,
    "parent_id" => $parentId,
    "name" => "상품관리",
    "url" => "/sales/products",
    "is_active" => true,
    "sort_order" => 4,
    "hidden" => false,
    "icon" => "cube"
]);
echo "Created: " . $m->id;
'

결과: Menu ID 15400 생성