diff --git a/deploys/ops-manual/04-service-cicd.md b/deploys/ops-manual/04-service-cicd.md index cf11c59..be941ac 100644 --- a/deploys/ops-manual/04-service-cicd.md +++ b/deploys/ops-manual/04-service-cicd.md @@ -48,7 +48,7 @@ sudo journalctl -u jenkins --since "2 hours ago" --no-pager | Credential ID | 유형 | 용도 | |--------------|------|------| | deploy-ssh-key | SSH Username with private key | 운영/개발서버 SSH 배포 | -| gitea-api-token | Secret text | Gitea API 연동 | +| gitea-api-token | Username with password | Gitea API 연동 (token을 username, 비밀번호 빈값) | **Credential 위치:** Jenkins 관리 > Credentials > System > Global credentials @@ -181,18 +181,24 @@ sudo -u git /usr/local/bin/gitea admin user create \ **토큰 파일 (개발서버):** `/data/GIT/.cicd-env` (chmod 600, owner: git) -| 저장소 | 동기화 브랜치 | -|--------|-------------| -| sam-react-prod | stage, develop | -| sam-api | stage | -| sam-sales | main | -| sam-manage | 없음 (배포관리자 수동 push) | +| 저장소 | 동기화 브랜치 | 비고 | +|--------|-------------|------| +| sam-react-prod | main, develop | post-update hook 비활성화 (CI/CD가 개발서버 배포 담당) | +| sam-api | main | develop은 기존 post-update hook 유지 | +| sam-sales | main | | +| sam-manage | main | 2026-02-24 hook 추가 | + +> **참고:** react의 개발서버 배포는 Jenkins CI/CD 파이프라인이 처리한다. +> 기존 post-update hook의 git pull 방식(`pull_react.sh`)은 비활성화됨 (2026-02-24). +> 스크립트 위치: `/home/webservice/script/pull_react.sh` **동기화 로그 확인:** ```bash -ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-react-prod.log" -ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-api.log" +ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_react-prod.log" +ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_api.log" +ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sales.log" +ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_manage.log" ``` --- @@ -303,6 +309,13 @@ sudo systemctl status nginx | /etc/nginx/sites-available/ci.sam.it.kr | Jenkins 리버스 프록시 | | /etc/nginx/sites-available/monitor.sam.it.kr | Grafana 리버스 프록시 | +**git.sam.it.kr 주요 설정:** + +```nginx +client_max_body_size 500M; # 대용량 Git push 허용 +proxy_request_buffering off; # 스트리밍 전송 (413 방지) +``` + --- ## node_exporter / Certbot / fail2ban / UFW diff --git a/deploys/ops-manual/05-deployment.md b/deploys/ops-manual/05-deployment.md index 96c0b34..6cffb22 100644 --- a/deploys/ops-manual/05-deployment.md +++ b/deploys/ops-manual/05-deployment.md @@ -17,28 +17,28 @@ | 저장소 | 파이프라인 | 트리거 브랜치 | 배포 대상 | |--------|-----------|-------------|----------| -| sam-react-prod | React 빌드+배포 | develop, stage, main | 개발/Stage/운영 | -| sam-api | Laravel API 배포 | stage, main | Stage/운영 | -| sam-manage | Laravel Admin 배포 | main | 운영 | -| sam-sales | 레거시 PHP 배포 | main | 운영 | +| sam-react-prod | React 빌드+배포 | develop, main | 개발 / Stage→승인→운영 | +| sam-api | Laravel API 배포 | main | Stage→승인→운영 | +| sam-manage | Laravel Admin 배포 | main | 운영 (직접) | +| sam-sales | 레거시 PHP 배포 | main | 운영 (직접) | -### 브랜치별 동작 +### 2-Branch 전략 (develop + main) + +> **stage 브랜치 없음.** main 브랜치 push 시 Stage 자동 배포 → Jenkins 승인 → Production 배포. | 브랜치 | react | api | mng | sales | |--------|-------|-----|-----|-------| -| develop | Jenkins 빌드 -> 개발서버 | 기존 hook | 기존 hook | 기존 hook | -| stage | Jenkins 빌드 -> 운영 Stage | Jenkins -> 운영 Stage | - | - | -| main | Jenkins 빌드 -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 pull | +| develop | Jenkins 빌드 → 개발서버 | 기존 post-update hook | 기존 post-update hook | 기존 post-update hook | +| main | Stage 배포 → **승인** → Production 배포 | Stage 배포 → **승인** → Production 배포 | Production 직접 배포 | Production 직접 배포 | -**main 브랜치 배포:** 배포관리자가 CI/CD Gitea에 수동 push 후 자동 실행 +**main 브랜치 배포 흐름 (react/api):** +1. 개발자가 develop → main 머지 후 push +2. post-receive hook → CI/CD Gitea 자동 push +3. Jenkins 빌드 → Stage 자동 배포 +4. Jenkins UI에서 **승인 클릭** → Production 배포 (24시간 타임아웃) -```bash -# 1회 remote 등록 -git remote add production https://git.sam.it.kr/SamProject/sam-react-prod.git - -# 운영 배포 시 -git push production main -``` +**main 브랜치 배포 흐름 (mng/sales):** +1. 개발자가 main push → hook → CI/CD Gitea → Jenkins → Production 직접 배포 --- @@ -51,7 +51,7 @@ git push production main ``` 개발자 로컬 - │ git push origin (develop/stage/main) + │ git push origin (develop / main) ▼ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자의 origin │ @@ -59,30 +59,21 @@ git push production main │ ├─ api/mng/sales: 기존 post-update hook (개발서버 pull) ← 현행 유지 │ └─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 개발서버 배포 │ - ├─ stage push 시 - │ ├─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 운영서버 Stage 배포 - │ └─ api: hook → CI/CD Gitea push → Jenkins → 운영서버 Stage pull - │ - └─ main push 시 (react/mng/api) - └─ ❌ CI/CD Gitea에 자동 push 안함 - → 배포관리자가 수동으로 CI/CD Gitea에 push - → Jenkins 자동 배포 - -별도 처리: - sales/www(landing): hook → CI/CD Gitea → Jenkins → 운영서버 pull + └─ main push 시 + ├─ react: hook → CI/CD Gitea → Jenkins 빌드 → Stage 배포 → 승인 → Production 배포 + ├─ api: hook → CI/CD Gitea → Jenkins → Stage 배포 → 승인 → Production 배포 + ├─ mng: hook → CI/CD Gitea → Jenkins → Production 직접 배포 + └─ sales: hook → CI/CD Gitea → Jenkins → Production 직접 배포 ``` ### 브랜치별 배포 정책 상세 | 브랜치 | 저장소 | CI/CD Gitea 동기화 | Jenkins 배포 | 배포 대상 | |--------|--------|-------------------|-------------|----------| -| **stage** | react | 자동 (hook) | 빌드 + rsync | 운영서버 Stage | -| **stage** | api | 자동 (hook) | SSH pull | 운영서버 Stage | -| **main** | react | 수동 (배포관리자) | 빌드 + rsync | 운영서버 Production | -| **main** | mng | 수동 (배포관리자) | SSH deploy | 운영서버 Production | -| **main** | api | 수동 (배포관리자) | SSH deploy | 운영서버 Production | -| **main** | sales | 자동 (hook) | SSH pull | 운영서버 Production | -| **main** | www | 자동 (hook) | SSH pull | 운영서버 Production | +| **main** | react | 자동 (hook) | 빌드 → Stage → **승인** → 재빌드 → Production | Stage + Production | +| **main** | api | 자동 (hook) | rsync → Stage → **승인** → rsync → Production | Stage + Production | +| **main** | mng | 자동 (hook) | rsync + npm build → Production | Production | +| **main** | sales | 자동 (hook) | rsync → Production | Production | | **develop** | react | 자동 (hook) | 빌드 → 개발서버 배포 | 개발서버 | | **develop** | api/mng/sales | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) | @@ -90,11 +81,11 @@ git push production main | 저장소 | hook 대상 브랜치 | 동작 | |--------|-----------------|------| -| sam-react-prod | stage, develop | CI/CD Gitea에 push | -| sam-api | stage | CI/CD Gitea에 push | +| sam-react-prod | main, develop | CI/CD Gitea에 push | +| sam-api | main | CI/CD Gitea에 push | +| sam-manage | main | CI/CD Gitea에 push | | sam-sales | main | CI/CD Gitea에 push | | sam-landing | main | CI/CD Gitea에 push | -| sam-manage | ❌ 없음 | main만 사용, 배포관리자 수동 push | hook 스크립트 경로: `/data/GIT/samproject/.git/hooks/post-receive.d/push-to-cicd` 토큰 환경변수: `/data/GIT/.cicd-env` (chmod 600, owner: git) @@ -117,7 +108,7 @@ Repository Settings → Webhooks → Add Webhook (Gitea) ``` 개발자 로컬 - │ git push origin (develop / stage / main) + │ git push origin (develop / main) ▼ ┌──────────────────────────────────────────────────────────────┐ │ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자 origin │ @@ -132,46 +123,42 @@ Repository Settings → Webhooks → Add Webhook (Gitea) │ │ sales → 기존 post-update hook (pull) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ -│ ┌─ stage push ──────────────────────────────────────────┐ │ -│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │ -│ │ → rsync → 운영서버 Stage + PM2 reload │ │ +│ ┌─ main push (모든 저장소 자동) ────────────────────────┐ │ +│ │ react → hook: CI/CD Gitea push ──→ Jenkins │ │ +│ │ → Stage 빌드+배포 → 승인 → Production 재빌드 │ │ │ │ api → hook: CI/CD Gitea push ──→ Jenkins │ │ -│ │ → 운영서버 Stage Release + 심링크 │ │ -│ └───────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─ main push (sales/www만 자동) ────────────────────────┐ │ +│ │ → Stage rsync+배포 → 승인 → Production 배포 │ │ +│ │ mng → hook: CI/CD Gitea push ──→ Jenkins │ │ +│ │ → Production rsync + build │ │ │ │ sales → hook: CI/CD Gitea push ──→ Jenkins │ │ -│ │ → 운영서버 rsync │ │ -│ │ www → hook: CI/CD Gitea push ──→ Jenkins │ │ -│ │ → 운영서버 pull │ │ -│ │ react/mng/api → ❌ 자동 push 안함 │ │ +│ │ → Production rsync │ │ │ └───────────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────────────┘ -┌─ 운영 배포 (main - react/mng/api) ──────────────────────────┐ +┌─ Jenkins 승인 흐름 (react/api main) ─────────────────────────┐ │ │ -│ 배포관리자 로컬 │ -│ │ git push production main (CI/CD Gitea remote) │ -│ ▼ │ -│ CI/CD Gitea (git.sam.it.kr) │ -│ │ Webhook │ -│ ▼ │ -│ Jenkins → 운영서버 배포 │ -│ react: CI/CD 빌드 → rsync → PM2 reload │ -│ api: Release + 심링크 → PHP-FPM reload │ -│ mng: Release + 심링크 → PHP-FPM reload │ +│ Jenkins 빌드 시작 │ +│ │ │ +│ ├─ Stage 자동 배포 (react: .env.stage 빌드) │ +│ │ │ +│ ├─ ⏸️ 승인 대기 (24시간 타임아웃) │ +│ │ https://ci.sam.it.kr 에서 "운영 배포 진행" 클릭 │ +│ │ │ +│ ├─ Production 배포 (react: .env.main 재빌드) │ +│ │ │ +│ └─ 완료 │ │ │ └───────────────────────────────────────────────────────────────┘ ``` ### 환경별 배포 비교 -| 항목 | 운영 (main) | Stage (stage) | 개발 (develop) | -|------|------------|---------------|----------------| -| **트리거** | 배포관리자 수동 push | 자동 (hook) | react만 자동 (hook), 나머지 기존 hook | -| **react 전략** | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | -| **api 전략** | Release + 심링크 | Release + 심링크 | 기존 post-update (pull) | -| **mng 전략** | Release + 심링크 | - | 기존 post-update (pull + build) | +| 항목 | Production (main→승인) | Stage (main→자동) | 개발 (develop) | +|------|----------------------|------------------|----------------| +| **트리거** | main push → Jenkins 승인 | main push → 자동 | react만 자동 (hook), 나머지 기존 hook | +| **react 전략** | CI/CD 빌드(.env.main) → rsync | CI/CD 빌드(.env.stage) → rsync | CI/CD 빌드(.env.develop) → rsync | +| **api 전략** | rsync + Release 심링크 | rsync + Release 심링크 | 기존 post-update (pull) | +| **mng 전략** | rsync + npm build + Release 심링크 | - | 기존 post-update (pull + build) | | **롤백** | 이전 릴리즈 심링크 | 이전 릴리즈 심링크 | git revert | | **릴리즈 보관** | 최근 5개 | 최근 3개 | - | @@ -188,11 +175,11 @@ CI/CD Gitea push -> Webhook -> Jenkins **브랜치별 배포 대상:** -| 브랜치 | 대상 서버 | 대상 경로 | PM2 이름 | 트리거 | -|--------|----------|----------|----------|--------| -| develop | 개발서버 (114.203.209.83) | /home/webservice/react/ | sam-react | 자동 (hook) | -| stage | 운영서버 (211.117.60.189) | /home/webservice/react-stage/releases/ | sam-front-stage | 자동 (hook) | -| main | 운영서버 (211.117.60.189) | /home/webservice/react/releases/ | sam-front | 수동 push | +| 브랜치 | 배포 단계 | 대상 서버 | 대상 경로 | PM2 이름 | +|--------|----------|----------|----------|----------| +| develop | 개발서버 | 114.203.209.83 | /home/webservice/react/ | sam-react | +| main | Stage (자동) | 211.117.60.189 | /home/webservice/react-stage/releases/ | sam-front-stage | +| main | Production (승인 후) | 211.117.60.189 | /home/webservice/react/releases/ | sam-front | **환경변수 파일 (CI/CD 서버):** /var/lib/jenkins/env-files/react/ @@ -225,8 +212,13 @@ pipeline { stage('Prepare Env') { steps { script { - def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" - sh "cp ${envFile} .env.local" + if (env.BRANCH_NAME == 'main') { + // main: Stage 빌드 먼저 (승인 후 Production 재빌드) + sh "cp /var/lib/jenkins/env-files/react/.env.stage .env.local" + } else { + def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}" + sh "cp ${envFile} .env.local" + } } } } @@ -256,9 +248,9 @@ pipeline { } } - // ── stage → 운영서버 Stage 배포 ── + // ── main → 운영서버 Stage 배포 ── stage('Deploy Stage') { - when { branch 'stage' } + when { branch 'main' } steps { sshagent(credentials: ['deploy-ssh-key']) { sh """ @@ -277,6 +269,26 @@ pipeline { } } + // ── 운영 배포 승인 ── + stage('Production Approval') { + when { branch 'main' } + steps { + timeout(time: 24, unit: 'HOURS') { + input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage: https://stage.sam.it.kr', + ok: '운영 배포 진행' + } + } + } + + // ── main → Production 재빌드 (운영 환경변수) ── + stage('Rebuild for Production') { + when { branch 'main' } + steps { + sh "cp /var/lib/jenkins/env-files/react/.env.main .env.local" + sh 'npm run build' + } + } + // ── main → 운영서버 Production 배포 ── stage('Deploy Production') { when { branch 'main' } @@ -306,6 +318,10 @@ pipeline { } ``` +> **참고:** Next.js는 `NEXT_PUBLIC_*` 환경변수가 빌드 시 바인딩되므로, +> Stage(.env.stage)와 Production(.env.main)에서 별도 빌드가 필요하다. +> main 빌드 시 Stage용으로 먼저 빌드 → 승인 후 Production용으로 재빌드. + ### PM2 수동 재시작 ```bash @@ -329,16 +345,16 @@ pm2 save ``` CI/CD Gitea push -> Webhook -> Jenkins --> SSH: git clone -> composer install -> artisan cache -> migrate -> 심링크 전환 -> PHP-FPM reload +-> checkout -> rsync → Stage 배포 → 승인 → rsync → Production 배포 ``` **브랜치별 배포 대상:** -| 브랜치 | 대상 서버 | 대상 경로 | 트리거 | -|--------|----------|----------|--------| -| stage | 운영서버 | /home/webservice/api-stage/releases/ | 자동 (hook) | -| main | 운영서버 | /home/webservice/api/releases/ | 수동 push | -| develop | 개발서버 | - (기존 post-update hook) | 기존 hook | +| 브랜치 | 배포 단계 | 대상 서버 | 대상 경로 | +|--------|----------|----------|----------| +| main | Stage (자동) | 운영서버 | /home/webservice/api-stage/releases/ | +| main | Production (승인 후) | 운영서버 | /home/webservice/api/releases/ | +| develop | 개발서버 | 개발서버 | 기존 post-update hook | ### Jenkinsfile (api/Jenkinsfile) @@ -348,8 +364,7 @@ pipeline { environment { DEPLOY_USER = 'hskwon' - APP_NAME = 'api' - RELEASE_ID = new Date().format('yyyyMMdd_HHmmss') + RELEASE_ID = new Date().format('yyyyMMdd_HHmmss') } stages { @@ -357,18 +372,63 @@ pipeline { steps { checkout scm } } - // ── main → 운영서버 (배포관리자 수동 push 후 트리거) ── + // ── main → 운영서버 Stage 배포 ── + stage('Deploy Stage') { + when { branch 'main' } + steps { + sshagent(credentials: ['deploy-ssh-key']) { + sh """ + ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/api-stage/releases/${RELEASE_ID}' + rsync -az --delete \ + --exclude='.git' --exclude='.env' \ + --exclude='storage/app' --exclude='storage/logs' \ + --exclude='storage/framework/sessions' --exclude='storage/framework/cache' \ + . ${DEPLOY_USER}@211.117.60.189:/home/webservice/api-stage/releases/${RELEASE_ID}/ + ssh ${DEPLOY_USER}@211.117.60.189 ' + cd /home/webservice/api-stage/releases/${RELEASE_ID} && + ln -sfn /home/webservice/api-stage/shared/.env .env && + ln -sfn /home/webservice/api-stage/shared/storage/app storage/app && + composer install --no-dev --optimize-autoloader --no-interaction && + php artisan config:cache && + php artisan route:cache && + php artisan view:cache && + php artisan migrate --force && + ln -sfn /home/webservice/api-stage/releases/${RELEASE_ID} /home/webservice/api-stage/current && + sudo systemctl reload php8.4-fpm && + cd /home/webservice/api-stage/releases && ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true + ' + """ + } + } + } + + // ── 운영 배포 승인 ── + stage('Production Approval') { + when { branch 'main' } + steps { + timeout(time: 24, unit: 'HOURS') { + input message: 'Stage 확인 후 운영 배포를 진행하시겠습니까?\nStage API: https://stage-api.sam.it.kr', + ok: '운영 배포 진행' + } + } + } + + // ── main → 운영서버 Production 배포 ── stage('Deploy Production') { when { branch 'main' } steps { sshagent(credentials: ['deploy-ssh-key']) { sh """ + ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/api/releases/${RELEASE_ID}' + rsync -az --delete \ + --exclude='.git' --exclude='.env' \ + --exclude='storage/app' --exclude='storage/logs' \ + --exclude='storage/framework/sessions' --exclude='storage/framework/cache' \ + . ${DEPLOY_USER}@211.117.60.189:/home/webservice/api/releases/${RELEASE_ID}/ ssh ${DEPLOY_USER}@211.117.60.189 ' - cd /home/webservice/api/releases && - git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} && - ln -sfn /home/webservice/api/shared/storage /home/webservice/api/releases/${RELEASE_ID}/storage && - ln -sfn /home/webservice/api/shared/.env /home/webservice/api/releases/${RELEASE_ID}/.env && cd /home/webservice/api/releases/${RELEASE_ID} && + ln -sfn /home/webservice/api/shared/.env .env && + ln -sfn /home/webservice/api/shared/storage/app storage/app && composer install --no-dev --optimize-autoloader --no-interaction && php artisan config:cache && php artisan route:cache && @@ -377,35 +437,7 @@ pipeline { ln -sfn /home/webservice/api/releases/${RELEASE_ID} /home/webservice/api/current && sudo systemctl reload php8.4-fpm && sudo supervisorctl restart sam-queue-worker:* && - cd /home/webservice/api/releases && - ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true - ' - """ - } - } - } - - // ── stage → 운영서버 Stage ── - stage('Deploy Stage') { - when { branch 'stage' } - steps { - sshagent(credentials: ['deploy-ssh-key']) { - sh """ - ssh ${DEPLOY_USER}@211.117.60.189 ' - cd /home/webservice/api-stage/releases && - git clone --depth 1 --branch stage https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} && - ln -sfn /home/webservice/api-stage/shared/storage /home/webservice/api-stage/releases/${RELEASE_ID}/storage && - ln -sfn /home/webservice/api-stage/shared/.env /home/webservice/api-stage/releases/${RELEASE_ID}/.env && - cd /home/webservice/api-stage/releases/${RELEASE_ID} && - composer install --no-dev --optimize-autoloader --no-interaction && - php artisan config:cache && - php artisan route:cache && - php artisan view:cache && - php artisan migrate --force && - ln -sfn /home/webservice/api-stage/releases/${RELEASE_ID} /home/webservice/api-stage/current && - sudo systemctl reload php8.4-fpm && - cd /home/webservice/api-stage/releases && - ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true + cd /home/webservice/api/releases && ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true ' """ } @@ -420,17 +452,14 @@ pipeline { failure { echo "❌ api 배포 실패 (${env.BRANCH_NAME})" script { - if (env.BRANCH_NAME in ['main', 'stage']) { - def baseDir = env.BRANCH_NAME == 'main' - ? '/home/webservice/api' - : '/home/webservice/api-stage' + if (env.BRANCH_NAME == 'main') { sshagent(credentials: ['deploy-ssh-key']) { sh """ ssh ${DEPLOY_USER}@211.117.60.189 ' - PREV=\$(ls -1dt ${baseDir}/releases/*/ | sed -n "2p" | xargs basename) && - [ -n "\$PREV" ] && ln -sfn ${baseDir}/releases/\$PREV ${baseDir}/current && + PREV=\$(ls -1dt /home/webservice/api/releases/*/ | sed -n "2p" | xargs basename 2>/dev/null) && + [ -n "\$PREV" ] && ln -sfn /home/webservice/api/releases/\$PREV /home/webservice/api/current && sudo systemctl reload php8.4-fpm - ' + ' || true """ } } @@ -440,8 +469,15 @@ pipeline { } ``` +> **참고:** Laravel은 런타임 .env를 사용하므로 Stage/Production 별도 빌드가 필요 없다. +> 각 환경의 shared/.env가 심링크로 연결된다. + ### 수동 배포 절차 (API Production) +> **참고:** CI/CD Gitea는 `REQUIRE_SIGNIN_VIEW = true` 설정이므로, +> 수동 git clone 시 `https://사용자:비밀번호@git.sam.it.kr/...` 형식 또는 +> CI/CD 서버에서 rsync로 전송하는 방식을 사용한다. + ```bash ssh sam-prod @@ -511,6 +547,82 @@ ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true API와 동일한 releases/shared 구조. 차이점: npm build 추가, Queue Worker 재시작 불필요. +### Jenkinsfile (mng/Jenkinsfile) + +```groovy +pipeline { + agent any + + environment { + DEPLOY_USER = 'hskwon' + RELEASE_ID = new Date().format('yyyyMMdd_HHmmss') + } + + stages { + stage('Checkout') { + steps { checkout scm } + } + + // ── main → 운영서버 Production ── + stage('Deploy Production') { + when { branch 'main' } + steps { + sshagent(credentials: ['deploy-ssh-key']) { + sh """ + ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/mng/releases/${RELEASE_ID}' + rsync -az --delete \ + --exclude='.git' --exclude='.env' \ + --exclude='storage/app' --exclude='storage/logs' \ + --exclude='storage/framework/sessions' --exclude='storage/framework/cache' \ + --exclude='node_modules' \ + . ${DEPLOY_USER}@211.117.60.189:/home/webservice/mng/releases/${RELEASE_ID}/ + ssh ${DEPLOY_USER}@211.117.60.189 ' + cd /home/webservice/mng/releases/${RELEASE_ID} && + ln -sfn /home/webservice/mng/shared/.env .env && + ln -sfn /home/webservice/mng/shared/storage/app storage/app && + composer install --no-dev --optimize-autoloader --no-interaction && + npm install --production=false && + npm run build && + php artisan config:cache && + php artisan route:cache && + php artisan view:cache && + php artisan migrate --force && + ln -sfn /home/webservice/mng/releases/${RELEASE_ID} /home/webservice/mng/current && + sudo systemctl reload php8.4-fpm && + cd /home/webservice/mng/releases && ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true + ' + """ + } + } + } + + // develop → Jenkins 관여 안함 (기존 post-update hook 유지) + } + + post { + success { echo "✅ mng 배포 완료 (${env.BRANCH_NAME})" } + failure { + echo "❌ mng 배포 실패 (${env.BRANCH_NAME})" + script { + if (env.BRANCH_NAME == 'main') { + sshagent(credentials: ['deploy-ssh-key']) { + sh """ + ssh ${DEPLOY_USER}@211.117.60.189 ' + PREV=\$(ls -1dt /home/webservice/mng/releases/*/ | sed -n "2p" | xargs basename) && + [ -n "\$PREV" ] && ln -sfn /home/webservice/mng/releases/\$PREV /home/webservice/mng/current && + sudo systemctl reload php8.4-fpm + ' + """ + } + } + } + } + } +} +``` + +### 수동 배포 + ```bash ssh sam-prod