From 1eac5db02d145da8e8fd87fd4efda0f2f27305ba Mon Sep 17 00:00:00 2001 From: sdw086 Date: Mon, 20 Jul 2026 17:49:39 +0900 Subject: [PATCH] =?UTF-8?q?admin/front=20=EB=B6=84=EB=A6=AC2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src}/main/resources/application.yaml | 13 +- .../src/main/resources/application-local.yaml | 34 +- .../src/main/resources/application-pjt.yaml | 36 +- Jenkinsfile.pjt | 308 ++++++++---------- 4 files changed, 154 insertions(+), 237 deletions(-) rename {src => 02-api-admin/src}/main/resources/application.yaml (81%) diff --git a/src/main/resources/application.yaml b/02-api-admin/src/main/resources/application.yaml similarity index 81% rename from src/main/resources/application.yaml rename to 02-api-admin/src/main/resources/application.yaml index 189cd9c..a9f7380 100644 --- a/src/main/resources/application.yaml +++ b/02-api-admin/src/main/resources/application.yaml @@ -1,17 +1,10 @@ server: - port: 8106 + port: 8111 forward-headers-strategy: framework spring: application: - name: api - -mybatis: - mapper-locations: classpath:mapper/**/*.xml - type-aliases-package: com.alist.api - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + name: api-admin springdoc: swagger-ui: @@ -51,4 +44,4 @@ management: show-details: always health: db: - enabled: false \ No newline at end of file + enabled: false diff --git a/03-api-front/src/main/resources/application-local.yaml b/03-api-front/src/main/resources/application-local.yaml index 9778f86..e853097 100644 --- a/03-api-front/src/main/resources/application-local.yaml +++ b/03-api-front/src/main/resources/application-local.yaml @@ -1,6 +1,4 @@ spring: - application: - name: api datasource: url: jdbc:log4jdbc:mariadb://121.160.234.222:3000/ALISTLMS?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul&connectionCollation=utf8mb4_uca1400_ai_ci username: alist_dev @@ -24,6 +22,7 @@ spring: migration: datasource: alist: + enabled: true jdbc-url: jdbc:log4jdbc:sqlserver://bigfuntnp.co.kr:51433;databaseName=alist_dev;encrypt=false;trustServerCertificate=true username: bigfuntnp password: password12!@ @@ -32,6 +31,7 @@ migration: minimum-idle: 1 connection-timeout: 10000 eltown: + enabled: true jdbc-url: jdbc:log4jdbc:sqlserver://bigfuntnp.co.kr:51433;databaseName=eltown;encrypt=false;trustServerCertificate=true username: bigfuntnp password: password12!@ @@ -51,7 +51,6 @@ jwt: cookie: secure: false # (HTTP) -> false domain: # (HTTP) -> 비워두세요 - name: ALIST_SSO same-site: Lax # (HTTP) -> Lax swagger: @@ -59,32 +58,11 @@ swagger: id: alist password: "1qaz2wsx!@" -tus-file: - upload: - tus-endpoint: https://file-alist.pjt.kr/tus/file/ - public-base-url: https://file-alist.pjt.kr - tmp-root: /srv/project/alist/uploads/tmp - final-root: /srv/project/alist/uploads - interrupt-seconds: 30 - auth-cache: - ttl-seconds: 20 - -# types: -# notice: -# folder: notice 저장폴더 -# max-size: 20MB 최대 용량 -# image-only: false -# resize: -# enabled: true -# width: 800 고정넓이 -# height: 600 고정높이 -# max-width: 1200 최대 넓이 - file: + storage: + root-path: /srv/project/alist/uploads + view-domain: http://localhost:8110 upload: - view: - file-domain: http://localhost:8110 - root-path: ${tus-file.upload.final-root} max-size: 10MB allowed-extensions: [jpg, jpeg, png, gif, pdf, hwp, hwpx, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip] types: @@ -116,4 +94,4 @@ sso: code: ttl-seconds: 300 cookie: - name: ALIST_SSO \ No newline at end of file + name: ALIST_SSO diff --git a/03-api-front/src/main/resources/application-pjt.yaml b/03-api-front/src/main/resources/application-pjt.yaml index 6ccf530..3a8c8ed 100644 --- a/03-api-front/src/main/resources/application-pjt.yaml +++ b/03-api-front/src/main/resources/application-pjt.yaml @@ -1,6 +1,4 @@ spring: - application: - name: api main: banner-mode: log datasource: @@ -26,6 +24,7 @@ spring: migration: datasource: alist: + enabled: true jdbc-url: jdbc:log4jdbc:sqlserver://${ALIST_DB_HOST}:${ALIST_DB_PORT};databaseName=${ALIST_DB_NAME};encrypt=false;trustServerCertificate=true username: ${ALIST_DB_USERNAME} password: ${ALIST_DB_PASSWORD} @@ -34,6 +33,7 @@ migration: minimum-idle: 1 connection-timeout: 10000 eltown: + enabled: true jdbc-url: jdbc:log4jdbc:sqlserver://${ELTOWN_DB_HOST}:${ELTOWN_DB_PORT};databaseName=${ELTOWN_DB_NAME};encrypt=false;trustServerCertificate=true username: ${ELTOWN_DB_USERNAME} password: ${ELTOWN_DB_PASSWORD} @@ -52,8 +52,7 @@ jwt: cookie: secure: true # 프로젝트 환경 (HTTPS) - domain: api-alist.pjt.kr - name: ALIST_SSO + domain: apifront-alist.pjt.kr same-site: None swagger: @@ -61,32 +60,11 @@ swagger: id: ${SWAGGER_ID} password: ${SWAGGER_PASSWORD} -tus-file: - upload: - tus-endpoint: https://file-alist.pjt.kr/tus/file/ - public-base-url: https://file-alist.pjt.kr - tmp-root: /srv/project/alist/uploads/tmp - final-root: /srv/project/alist/uploads - interrupt-seconds: 30 - auth-cache: - ttl-seconds: 20 - -# types: -# notice: -# folder: notice 저장폴더 -# max-size: 20MB 최대 용량 -# image-only: false -# resize: -# enabled: true -# width: 800 고정넓이 -# height: 600 고정높이 -# max-width: 1200 최대 넓이 - file: + storage: + root-path: /srv/project/alist/uploads + view-domain: https://file-alist.pjt.kr upload: - view: - file-domain: https://file-alist.pjt.kr - root-path: ${tus-file.upload.final-root} max-size: 10MB allowed-extensions: [jpg, jpeg, png, gif, pdf, hwp, hwpx, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip] types: @@ -118,4 +96,4 @@ sso: code: ttl-seconds: 300 cookie: - name: ALIST_SSO \ No newline at end of file + name: ALIST_SSO diff --git a/Jenkinsfile.pjt b/Jenkinsfile.pjt index 23eca4e..62e1cec 100644 --- a/Jenkinsfile.pjt +++ b/Jenkinsfile.pjt @@ -6,73 +6,110 @@ pipeline { disableConcurrentBuilds() } + parameters { + choice( + name: 'TARGET', + choices: ['admin', 'front', 'all'], + description: '빌드와 배포할 API 서버를 선택합니다.' + ) + } + environment { - // ===== Registry ===== - REGISTRY_HOST = "registry.pjt.kr" - IMAGE_REPO = "alist/api" - DOCKER_CRED = "registry-pjt" + REGISTRY_HOST = 'registry.pjt.kr' + DOCKER_CRED = 'registry-pjt' - // ===== api ssh ===== - SSH_HOST = "121.160.234.222" - SSH_PORT = "2001" - SSH_USER = "bigfuntnp" - SSH_COMPOSE_DIR = "/srv/project/alist/compose" - SSH_COMPOSE_FILE = "api-compose.pjt.yml" + SSH_HOST = '121.160.234.222' + SSH_PORT = '2001' + SSH_USER = 'bigfuntnp' + SSH_COMPOSE_DIR = '/srv/project/alist/compose' - // ===== jenkins workspace path ===== - WORK_COMPOSE_DIR = "deploy/pjt/compose" - WORK_COMPOSE_FILE = "api-compose.pjt.yml" + ADMIN_IMAGE_REPO = 'alist/api-admin' + ADMIN_MODULE_DIR = '02-api-admin' + ADMIN_COMPOSE_FILE = 'api-admin-compose.pjt.yml' + ADMIN_ENV_FILE = '/srv/project/alist/env/api-admin/.env' + + FRONT_IMAGE_REPO = 'alist/api-front' + FRONT_MODULE_DIR = '03-api-front' + FRONT_COMPOSE_FILE = 'api-front-compose.pjt.yml' + FRONT_ENV_FILE = '/srv/project/alist/env/api-front/.env' + + WORK_COMPOSE_DIR = 'deploy/pjt/compose' } stages { stage('1) Git check out') { steps { - echo "[1/1] Git check out" - checkout scm } } - stage('2) Build (Gradle)') { - steps { - sh ''' - set -e - - echo "[1/1] Gradle build" - - chmod +x ./gradlew - ./gradlew clean build -x test - ''' - } - } - - stage('3) ImageTag create') { + stage('2) ImageTag create') { steps { script { - - echo "[1/1] ImageTag create" - - env.SHORT_SHA = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim() - echo "SHORT_SHA = ${env.SHORT_SHA}" + env.SHORT_SHA = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() env.IMAGE_TAG = "${env.BUILD_NUMBER}-${env.SHORT_SHA}" + + echo "TARGET = ${params.TARGET}" echo "IMAGE_TAG = ${env.IMAGE_TAG}" } } } - stage('4) Docker Build') { + stage('3) Build admin') { + when { + expression { params.TARGET == 'admin' || params.TARGET == 'all' } + } steps { sh ''' set -e - - echo "[1/1] docker build" - - docker build --no-cache -t ${REGISTRY_HOST}/${IMAGE_REPO}:${IMAGE_TAG} . - ''' + chmod +x ./gradlew + ./gradlew :02-api-admin:bootJar + ''' } } - stage('5) Docker registry Login & Push') { + stage('4) Build front') { + when { + expression { params.TARGET == 'front' || params.TARGET == 'all' } + } + steps { + sh ''' + set -e + chmod +x ./gradlew + ./gradlew :03-api-front:bootJar + ''' + } + } + + stage('5) Docker build admin') { + when { + expression { params.TARGET == 'admin' || params.TARGET == 'all' } + } + steps { + sh ''' + set -e + docker build --no-cache \\ + -t ${REGISTRY_HOST}/${ADMIN_IMAGE_REPO}:${IMAGE_TAG} \\ + ${ADMIN_MODULE_DIR} + ''' + } + } + + stage('6) Docker build front') { + when { + expression { params.TARGET == 'front' || params.TARGET == 'all' } + } + steps { + sh ''' + set -e + docker build --no-cache \\ + -t ${REGISTRY_HOST}/${FRONT_IMAGE_REPO}:${IMAGE_TAG} \\ + ${FRONT_MODULE_DIR} + ''' + } + } + + stage('7) Docker push') { steps { withCredentials([usernamePassword( credentialsId: DOCKER_CRED, @@ -81,12 +118,15 @@ pipeline { )]) { sh ''' set -e - - echo "[1/2] docker registry login" echo "$DOCKER_PASS" | docker login ${REGISTRY_HOST} -u "$DOCKER_USER" --password-stdin - echo "[2/2] docker image push" - docker push ${REGISTRY_HOST}/${IMAGE_REPO}:${IMAGE_TAG} + if [ "${TARGET}" = "admin" ] || [ "${TARGET}" = "all" ]; then + docker push ${REGISTRY_HOST}/${ADMIN_IMAGE_REPO}:${IMAGE_TAG} + fi + + if [ "${TARGET}" = "front" ] || [ "${TARGET}" = "all" ]; then + docker push ${REGISTRY_HOST}/${FRONT_IMAGE_REPO}:${IMAGE_TAG} + fi docker logout ${REGISTRY_HOST} ''' @@ -94,42 +134,10 @@ pipeline { } } - stage('6) Jenkins cleanup') { - steps { - sh ''' - set -e - - echo "[1/3] Remove pushed image from Jenkins" - docker rmi ${REGISTRY_HOST}/${IMAGE_REPO}:${IMAGE_TAG} 2>/dev/null || true - - echo "[2/3] Remove dangling images" - docker image prune -f - - echo "[3/3] Remove old build cache (safe-ish)" - docker builder prune -f || true - ''' + stage('8) Deploy admin') { + when { + expression { params.TARGET == 'admin' || params.TARGET == 'all' } } - } - - stage('7) Copy compose') { - steps { - sshagent(['ssh-pjt']) { - sh ''' - set -e - - echo "[1/2] ssh login" - - SRC="${WORK_COMPOSE_DIR}/${WORK_COMPOSE_FILE}" - DST="${SSH_USER}@${SSH_HOST}:${SSH_COMPOSE_DIR}/${SSH_COMPOSE_FILE}" - - echo "[2/2] Copy compose $SRC -> $DST" - scp -P ${SSH_PORT} -o StrictHostKeyChecking=no "$SRC" "$DST" - ''' - } - } - } - - stage('8) Remote docker registry login') { steps { sshagent(['ssh-pjt']) { withCredentials([usernamePassword( @@ -140,15 +148,21 @@ pipeline { sh ''' set -e - echo "[1/1] docker registry login" + scp -P ${SSH_PORT} -o StrictHostKeyChecking=no \\ + ${WORK_COMPOSE_DIR}/${ADMIN_COMPOSE_FILE} \\ + ${SSH_USER}@${SSH_HOST}:${SSH_COMPOSE_DIR}/${ADMIN_COMPOSE_FILE} - ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${SSH_USER}@${SSH_HOST} ' - set -e - REGISTRY_HOST='"${REGISTRY_HOST}"' - DOCKER_USER='"${DOCKER_USER}"' - DOCKER_PASS='"${DOCKER_PASS}"' + ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\ + ${SSH_USER}@${SSH_HOST} ' + set -e + cd '"${SSH_COMPOSE_DIR}"' + export IMAGE_TAG='"${IMAGE_TAG}"' - echo "$DOCKER_PASS" | docker login "$REGISTRY_HOST" -u "$DOCKER_USER" --password-stdin + echo '"$DOCKER_PASS"' | docker login '"${REGISTRY_HOST}"' -u '"$DOCKER_USER"' --password-stdin + docker compose -p alist-api-admin --env-file '"${ADMIN_ENV_FILE}"' -f '"${ADMIN_COMPOSE_FILE}"' pull + docker compose -p alist-api-admin --env-file '"${ADMIN_ENV_FILE}"' -f '"${ADMIN_COMPOSE_FILE}"' up -d --remove-orphans + docker compose -p alist-api-admin --env-file '"${ADMIN_ENV_FILE}"' -f '"${ADMIN_COMPOSE_FILE}"' ps + docker logout '"${REGISTRY_HOST}"' ' ''' } @@ -156,105 +170,59 @@ pipeline { } } - stage('9) Remote docker pull') { - steps { - sshagent(['ssh-pjt']) { - sh ''' - set -e - ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - ${SSH_USER}@${SSH_HOST} \ - 'set -e - cd '"${SSH_COMPOSE_DIR}"' - export IMAGE_TAG='"${IMAGE_TAG}"' - - echo "[1/1] docker pull" - docker compose -p alist-api --env-file /srv/project/alist/env/api/.env -f '"${SSH_COMPOSE_FILE}"' pull - ' - ''' - } + stage('9) Deploy front') { + when { + expression { params.TARGET == 'front' || params.TARGET == 'all' } } - } - - stage('10) Remote docker down & up') { steps { sshagent(['ssh-pjt']) { - sh ''' - set -e - ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - ${SSH_USER}@${SSH_HOST} \ - 'set -e - cd '"${SSH_COMPOSE_DIR}"' - export IMAGE_TAG='"${IMAGE_TAG}"' - - echo "[1/3] docker down (remove orphans)" - docker compose -p alist-api --env-file /srv/project/alist/env/api/.env -f "'"${SSH_COMPOSE_FILE}"'" down --remove-orphans - - echo "[2/3] docker up" - docker compose -p alist-api --env-file /srv/project/alist/env/api/.env -f '"${SSH_COMPOSE_FILE}"' up -d - - echo "[3/3] docker ps" - docker compose -p alist-api --env-file /srv/project/alist/env/api/.env -f '"${SSH_COMPOSE_FILE}"' ps - ' - ''' - } - } - } - - stage('11) Remote cleanup') { - steps { - sshagent(['ssh-pjt']) { - sh ''' - set -e - ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${SSH_USER}@${SSH_HOST} ' + withCredentials([usernamePassword( + credentialsId: DOCKER_CRED, + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS' + )]) { + sh ''' set -e - Repo='"${REGISTRY_HOST}/${IMAGE_REPO}"' + scp -P ${SSH_PORT} -o StrictHostKeyChecking=no \\ + ${WORK_COMPOSE_DIR}/${FRONT_COMPOSE_FILE} \\ + ${SSH_USER}@${SSH_HOST}:${SSH_COMPOSE_DIR}/${FRONT_COMPOSE_FILE} - echo "[1/3] Remove old image" - docker images "${Repo}" --format '{{.Repository}}:{{.Tag}}' | grep -v -F -f <(docker ps --format '{{.Image}}') | xargs -r docker rmi + ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\ + ${SSH_USER}@${SSH_HOST} ' + set -e + cd '"${SSH_COMPOSE_DIR}"' + export IMAGE_TAG='"${IMAGE_TAG}"' - echo "[2/3] Remove dangling images" - docker image prune -f - - echo "[3/3] Remove old build cache (safe-ish)" - docker builder prune -f || true - ' - ''' + echo '"$DOCKER_PASS"' | docker login '"${REGISTRY_HOST}"' -u '"$DOCKER_USER"' --password-stdin + docker compose -p alist-api-front --env-file '"${FRONT_ENV_FILE}"' -f '"${FRONT_COMPOSE_FILE}"' pull + docker compose -p alist-api-front --env-file '"${FRONT_ENV_FILE}"' -f '"${FRONT_COMPOSE_FILE}"' up -d --remove-orphans + docker compose -p alist-api-front --env-file '"${FRONT_ENV_FILE}"' -f '"${FRONT_COMPOSE_FILE}"' ps + docker logout '"${REGISTRY_HOST}"' + ' + ''' + } } } } - stage('12) registry docker image cleanup') { + stage('10) Jenkins cleanup') { steps { - withCredentials([usernamePassword( - credentialsId: DOCKER_CRED, - usernameVariable: 'DOCKER_USER', - passwordVariable: 'DOCKER_PASS' - )]) { - sh ''' - set -e + sh ''' + set -e - echo "[1/2] Remove registry old image tag list" - TAGS_TO_DELETE=$(curl -s -u "$DOCKER_USER:$DOCKER_PASS" https://${REGISTRY_HOST}/v2/${IMAGE_REPO}/tags/list | jq -r '.tags[]' | grep -E '^[0-9]+' | sort -t- -k1,1n | head -n -5) + if [ "${TARGET}" = "admin" ] || [ "${TARGET}" = "all" ]; then + docker rmi ${REGISTRY_HOST}/${ADMIN_IMAGE_REPO}:${IMAGE_TAG} 2>/dev/null || true + fi - echo "[2/2] Remove registry old image sha list and delete registry" - for TAG in $TAGS_TO_DELETE; do - DIGEST=$(curl -s -u "$DOCKER_USER:$DOCKER_PASS" -H "Accept: application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json" -D - https://${REGISTRY_HOST}/v2/${IMAGE_REPO}/manifests/$TAG -o /dev/null | grep -i Docker-Content-Digest | awk '{print $2}' | tr -d $'\r') + if [ "${TARGET}" = "front" ] || [ "${TARGET}" = "all" ]; then + docker rmi ${REGISTRY_HOST}/${FRONT_IMAGE_REPO}:${IMAGE_TAG} 2>/dev/null || true + fi - if [ -z "$DIGEST" ]; then - echo "[SKIP] $TAG digest not found" - continue - fi - - echo "[DRY] $TAG -> $DIGEST" - - curl -s -u "$DOCKER_USER:$DOCKER_PASS" -X DELETE "https://${REGISTRY_HOST}/v2/${IMAGE_REPO}/manifests/${DIGEST}" -o /dev/null -w "HTTP %{http_code}\n" - done - - ''' - } + docker image prune -f + docker builder prune -f || true + ''' } } - } }