diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bb89c4d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.gitignore +.gradle +build +out +**/node_modules +**/.idea +**/*.iml +**/.DS_Store \ No newline at end of file diff --git a/.github/workflows/release-deploy.yml b/.github/workflows/release-deploy.yml new file mode 100644 index 0000000..761ffc8 --- /dev/null +++ b/.github/workflows/release-deploy.yml @@ -0,0 +1,87 @@ +name: Release Deploy + +on: + push: + branches: ["release"] + workflow_dispatch: {} + +env: + AWS_REGION: us-west-2 + ACCOUNT_ID: 590184104064 + ECR_REPOSITORY: popcong-server + REGISTRY: ${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com + IMAGE_TAG: ${{ github.sha }} + +concurrency: + group: release-deploy + cancel-in-progress: true + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + uses: aws-actions/amazon-ecr-login@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build & Push (linux/amd64) + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + push: true + context: . + file: ./Dockerfile + tags: | + ${{ env.REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} + ${{ env.REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Connect & Deploy on EC2 + uses: appleboy/ssh-action@v1.2.0 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_SSH_KEY }} + script: | + set -euo pipefail + + AWS_REGION="${{ env.AWS_REGION }}" + ACCOUNT_ID="${{ env.ACCOUNT_ID }}" + REGISTRY="${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" + + echo "[EC2] ECR 로그인" + aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REGISTRY" + + echo "[EC2] 디렉토리 이동" + cd ~/popcong + + echo "[EC2] 최신 이미지 Pull" + docker compose pull + + echo "[EC2] 재기동" + docker compose up -d --remove-orphans + + echo "[EC2] 상태 확인" + docker compose ps + docker ps \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..68e9327 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# amazoncorretto:21-alpine 이미지를 베이스로 사용 / 컨테이너 내부 작업 디렉토리는 app으로 함 +FROM amazoncorretto:21-alpine AS builder +WORKDIR /app + +# Gradle 캐시 최적화를 위한 복사 +COPY gradlew gradle.properties* settings.gradle* build.gradle* /app/ +COPY gradle /app/gradle + +# Gradle Wrapper 실행 권한 부여 및 버전 확인 +RUN chmod +x gradlew && ./gradlew --version + +# 실제 소스코드 복사 +COPY . /app + +# 이미지 빌드 +RUN ./gradlew clean bootJar -x test + +# Spring Boot layertools로 레이어 추출 +RUN java -Djarmode=layertools -jar $(find build/libs -name "*.jar" | head -n 1) extract --destination /app/layers + +# Runtime +# 런타임에서도 같은 openjdk 이미지 사용 +FROM amazoncorretto:21-alpine + +# 일반 사용자로 실행 +RUN addgroup -S popcong && adduser -S popcong -G popcong -u 1001 + +# 런타임 작업 디렉토리 지정 +WORKDIR /app + +# Spring Boot layertools 사용한 경우 설정하는 부분 +# 의존성 레이어 +COPY --from=builder /app/layers/dependencies/ ./ +# Spring Boot 런처 레이어 +COPY --from=builder /app/layers/spring-boot-loader/ ./ +# 스냅샷 의존성 레이어 +COPY --from=builder /app/layers/snapshot-dependencies/ ./ +# 애플리케이션 코드 레이어: 가장 자주 바뀌는 부분 +COPY --from=builder /app/layers/application/ ./ + +# 한국 환경 기본 환경변수 설정 +ENV TZ=Asia/Seoul \ + LANG=ko_KR.UTF-8 \ + LANGUAGE=ko_KR:ko:en \ + LC_ALL=ko_KR.UTF-8 \ + SPRING_PROFILES_ACTIVE=prod \ + SERVER_PORT=8080 \ + JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:InitialRAMPercentage=50 -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Seoul" + +# 컨테이너에서 노출할 포트 +EXPOSE 8080 + +# root가 아닌 spring 사용자로 실행 +USER popcong + +# JarLauncher로 레이어 디렉토리 구조에 맞춰 앱 부팅 (Spring Boot layertools 사용한 경우) +ENTRYPOINT ["sh","-c","java $JAVA_OPTS -cp /app org.springframework.boot.loader.launch.JarLauncher"] \ No newline at end of file diff --git a/src/main/java/popcong/app/adapter/in/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/popcong/app/adapter/in/jwt/handler/OAuth2SuccessHandler.java index 03c20a6..ea45c3e 100644 --- a/src/main/java/popcong/app/adapter/in/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/popcong/app/adapter/in/jwt/handler/OAuth2SuccessHandler.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.time.Duration; import java.util.Arrays; -import java.util.List; import java.util.Optional; //import static popcong.app.infra.config.security.SignInRedirectCaptureFilter.ALLOW_LIST; @@ -39,10 +38,9 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler private String frontendBaseUrl; @Value("${app.frontend.path.main}") - private String mainPath; - + private String mainPath; // home @Value("${app.frontend.path.signup}") - private String signupPath; + private String signupPath; // signup @Value("${app.cookie.domain}") private String cookieDomain; @@ -87,18 +85,19 @@ public void onAuthenticationSuccess( log.info("accessToken: {}", accessToken); log.info("refreshToken: {}", refreshToken); - // 레거시 이름(예전 소문자) 먼저 제거 + // 이름 모두 제거 removeCookie(response, "access_token", "/"); removeCookie(response, "refresh_token", "/"); - -// 혹시 동일 이름이 다른 속성으로 남아있을 수 있으니 현행 이름도 한번 초기화 removeCookie(response, accessCookieName, accessCookiePath); removeCookie(response, refreshCookieName, refreshCookiePath); -// 현행 이름으로만 심기 + // 현재 이름으로만 심기 addCookie(response, accessCookieName, accessToken, accessCookiePath, accessCookieMaxAge); addCookie(response, refreshCookieName, refreshToken, refreshCookiePath, refreshCookieMaxAge); + response.setHeader("Cache-Control", "no-store"); + response.setHeader("Pragma", "no-cache"); + String desired = readCookie(request, "login_redirect") .or(() -> Optional.ofNullable(request.getParameter("redirect_uri"))) .or(() -> Optional.ofNullable(request.getHeader("Origin"))) diff --git a/src/main/java/popcong/app/global/exception/error/CommonErrorCode.java b/src/main/java/popcong/app/global/exception/error/CommonErrorCode.java index 70323b5..7765734 100644 --- a/src/main/java/popcong/app/global/exception/error/CommonErrorCode.java +++ b/src/main/java/popcong/app/global/exception/error/CommonErrorCode.java @@ -18,9 +18,6 @@ public enum CommonErrorCode implements ErrorCode { METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "C4004", "Invalid Http Method"), NO_REQUIRED_FILES(HttpStatus.BAD_REQUEST.value(), "C4005", "필수 제출 파일이 누락되었습니다."), - // 405 - METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "C4054", "Invalid Http Method"), - // 5XX INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "C5001", "Internal Server Error"); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ae72b98..bb6e2a8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,19 +1,3 @@ spring: profiles: - include: secret - - jpa: - hibernate: - ddl-auto: update # 필요시 create - properties: - hibernate: - format_sql: true - show_sql: true - open-in-view: false - -cloud: - aws: - profile: dev - region: us-east-2 # 버킷 리전 - s3: - bucket: popcongbucket + include: secret \ No newline at end of file