diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 8332b83..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,46 +0,0 @@ -pipeline { - agent { label 'mac-mini' } - - environment { - NAS = "happyhome@jisangs.synology.me" - NAS_PORT = "21022" - NAS_TMP = "/volume1/docker/service/jisangs/chocomae-stage/tmp" - ARCHIVE = "chocomae-build-${BUILD_NUMBER}.tar.gz" - } - - stages { - stage('Package') { - steps { - sh 'chmod +x deploy/stage-jenkins/*.sh && ./deploy/stage-jenkins/package.sh' - } - } - stage('Upload') { - steps { - sh ''' - ssh -p ${NAS_PORT} ${NAS} "mkdir -p ${NAS_TMP}" - scp -O -P ${NAS_PORT} ${ARCHIVE} deploy/stage-jenkins/remote_deploy.sh ${NAS}:${NAS_TMP}/ - ''' - } - } - stage('Deploy') { - steps { - sh ''' - ssh -p ${NAS_PORT} ${NAS} \ - "bash ${NAS_TMP}/remote_deploy.sh ${ARCHIVE}" - ''' - } - } - } - - post { - always { - sh 'rm -fv ${ARCHIVE} || true' - } - success { - echo "✓ chocomae-stage 배포 성공 (build #${BUILD_NUMBER})" - } - failure { - echo "✗ 배포 실패 — Deploy stage였다면 NAS에서 자동 롤백됨. Console Output 확인" - } - } -} \ No newline at end of file diff --git a/deploy/prod-jenkins/Jenkinsfile b/deploy/prod-jenkins/Jenkinsfile new file mode 100644 index 0000000..52528ec --- /dev/null +++ b/deploy/prod-jenkins/Jenkinsfile @@ -0,0 +1,55 @@ +pipeline { + agent { label 'mac-mini' } + + environment { + SSH_TARGET = "ubuntu@chocomae.jinaju.com" + SSH_PORT = "22" + REMOTE_TMP = "/tmp/chocomae-deploy" + DEPLOY_DIR = "deploy/prod-jenkins" + ARCHIVE = "chocomae-build-${BUILD_NUMBER}.tar.gz" + } + + stages { + stage('Package') { + steps { + sh 'chmod +x ${DEPLOY_DIR}/*.sh && ./${DEPLOY_DIR}/package.sh' + } + } + stage('Confirm Production') { + steps { + input( + message: "Production 서버(${env.SSH_TARGET})로 배포하시겠습니까?", + ok: 'Deploy' + ) + } + } + stage('Upload') { + steps { + sh ''' + ssh -p ${SSH_PORT} ${SSH_TARGET} "mkdir -p ${REMOTE_TMP}" + scp -O -P ${SSH_PORT} ${ARCHIVE} ${DEPLOY_DIR}/remote_deploy.sh ${SSH_TARGET}:${REMOTE_TMP}/ + ''' + } + } + stage('Deploy') { + steps { + sh ''' + ssh -p ${SSH_PORT} ${SSH_TARGET} \ + "bash ${REMOTE_TMP}/remote_deploy.sh ${ARCHIVE}" + ''' + } + } + } + + post { + always { + sh 'rm -fv ${ARCHIVE} || true' + } + success { + echo "✓ chocomae-production 배포 성공 (build #${BUILD_NUMBER})" + } + failure { + echo "✗ 배포 실패 — Deploy stage였다면 원격에서 자동 롤백됨. Console Output 확인" + } + } +} diff --git a/deploy/prod-jenkins/package.sh b/deploy/prod-jenkins/package.sh new file mode 100644 index 0000000..d493a01 --- /dev/null +++ b/deploy/prod-jenkins/package.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +# BUILD_NUMBER는 Jenkins가 주입 — 로컬 수동 실행 시엔 manual-시각 사용 +ARCHIVE_NAME="chocomae-build-${BUILD_NUMBER:-manual-$(date +%Y%m%d%H%M%S)}.tar.gz" + +EXCLUDES=( + ".DS_Store" ".git" ".idea" ".claude" ".gitignore" + "__pycache__" "*.pyc" "node_modules" "venv" + "*.zip" "*.tar" "*.tar.gz" "*.pdf" "*.psd" "*.log" + "src/web/server/setup/service_db_setting.php" + + # phaser-input 플러그인 개발 아티팩트 (런타임은 build/phaser-input.min.js만 필요) + "src/util/phaser-input-master/example" + "src/util/phaser-input-master/ts" + "src/util/phaser-input-master/config" + "src/util/phaser-input-master/Gruntfile.js" + "src/util/phaser-input-master/tslint.json" + "src/util/phaser-input-master/package.json" + "src/util/phaser-input-master/package-lock.json" + + # DB 초기 세팅 전용 — 웹 루트에 있으면 노출 위험 + "src/web/sql" + + # deprecated 배치 스크립트 + "src/php-cli/batch/old" + + # 백업/문서 + "src/web/css/color_button_backup.css" + "README.md" + "SECURITY.md" + "LICENSE" +) + +TAR_EXCLUDE_ARGS=() +for pattern in "${EXCLUDES[@]}"; do + TAR_EXCLUDE_ARGS+=(--exclude="${pattern}") +done + +echo "=== 압축: ${ARCHIVE_NAME} ===" +tar czf "${ARCHIVE_NAME}" "${TAR_EXCLUDE_ARGS[@]}" \ + index.html \ + src \ + resources + +ls -lh "${ARCHIVE_NAME}" diff --git a/deploy/prod-jenkins/remote_deploy.sh b/deploy/prod-jenkins/remote_deploy.sh new file mode 100644 index 0000000..094c2a1 --- /dev/null +++ b/deploy/prod-jenkins/remote_deploy.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# AWS Production 서버에서 실행되는 배포 본체 +# 사용법: remote_deploy.sh +set -euo pipefail + +ARCHIVE_NAME="$1" + +PROD_TMP_DIR="/tmp/chocomae-deploy" +PROD_BACKUP_DIR="/var/backups/chocomae" +PROD_HTML_ROOT="/var/www/html" +PROD_DEPLOY_DIR="${PROD_HTML_ROOT}/mouse-typing" + +TIMESTAMP=$(date +%Y%m%d%H%M%S) +NEW_DIR="${PROD_DEPLOY_DIR}_new" +OLD_DIR="${PROD_DEPLOY_DIR}_old" + +# ---------- 롤백 함수 ---------- +SWAPPED=0 +rollback() { + echo "!!! 배포 실패 — 롤백 시작 !!!" + if [ "${SWAPPED}" -eq 1 ] && sudo test -d "${OLD_DIR}"; then + # 교체 이후 실패 → 구버전 복원 + sudo rm -rf "${PROD_DEPLOY_DIR}" + sudo mv "${OLD_DIR}" "${PROD_DEPLOY_DIR}" + echo "구버전 디렉토리 복원 완료" + fi + sudo rm -rf "${NEW_DIR}" + echo "!!! 롤백 완료 — 빌드는 실패로 처리됨 !!!" + exit 1 +} +trap rollback ERR + +# ---------- 1. 새 버전을 옆에서 완성 (서비스 무영향 구간) ---------- +echo "=== [1/4] 새 버전 준비: ${NEW_DIR} ===" +sudo rm -rf "${NEW_DIR}" +sudo mkdir -p "${NEW_DIR}" +sudo tar xzf "${PROD_TMP_DIR}/${ARCHIVE_NAME}" -C "${NEW_DIR}" + +echo "=== [2/4] production 설정 적용 ===" +sudo mv -v "${NEW_DIR}/src/game/lib/global/global_variables_release.js" \ + "${NEW_DIR}/src/game/lib/global/global_variables.js" +sudo mv -v "${NEW_DIR}/src/web/server/setup/NA_service_db_setting.php" \ + "${NEW_DIR}/src/web/server/setup/service_db_setting.php" + +DB_CONFIG="${NEW_DIR}/src/web/server/setup/service_db_setting.php" +echo "--- DB 설정 확인 ---" +sudo grep -n "db_host\|db_port" "${DB_CONFIG}" + +# ---------- 2. 현재 버전 백업 ---------- +echo "=== [3/4] 현재 버전 백업 ===" +sudo mkdir -p "${PROD_BACKUP_DIR}" +if sudo test -d "${PROD_DEPLOY_DIR}"; then + sudo tar czf "${PROD_BACKUP_DIR}/chocomae-prod-backup-${TIMESTAMP}.tar.gz" \ + -C "$(dirname "${PROD_DEPLOY_DIR}")" "$(basename "${PROD_DEPLOY_DIR}")" +fi +# 7개 초과 백업은 오래된 순으로 정리 +sudo bash -c "ls -t ${PROD_BACKUP_DIR}/chocomae-prod-backup-*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm -fv" + +# ---------- 3. 순간 교체 ---------- +echo "=== [4/4] 디렉토리 스왑 ===" +#if sudo test -d "${PROD_DEPLOY_DIR}"; then +# sudo mv "${PROD_DEPLOY_DIR}" "${OLD_DIR}" +#fi +#sudo mv "${NEW_DIR}" "${PROD_DEPLOY_DIR}" +#SWAPPED=1 + +# index.html은 html 루트에 위치 +#sudo mv -v "${PROD_DEPLOY_DIR}/index.html" "${PROD_HTML_ROOT}/index.html" + +# ---------- 4. 정리 ---------- +echo "=== 정리 ===" +trap - ERR # 여기부터는 실패해도 롤백 불필요 +sudo rm -rf "${OLD_DIR}" +sudo rm -fv "${PROD_TMP_DIR}/${ARCHIVE_NAME}" +sudo rm -fv "${PROD_TMP_DIR}/remote_deploy.sh" + +echo "=== 배포 완료 (backup: ${TIMESTAMP}) ===" diff --git a/deploy/stage-jenkins/Jenkinsfile b/deploy/stage-jenkins/Jenkinsfile new file mode 100644 index 0000000..30ecef2 --- /dev/null +++ b/deploy/stage-jenkins/Jenkinsfile @@ -0,0 +1,47 @@ +pipeline { + agent { label 'mac-mini' } + + environment { + SSH_TARGET = "happyhome@jisangs.synology.me" + SSH_PORT = "21022" + REMOTE_TMP = "/volume1/docker/service/jisangs/chocomae-stage/tmp" + DEPLOY_DIR = "deploy/stage-jenkins" + ARCHIVE = "chocomae-build-${BUILD_NUMBER}.tar.gz" + } + + stages { + stage('Package') { + steps { + sh 'chmod +x ${DEPLOY_DIR}/*.sh && ./${DEPLOY_DIR}/package.sh' + } + } + stage('Upload') { + steps { + sh ''' + ssh -p ${SSH_PORT} ${SSH_TARGET} "mkdir -p ${REMOTE_TMP}" + scp -O -P ${SSH_PORT} ${ARCHIVE} ${DEPLOY_DIR}/remote_deploy.sh ${SSH_TARGET}:${REMOTE_TMP}/ + ''' + } + } + stage('Deploy') { + steps { + sh ''' + ssh -p ${SSH_PORT} ${SSH_TARGET} \ + "bash ${REMOTE_TMP}/remote_deploy.sh ${ARCHIVE}" + ''' + } + } + } + + post { + always { + sh 'rm -fv ${ARCHIVE} || true' + } + success { + echo "✓ chocomae-stage 배포 성공 (build #${BUILD_NUMBER})" + } + failure { + echo "✗ 배포 실패 — Deploy stage였다면 원격에서 자동 롤백됨. Console Output 확인" + } + } +} diff --git a/deploy/stage-jenkins/config.sh b/deploy/stage-jenkins/config.sh deleted file mode 100644 index 7141859..0000000 --- a/deploy/stage-jenkins/config.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# 배포 환경 설정 — 모든 스크립트가 공유 - -NAS_USER="happyhome" -NAS_HOST="jisangs.synology.me" -NAS_SSH_PORT="21022" - -NAS_BASE="/volume1/docker/service/jisangs/chocomae-stage" -NAS_TMP_DIR="${NAS_BASE}/tmp" -NAS_BACKUP_DIR="${NAS_BASE}/backup" -STAGE_HTML_DIR="${NAS_BASE}/html" -STAGE_DEPLOY_DIR="${STAGE_HTML_DIR}/mouse_typing" -STAGE_CONTAINER_NAME="chocomae-stage-http" # 실제 컨테이너명 확인 - -# BUILD_NUMBER는 Jenkins가 주입 — 로컬 수동 실행 시엔 manual-시각 사용 -ARCHIVE_NAME="chocomae-build-${BUILD_NUMBER:-manual-$(date +%Y%m%d%H%M%S)}.tar.gz" \ No newline at end of file diff --git a/deploy/stage-jenkins/package.sh b/deploy/stage-jenkins/package.sh index a3d0d77..b2a0869 100644 --- a/deploy/stage-jenkins/package.sh +++ b/deploy/stage-jenkins/package.sh @@ -1,13 +1,35 @@ #!/bin/bash set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -source "${SCRIPT_DIR}/config.sh" + +# BUILD_NUMBER는 Jenkins가 주입 — 로컬 수동 실행 시엔 manual-시각 사용 +ARCHIVE_NAME="chocomae-build-${BUILD_NUMBER:-manual-$(date +%Y%m%d%H%M%S)}.tar.gz" EXCLUDES=( ".DS_Store" ".git" ".idea" ".claude" ".gitignore" "__pycache__" "*.pyc" "node_modules" "venv" "*.zip" "*.tar" "*.tar.gz" "*.pdf" "*.psd" "*.log" "src/web/server/setup/service_db_setting.php" + + # phaser-input 플러그인 개발 아티팩트 (런타임은 build/phaser-input.min.js만 필요) + "src/util/phaser-input-master/example" + "src/util/phaser-input-master/ts" + "src/util/phaser-input-master/config" + "src/util/phaser-input-master/Gruntfile.js" + "src/util/phaser-input-master/tslint.json" + "src/util/phaser-input-master/package.json" + "src/util/phaser-input-master/package-lock.json" + + # DB 초기 세팅 전용 — 웹 루트에 있으면 노출 위험 + "src/web/sql" + + # deprecated 배치 스크립트 + "src/php-cli/batch/old" + + # 백업/문서 + "src/web/css/color_button_backup.css" + "README.md" + "SECURITY.md" + "LICENSE" ) TAR_EXCLUDE_ARGS=() diff --git a/deploy/stage-jenkins/remote_deploy.sh b/deploy/stage-jenkins/remote_deploy.sh index b4681d1..3bb3afd 100644 --- a/deploy/stage-jenkins/remote_deploy.sh +++ b/deploy/stage-jenkins/remote_deploy.sh @@ -43,9 +43,8 @@ mkdir -p "${NEW_DIR}" tar xzf "${NAS_TMP_DIR}/${ARCHIVE_NAME}" -C "${NEW_DIR}" echo "=== [2/5] stage 설정 적용 ===" -cp -v "${NEW_DIR}/src/game/lib/global/global_variables_debug.js" \ - "${NEW_DIR}/src/game/lib/global/global_variables.js" -cp -v "${NEW_DIR}/src/web/server/setup/NA_stage_db_setting.php" \ +rm "${NEW_DIR}/src/game/lib/global/global_variables_release.js" +mv -v "${NEW_DIR}/src/web/server/setup/NA_stage_db_setting.php" \ "${NEW_DIR}/src/web/server/setup/service_db_setting.php" DB_CONFIG="${NEW_DIR}/src/web/server/setup/service_db_setting.php" diff --git a/src/game/lib/global/global_variables_debug.js b/src/game/lib/global/global_variables_debug.js deleted file mode 100644 index bf9e03c..0000000 --- a/src/game/lib/global/global_variables_debug.js +++ /dev/null @@ -1,199 +0,0 @@ -var LANGUAGE_KOREAN = "korean"; -var LANGUAGE_ENGLISH = "english"; - -var MODE_RELEASE = "release"; -var MODE_DEBUG = "debug"; -var runMode = MODE_DEBUG; - -function isTddMode() { - // console.log("debug mode ? " + runMode); - if(typeof(tddMode) === "undefined") - return false; - - if(tddMode == true) - return true; - - return false; -} - -function isDebugMode() { - // console.log("debug mode ? " + runMode); - return runMode == MODE_DEBUG ? true : false; -} - -function isReleaseMode() { - // console.log("release mode ? " + runMode); - return runMode == MODE_RELEASE ? true : false; -} - - -var GAME_SCREEN_SIZE = { x: 1024, y: 768 } - - - -var sessionStorageManager = new SessionStorageManager(); - -{ - if(isTddMode()) { - console.log("Running in TDD mode"); - } else { - if(isDebugMode()) { - printSessionStorage(); - } - - if(sessionStorageManager.getMaestroID() === null && !isLogin()) { - goLogin(); - } - } -} - -function printSessionStorage() { - console.log("maestroName : " + sessionStorageManager.getMaestroName()); - console.log("maestroID : " + sessionStorageManager.getMaestroID()); - console.log("maestroAccountType : " + sessionStorageManager.getMaestroAccountType()); - console.log("playerName : " + sessionStorageManager.getPlayerName()); - console.log("playerID : " + sessionStorageManager.getPlayerID()); - console.log("playerAccountType : " + sessionStorageManager.getPlayerAccountType()); - console.log("playingAppID : " + sessionStorageManager.getPlayingAppID()); - console.log("playingAppName : " + sessionStorageManager.getPlayingAppName()); - console.log("playingAppKoreanName : " + sessionStorageManager.getPlayingAppKoreanName()); - console.log("record : " + sessionStorageManager.getRecord()); - console.log("appHighestRecord : " + sessionStorageManager.getAppHighestRecord()); - console.log("writingID : " + sessionStorageManager.getWritingID()); - console.log("appGroup : " + sessionStorageManager.getAppGroup()); - console.log("language : " + sessionStorageManager.getLanguage()); -} - -function isLogin() { - var filename = location.pathname.substring(location.pathname.lastIndexOf("/") + 1); - - if(filename === "login.html") - return true; - else if(filename === "license_timer.html") - return true; - - return false; -} - -function goLogin() { - location.href = "login.html"; -} - -function isTypingGameApp() { - if(sessionStorageManager.getPlayingAppName() == null) - return false; - - if(sessionStorageManager.getPlayingAppID === 200) - return true; - - if(sessionStorageManager.getPlayingAppID < 50 || sessionStorageManager.getPlayingAppID > 100) - return false; - - return true; -} - - -function isMouseGameApp() { - if(sessionStorageManager.getPlayingAppName() == null) - return false; - - if(sessionStorageManager.getPlayingAppID > 100) - return true; - - return false; -} - - -function isTypingPracticeApp() { - if(sessionStorageManager.getPlayingAppName().indexOf("practice_") > -1) - return true; - - return false; -} - -function isTypingTestApp() { - if(sessionStorageManager.getPlayingAppName().indexOf("test_") > -1) - return true; - - return false; -} - -function isTypingExamApp() { - if(sessionStorageManager.getPlayingAppName() == "typing_exam") - return true; - - return false; -} - -function isEnglishTypingApp() { - if(sessionStorageManager.getPlayingAppName().indexOf("english") > -1) - return true; - - return false; -} - -function isKoreanTypingApp() { - if(sessionStorageManager.getPlayingAppName().indexOf("korean") > -1) - return true; - - return false; -} - -function isTypingWordApp() { - var appName = sessionStorageManager.getPlayingAppName(); - - if(appName.indexOf("_word") > -1) - return true; - - // if((isTypingPracticeApp() || isTypingTestApp()) && appName.indexOf("_word") > -1) - // return true; - - return false; -} - -function isTypingSentenceApp() { - var appName = sessionStorageManager.getPlayingAppName(); - - if(appName.indexOf("_sentence") > -1) - return true; - - // if((isTypingPracticeApp() || isTypingTestApp()) && appName.indexOf("_sentence") > -1) - // return true; - - return false; -} - -function isExperiencePlayerAccount() { - if(sessionStorageManager.getMaestroAccountType() == 100) - return true; - - return false; -} - -function isExperienceMaestroAccount() { - if(sessionStorageManager.getMaestroAccountType() == 101) - return true; - - return false; -} - - -function getGameAppName() { - var appName = sessionStorageManager.getPlayingAppName(); - - if(isTypingPracticeApp()) - return "typing_practice"; - else if(isTypingTestApp()) - return "typing_test"; - else if(isTypingGameApp()) { - if(isKoreanTypingApp()) - return appName.substring("typing_korean_".length); - else if(isEnglishTypingApp()) - return appName.substring("typing_english_".length); - } - - return appName; -} - - -var textStyleBasic = { font: "bold 32px Arial", fill: "#fff", align: "center", boundsAlignH: "center", boundsAlignV: "middle" };