From 1db2c35ffa1b9aff85026606ab4eef9767ed5028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Fri, 27 Mar 2026 16:10:01 +0900 Subject: [PATCH 01/12] add: oci-vault library dependency --- hangsha/common/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hangsha/common/build.gradle.kts b/hangsha/common/build.gradle.kts index 8ae5288..01120e9 100644 --- a/hangsha/common/build.gradle.kts +++ b/hangsha/common/build.gradle.kts @@ -24,6 +24,8 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation(kotlin("stdlib")) + implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:2.1.0") + } kotlin { From 1eb5357d403e2107b6ce046e2122f0f875e887db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 00:42:15 +0900 Subject: [PATCH 02/12] add: vault related lib and dependency setting --- hangsha/common/build.gradle.kts | 22 +++++++++++++++++-- .../src/main/resources/application-common.yml | 12 +++++----- hangsha/src/main/resources/application.yml | 14 ++++++------ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/hangsha/common/build.gradle.kts b/hangsha/common/build.gradle.kts index 01120e9..2b34ed1 100644 --- a/hangsha/common/build.gradle.kts +++ b/hangsha/common/build.gradle.kts @@ -14,9 +14,27 @@ group = "com.team1" version = "unspecified" repositories { - mavenCentral() + mavenCentral() // 1. 공용 저장소 + maven { // 2. 와플스튜디오 사내 저장소 + url = uri("https://maven.pkg.github.com/wafflestudio/spring-waffle") + credentials { + username = "wafflestudio" + password = findProperty("gpr.key") as String? + ?: System.getenv("GITHUB_TOKEN") + ?: runCatching { + ProcessBuilder("gh", "auth", "token") + .start() + .inputStream + .bufferedReader() + .readText() + .trim() + }.getOrDefault("") + } + } } + + dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework:spring-web") @@ -24,7 +42,7 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation(kotlin("stdlib")) - implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:2.1.0") + implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:1.1.0") } diff --git a/hangsha/common/src/main/resources/application-common.yml b/hangsha/common/src/main/resources/application-common.yml index 99de074..1b77e24 100644 --- a/hangsha/common/src/main/resources/application-common.yml +++ b/hangsha/common/src/main/resources/application-common.yml @@ -13,9 +13,9 @@ spring: activate: on-profile: dev datasource: - url: ${SPRING_DATASOURCE_URL} - username: ${SPRING_DATASOURCE_USERNAME} - password: ${SPRING_DATASOURCE_PASSWORD} + url: + username: + password: --- spring: @@ -23,6 +23,6 @@ spring: activate: on-profile: prod datasource: - url: ${SPRING_DATASOURCE_URL} - username: ${SPRING_DATASOURCE_USERNAME} - password: ${SPRING_DATASOURCE_PASSWORD} \ No newline at end of file + url: + username: + password: \ No newline at end of file diff --git a/hangsha/src/main/resources/application.yml b/hangsha/src/main/resources/application.yml index 972a1ad..d000eb6 100644 --- a/hangsha/src/main/resources/application.yml +++ b/hangsha/src/main/resources/application.yml @@ -26,15 +26,15 @@ spring: client: registration: google: - client-id: ${GOOGLE_CLIENT_ID} - client-secret: ${GOOGLE_CLIENT_SECRET} + client-id: + client-secret: scope: - email - profile redirect-uri: "{baseUrl}/login/oauth2/code/google" naver: - client-id: ${NAVER_CLIENT_ID} - client-secret: ${NAVER_CLIENT_SECRET} + client-id: + client-secret: client-authentication-method: client_secret_post authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/naver" @@ -43,8 +43,8 @@ spring: - email client-name: Naver kakao: - client-id: ${KAKAO_CLIENT_ID} - client-secret: ${KAKAO_CLIENT_SECRET} + client-id: + client-secret: client-authentication-method: client_secret_post authorization-grant-type: authorization_code redirect-uri: "{baseUrl}/login/oauth2/code/kakao" @@ -65,7 +65,7 @@ spring: user-name-attribute: id jwt: - secret: ${JWT_SECRET} # TODO: jwt secret 또한 secret var 설정 + secret: access-expiration-ms: 3600000 refresh-expiration-ms: 1209600000 From e7171efab4b2d7d60541c31580401f9b812f4df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 09:46:34 +0900 Subject: [PATCH 03/12] chore: oci vault lib bug patch and test value logging --- hangsha/build.gradle.kts | 17 +++++ .../src/main/resources/application-common.yml | 23 +++++- .../team1/hangsha/config/TestValueLogger.kt | 19 +++++ .../VaultDebugEnvironmentPostProcessor.kt | 76 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 + 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt create mode 100644 hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt create mode 100644 hangsha/src/main/resources/META-INF/spring.factories diff --git a/hangsha/build.gradle.kts b/hangsha/build.gradle.kts index cec5c2d..f3df268 100644 --- a/hangsha/build.gradle.kts +++ b/hangsha/build.gradle.kts @@ -22,10 +22,27 @@ configurations { repositories { mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/wafflestudio/spring-waffle") + credentials { + username = "wafflestudio" + password = findProperty("gpr.key") as String? + ?: System.getenv("GITHUB_TOKEN") + ?: runCatching { + ProcessBuilder("gh", "auth", "token") + .start() + .inputStream + .bufferedReader() + .readText() + .trim() + }.getOrDefault("") + } + } } dependencies { implementation(project(":common")) + implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:1.1.0") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-validation") diff --git a/hangsha/common/src/main/resources/application-common.yml b/hangsha/common/src/main/resources/application-common.yml index 1b77e24..51d1e55 100644 --- a/hangsha/common/src/main/resources/application-common.yml +++ b/hangsha/common/src/main/resources/application-common.yml @@ -7,6 +7,19 @@ spring: username: user password: password +oci: + auth: + type: config + vault: + secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqawu5rdzjsnl3zot7ii3svbmup5akjqljppt4d5wwjimgq" + + +logging: # for local debugging + level: + org.springframework.boot.context.config: TRACE + org.springframework.core.env: DEBUG + com.oracle.bmc: DEBUG + com.team1.hangsha: DEBUG --- spring: config: @@ -17,6 +30,10 @@ spring: username: password: +oci: + vault: + secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqagobbswqnpk5ezitpkdb7kidfrnhuuotu5yudac62bhpa" + --- spring: config: @@ -25,4 +42,8 @@ spring: datasource: url: username: - password: \ No newline at end of file + password: + +oci: + vault: + secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqaf77fsikezk5n2xakqhaokn33iedgj7wv6pxqa4q23aea" diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt b/hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt new file mode 100644 index 0000000..946f6b7 --- /dev/null +++ b/hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt @@ -0,0 +1,19 @@ +package com.team1.hangsha.config + +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Component +import org.springframework.boot.context.event.ApplicationReadyEvent + +@Component +class TestValueLogger( + @Value("\${test:}") private val testValue: String, +) { + private val log = LoggerFactory.getLogger(TestValueLogger::class.java) + + @EventListener(ApplicationReadyEvent::class) + fun logTestValue() { + log.info("[test-value] test={}", testValue) + } +} diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt b/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt new file mode 100644 index 0000000..3e44c25 --- /dev/null +++ b/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt @@ -0,0 +1,76 @@ +package com.team1.hangsha.config + +import org.slf4j.LoggerFactory +import org.springframework.boot.SpringApplication +import org.springframework.boot.env.EnvironmentPostProcessor +import org.springframework.core.Ordered +import org.springframework.core.env.ConfigurableEnvironment +import org.springframework.core.env.EnumerablePropertySource +import org.springframework.core.env.PropertySource + +class VaultDebugEnvironmentPostProcessor : EnvironmentPostProcessor, Ordered { + private val log = LoggerFactory.getLogger(VaultDebugEnvironmentPostProcessor::class.java) + + private val interestingKeys = listOf( + "spring.security.oauth2.client.registration.google.client-id", + "spring.security.oauth2.client.registration.google.client-secret", + "spring.security.oauth2.client.registration.naver.client-id", + "spring.security.oauth2.client.registration.kakao.client-id", + "jwt.secret", + ) + + override fun getOrder(): Int = Ordered.LOWEST_PRECEDENCE + + override fun postProcessEnvironment( + environment: ConfigurableEnvironment, + application: SpringApplication, + ) { + log.info( + "[vault-debug] activeProfiles={}, defaultProfiles={}", + environment.activeProfiles.joinToString(","), + environment.defaultProfiles.joinToString(","), + ) + + val secretIds = environment.getProperty("oci.vault.secret-ids") + log.info("[vault-debug] oci.vault.secret-ids present={}, len={}", secretIds != null, secretIds?.length ?: -1) + + val propertySourceNames = environment.propertySources.map(PropertySource<*>::getName) + log.info("[vault-debug] propertySources={}", propertySourceNames.joinToString(" -> ")) + + val vaultSource = environment.propertySources["oci-vault-secrets"] + if (vaultSource == null) { + log.warn("[vault-debug] propertySource 'oci-vault-secrets' NOT FOUND") + } else { + log.info("[vault-debug] propertySource 'oci-vault-secrets' FOUND (type={})", vaultSource.javaClass.name) + if (vaultSource is EnumerablePropertySource<*>) { + val names = vaultSource.propertyNames.toSet() + log.info("[vault-debug] oci-vault-secrets keyCount={}", names.size) + for (key in interestingKeys) { + log.info("[vault-debug] oci-vault-secrets hasKey({})={}", key, names.contains(key)) + } + } + } + + for (key in interestingKeys) { + val resolved = environment.getProperty(key) + val owner = findOwner(environment, key) + log.info( + "[vault-debug] key={} resolvedLen={} owner={}", + key, + resolved?.length ?: -1, + owner ?: "NOT_FOUND", + ) + } + + } + + private fun findOwner(environment: ConfigurableEnvironment, key: String): String? { + for (source in environment.propertySources) { + val value = source.getProperty(key) + if (value != null) { + return source.name + } + } + return null + } +} diff --git a/hangsha/src/main/resources/META-INF/spring.factories b/hangsha/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..f92ba1b --- /dev/null +++ b/hangsha/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ +com.wafflestudio.spring.ocivault.config.OciVaultEnvironmentPostProcessor,\ +com.team1.hangsha.config.VaultDebugEnvironmentPostProcessor From c7b19340a798ab3d71e0e3185174b036e0f31de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 10:00:25 +0900 Subject: [PATCH 04/12] fix: migrate vault config to common setting --- hangsha/batch/build.gradle.kts | 17 +++++ .../team1/hangsha/batch/BatchApplication.kt | 4 +- .../batch/src/main/resources/application.yml | 4 +- hangsha/common/build.gradle.kts | 20 +---- .../team1/hangsha/config/TestValueLogger.kt | 5 +- .../main/resources/META-INF/spring.factories | 3 +- .../VaultDebugEnvironmentPostProcessor.kt | 76 ------------------- hangsha/src/main/resources/application.yml | 1 + 8 files changed, 28 insertions(+), 102 deletions(-) rename hangsha/{ => common}/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt (71%) rename hangsha/{ => common}/src/main/resources/META-INF/spring.factories (62%) delete mode 100644 hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt diff --git a/hangsha/batch/build.gradle.kts b/hangsha/batch/build.gradle.kts index 47b1b6d..6d601da 100644 --- a/hangsha/batch/build.gradle.kts +++ b/hangsha/batch/build.gradle.kts @@ -17,10 +17,27 @@ java { repositories { mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/wafflestudio/spring-waffle") + credentials { + username = "wafflestudio" + password = findProperty("gpr.key") as String? + ?: System.getenv("GITHUB_TOKEN") + ?: runCatching { + ProcessBuilder("gh", "auth", "token") + .start() + .inputStream + .bufferedReader() + .readText() + .trim() + }.getOrDefault("") + } + } } dependencies { implementation(project(":common")) + implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:1.1.0") implementation("org.springframework.boot:spring-boot-starter") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") diff --git a/hangsha/batch/src/main/kotlin/com/team1/hangsha/batch/BatchApplication.kt b/hangsha/batch/src/main/kotlin/com/team1/hangsha/batch/BatchApplication.kt index 06723f5..d11dd80 100644 --- a/hangsha/batch/src/main/kotlin/com/team1/hangsha/batch/BatchApplication.kt +++ b/hangsha/batch/src/main/kotlin/com/team1/hangsha/batch/BatchApplication.kt @@ -1,6 +1,7 @@ package com.team1.hangsha.batch import com.team1.hangsha.config.DatabaseConfig +import com.team1.hangsha.config.TestValueLogger import com.team1.hangsha.com.team1.hangsha.config.JacksonConfig import com.team1.hangsha.event.service.EventSyncService import org.springframework.boot.WebApplicationType @@ -13,7 +14,8 @@ import org.springframework.context.annotation.Import DatabaseConfig::class, JacksonConfig::class, EventSyncService::class, -) + TestValueLogger::class, +) // for explicit bean import class BatchApplication fun main(args: Array) { diff --git a/hangsha/batch/src/main/resources/application.yml b/hangsha/batch/src/main/resources/application.yml index eca862d..fd71ced 100644 --- a/hangsha/batch/src/main/resources/application.yml +++ b/hangsha/batch/src/main/resources/application.yml @@ -11,4 +11,6 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver jackson: - time-zone: Asia/Seoul \ No newline at end of file + time-zone: Asia/Seoul + +test: \ No newline at end of file diff --git a/hangsha/common/build.gradle.kts b/hangsha/common/build.gradle.kts index 2b34ed1..0b98ff0 100644 --- a/hangsha/common/build.gradle.kts +++ b/hangsha/common/build.gradle.kts @@ -14,23 +14,7 @@ group = "com.team1" version = "unspecified" repositories { - mavenCentral() // 1. 공용 저장소 - maven { // 2. 와플스튜디오 사내 저장소 - url = uri("https://maven.pkg.github.com/wafflestudio/spring-waffle") - credentials { - username = "wafflestudio" - password = findProperty("gpr.key") as String? - ?: System.getenv("GITHUB_TOKEN") - ?: runCatching { - ProcessBuilder("gh", "auth", "token") - .start() - .inputStream - .bufferedReader() - .readText() - .trim() - }.getOrDefault("") - } - } + mavenCentral() } @@ -42,8 +26,6 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation(kotlin("stdlib")) - implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:1.1.0") - } kotlin { diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt similarity index 71% rename from hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt rename to hangsha/common/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt index 946f6b7..9b9d11b 100644 --- a/hangsha/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt +++ b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/TestValueLogger.kt @@ -2,9 +2,8 @@ package com.team1.hangsha.config import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value -import org.springframework.context.event.EventListener import org.springframework.stereotype.Component -import org.springframework.boot.context.event.ApplicationReadyEvent +import jakarta.annotation.PostConstruct @Component class TestValueLogger( @@ -12,7 +11,7 @@ class TestValueLogger( ) { private val log = LoggerFactory.getLogger(TestValueLogger::class.java) - @EventListener(ApplicationReadyEvent::class) + @PostConstruct fun logTestValue() { log.info("[test-value] test={}", testValue) } diff --git a/hangsha/src/main/resources/META-INF/spring.factories b/hangsha/common/src/main/resources/META-INF/spring.factories similarity index 62% rename from hangsha/src/main/resources/META-INF/spring.factories rename to hangsha/common/src/main/resources/META-INF/spring.factories index f92ba1b..2ac64bd 100644 --- a/hangsha/src/main/resources/META-INF/spring.factories +++ b/hangsha/common/src/main/resources/META-INF/spring.factories @@ -1,3 +1,2 @@ org.springframework.boot.env.EnvironmentPostProcessor=\ -com.wafflestudio.spring.ocivault.config.OciVaultEnvironmentPostProcessor,\ -com.team1.hangsha.config.VaultDebugEnvironmentPostProcessor +com.wafflestudio.spring.ocivault.config.OciVaultEnvironmentPostProcessor diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt b/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt deleted file mode 100644 index 3e44c25..0000000 --- a/hangsha/src/main/kotlin/com/team1/hangsha/config/VaultDebugEnvironmentPostProcessor.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.team1.hangsha.config - -import org.slf4j.LoggerFactory -import org.springframework.boot.SpringApplication -import org.springframework.boot.env.EnvironmentPostProcessor -import org.springframework.core.Ordered -import org.springframework.core.env.ConfigurableEnvironment -import org.springframework.core.env.EnumerablePropertySource -import org.springframework.core.env.PropertySource - -class VaultDebugEnvironmentPostProcessor : EnvironmentPostProcessor, Ordered { - private val log = LoggerFactory.getLogger(VaultDebugEnvironmentPostProcessor::class.java) - - private val interestingKeys = listOf( - "spring.security.oauth2.client.registration.google.client-id", - "spring.security.oauth2.client.registration.google.client-secret", - "spring.security.oauth2.client.registration.naver.client-id", - "spring.security.oauth2.client.registration.kakao.client-id", - "jwt.secret", - ) - - override fun getOrder(): Int = Ordered.LOWEST_PRECEDENCE - - override fun postProcessEnvironment( - environment: ConfigurableEnvironment, - application: SpringApplication, - ) { - log.info( - "[vault-debug] activeProfiles={}, defaultProfiles={}", - environment.activeProfiles.joinToString(","), - environment.defaultProfiles.joinToString(","), - ) - - val secretIds = environment.getProperty("oci.vault.secret-ids") - log.info("[vault-debug] oci.vault.secret-ids present={}, len={}", secretIds != null, secretIds?.length ?: -1) - - val propertySourceNames = environment.propertySources.map(PropertySource<*>::getName) - log.info("[vault-debug] propertySources={}", propertySourceNames.joinToString(" -> ")) - - val vaultSource = environment.propertySources["oci-vault-secrets"] - if (vaultSource == null) { - log.warn("[vault-debug] propertySource 'oci-vault-secrets' NOT FOUND") - } else { - log.info("[vault-debug] propertySource 'oci-vault-secrets' FOUND (type={})", vaultSource.javaClass.name) - if (vaultSource is EnumerablePropertySource<*>) { - val names = vaultSource.propertyNames.toSet() - log.info("[vault-debug] oci-vault-secrets keyCount={}", names.size) - for (key in interestingKeys) { - log.info("[vault-debug] oci-vault-secrets hasKey({})={}", key, names.contains(key)) - } - } - } - - for (key in interestingKeys) { - val resolved = environment.getProperty(key) - val owner = findOwner(environment, key) - log.info( - "[vault-debug] key={} resolvedLen={} owner={}", - key, - resolved?.length ?: -1, - owner ?: "NOT_FOUND", - ) - } - - } - - private fun findOwner(environment: ConfigurableEnvironment, key: String): String? { - for (source in environment.propertySources) { - val value = source.getProperty(key) - if (value != null) { - return source.name - } - } - return null - } -} diff --git a/hangsha/src/main/resources/application.yml b/hangsha/src/main/resources/application.yml index d000eb6..0cf565c 100644 --- a/hangsha/src/main/resources/application.yml +++ b/hangsha/src/main/resources/application.yml @@ -96,6 +96,7 @@ logging: level: com.team1.hangsha : DEBUG +test: --- # ========================================== # local profile setting From 2866ca0ba9dc052a3b39e9cf6c05ed5d9a30d2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 11:04:58 +0900 Subject: [PATCH 05/12] add: oci sdk dependency setting --- hangsha/common/build.gradle.kts | 4 ++++ .../src/main/resources/application-common.yml | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/hangsha/common/build.gradle.kts b/hangsha/common/build.gradle.kts index 0b98ff0..511a30b 100644 --- a/hangsha/common/build.gradle.kts +++ b/hangsha/common/build.gradle.kts @@ -26,6 +26,10 @@ dependencies { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation(kotlin("stdlib")) + // oci sdk for storage service + implementation("com.oracle.oci.sdk:oci-java-sdk-objectstorage:3.80.1") + implementation("com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey3:3.80.1") + } kotlin { diff --git a/hangsha/common/src/main/resources/application-common.yml b/hangsha/common/src/main/resources/application-common.yml index 51d1e55..6e397e1 100644 --- a/hangsha/common/src/main/resources/application-common.yml +++ b/hangsha/common/src/main/resources/application-common.yml @@ -1,3 +1,8 @@ +oci: + storage: + namespace: ax1dvc8vmenm + region: ap-chuncheon-1 +--- spring: config: activate: @@ -12,7 +17,8 @@ oci: type: config vault: secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqawu5rdzjsnl3zot7ii3svbmup5akjqljppt4d5wwjimgq" - + storage: + bucket: hangsha-asset-dev logging: # for local debugging level: @@ -20,6 +26,7 @@ logging: # for local debugging org.springframework.core.env: DEBUG com.oracle.bmc: DEBUG com.team1.hangsha: DEBUG + --- spring: config: @@ -33,7 +40,8 @@ spring: oci: vault: secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqagobbswqnpk5ezitpkdb7kidfrnhuuotu5yudac62bhpa" - + storage: + bucket: hangsha-asset-dev --- spring: config: @@ -47,3 +55,5 @@ spring: oci: vault: secret-ids: "ocid1.vaultsecret.oc1.ap-chuncheon-1.amaaaaaat2m5lbqaf77fsikezk5n2xakqhaokn33iedgj7wv6pxqa4q23aea" + storage: + bucket: hangsha-asset \ No newline at end of file From 5d843f1f2c5f2e436d3086dda82ab247d0065b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 11:29:08 +0900 Subject: [PATCH 06/12] add: oci config --- .../com/team1/hangsha/config/OciAuthProbe.kt | 28 ++++++++++++ .../com/team1/hangsha/config/OciConfig.kt | 45 +++++++++++++++++++ .../src/main/resources/application-common.yml | 3 ++ 3 files changed, 76 insertions(+) create mode 100644 hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciAuthProbe.kt create mode 100644 hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciConfig.kt diff --git a/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciAuthProbe.kt b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciAuthProbe.kt new file mode 100644 index 0000000..df23093 --- /dev/null +++ b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciAuthProbe.kt @@ -0,0 +1,28 @@ +package com.team1.hangsha.config + +import com.oracle.bmc.objectstorage.ObjectStorage +import com.oracle.bmc.objectstorage.requests.GetNamespaceRequest +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.context.event.EventListener +import org.springframework.stereotype.Component + +@Component +class OciAuthProbe( + private val objectStorage: ObjectStorage, + @Value("\${oci.auth.verify:false}") private val verify: Boolean, +) { + private val log = LoggerFactory.getLogger(javaClass) + + @EventListener(ApplicationReadyEvent::class) + fun verifyAuth() { + if (!verify) return + try { + val response = objectStorage.getNamespace(GetNamespaceRequest.builder().build()) + log.info("[oci-auth] OK: namespace={}", response.value) + } catch (e: Exception) { + log.error("[oci-auth] FAILED: {}", e.message, e) + } + } +} diff --git a/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciConfig.kt b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciConfig.kt new file mode 100644 index 0000000..eaf77a7 --- /dev/null +++ b/hangsha/common/src/main/kotlin/com/team1/hangsha/config/OciConfig.kt @@ -0,0 +1,45 @@ +package com.team1.hangsha.config + +import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider +import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider +import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider +import com.oracle.bmc.objectstorage.ObjectStorage +import com.oracle.bmc.objectstorage.ObjectStorageClient +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class OciConfig( + @Value("\${oci.auth.type:auto}") + private val authType: String, + @Value("\${oci.auth.profile:DEFAULT}") + private val configProfile: String, + @Value("\${oci.storage.region}") + private val region: String, +) { + private val log = LoggerFactory.getLogger(javaClass) + + @Bean + fun ociAuthProvider(): BasicAuthenticationDetailsProvider { + return when (authType.trim().lowercase()) { + "auto" -> try { + InstancePrincipalsAuthenticationDetailsProvider.builder().build() + } catch (e: Exception) { + log.info("OCI Instance Principal failed; falling back to config file auth: {}", e.message) + ConfigFileAuthenticationDetailsProvider(configProfile) + } + "config" -> ConfigFileAuthenticationDetailsProvider(configProfile) + "instance_principal" -> InstancePrincipalsAuthenticationDetailsProvider.builder().build() + else -> throw IllegalArgumentException("Unsupported oci.auth.type: $authType") + } + } + + @Bean + fun objectStorageClient(authProvider: BasicAuthenticationDetailsProvider): ObjectStorage { + return ObjectStorageClient.builder() + .region(region) + .build(authProvider) + } +} diff --git a/hangsha/common/src/main/resources/application-common.yml b/hangsha/common/src/main/resources/application-common.yml index 6e397e1..0022acb 100644 --- a/hangsha/common/src/main/resources/application-common.yml +++ b/hangsha/common/src/main/resources/application-common.yml @@ -1,7 +1,10 @@ oci: + auth: + verify: true # get namespace when booting storage: namespace: ax1dvc8vmenm region: ap-chuncheon-1 + --- spring: config: From 70fce0f3020112acd751e9e6f964afcbb3fb2d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 11:38:44 +0900 Subject: [PATCH 07/12] add: test ocl upload endpoints --- hangsha/build.gradle.kts | 2 + .../hangsha/common/upload/OciUploadService.kt | 52 +++++++++++ .../common/upload/dto/UploadResponse.kt | 5 ++ .../hangsha/common/upload/OciUploadService.kt | 89 +++++++++++++++++++ .../team1/hangsha/config/SecurityConfig.kt | 3 +- .../hangsha/upload/OciUploadController.kt | 25 ++++++ 6 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt create mode 100644 hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/dto/UploadResponse.kt create mode 100644 hangsha/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt create mode 100644 hangsha/src/main/kotlin/com/team1/hangsha/upload/OciUploadController.kt diff --git a/hangsha/build.gradle.kts b/hangsha/build.gradle.kts index f3df268..6b30413 100644 --- a/hangsha/build.gradle.kts +++ b/hangsha/build.gradle.kts @@ -43,6 +43,8 @@ repositories { dependencies { implementation(project(":common")) implementation("com.wafflestudio.spring:spring-boot-starter-waffle-oci-vault:1.1.0") + implementation("com.oracle.oci.sdk:oci-java-sdk-objectstorage:3.80.1") + // TODO: common의 dependency를 root api, batch에 공유하는 과정에서 문제 -> 나중에 해결하기. implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-validation") diff --git a/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt b/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt new file mode 100644 index 0000000..7470553 --- /dev/null +++ b/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt @@ -0,0 +1,52 @@ +package com.team1.hangsha.common.upload + +import com.oracle.bmc.objectstorage.ObjectStorage +import com.oracle.bmc.objectstorage.requests.PutObjectRequest +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.UUID + +@Service +class OciUploadService( + private val objectStorage: ObjectStorage, + @Value("\${oci.storage.namespace}") + private val namespace: String, + @Value("\${oci.storage.bucket}") + private val bucket: String, + @Value("\${oci.storage.region}") + private val region: String, +) { + fun uploadFile(prefix: String?, file: MultipartFile): String { + val objectName = buildObjectName(prefix, file.originalFilename) + val contentType = file.contentType ?: "application/octet-stream" + + file.inputStream.use { input -> + val request = PutObjectRequest.builder() + .namespaceName(namespace) + .bucketName(bucket) + .objectName(objectName) + .contentLength(file.size) + .contentType(contentType) + .putObjectBody(input) + .build() + objectStorage.putObject(request) + } + + return buildPublicUrl(objectName) + } + + private fun buildObjectName(prefix: String?, originalFilename: String?): String { + val safePrefix = prefix?.trim()?.trim('/')?.takeIf { it.isNotBlank() } + val filename = originalFilename?.takeIf { it.isNotBlank() } ?: "upload-${UUID.randomUUID()}" + return if (safePrefix == null) filename else "$safePrefix/$filename" + } + + private fun buildPublicUrl(objectName: String): String { + val encodedObjectName = URLEncoder.encode(objectName, StandardCharsets.UTF_8) + .replace("+", "%20") + return "https://objectstorage.$region.oraclecloud.com/n/$namespace/b/$bucket/o/$encodedObjectName" + } +} diff --git a/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/dto/UploadResponse.kt b/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/dto/UploadResponse.kt new file mode 100644 index 0000000..6176e2c --- /dev/null +++ b/hangsha/common/src/main/kotlin/com/team1/hangsha/common/upload/dto/UploadResponse.kt @@ -0,0 +1,5 @@ +package com.team1.hangsha.common.upload.dto + +data class UploadResponse( + val url: String, +) diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt b/hangsha/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt new file mode 100644 index 0000000..b16e3a5 --- /dev/null +++ b/hangsha/src/main/kotlin/com/team1/hangsha/common/upload/OciUploadService.kt @@ -0,0 +1,89 @@ +package com.team1.hangsha.common.upload + +import com.oracle.bmc.objectstorage.ObjectStorage +import com.oracle.bmc.objectstorage.requests.PutObjectRequest +import com.team1.hangsha.common.error.DomainException +import com.team1.hangsha.common.error.ErrorCode +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile +import java.net.URLEncoder +import java.nio.charset.StandardCharsets +import java.util.UUID + +@Service +class OciUploadService( + private val objectStorage: ObjectStorage, + private val uploadProperties: UploadProperties, + @Value("\${oci.storage.namespace}") private val namespace: String, + @Value("\${oci.storage.bucket}") private val bucket: String, + @Value("\${oci.storage.region}") private val region: String, +) { + fun uploadFile(prefix: String?, file: MultipartFile): String { + if (file.isEmpty || file.size <= 0) { + throw DomainException(ErrorCode.UPLOAD_FILE_EMPTY) + } + if (file.size > uploadProperties.maxSizeBytes) { + throw DomainException(ErrorCode.UPLOAD_FAILED, "파일이 너무 큽니다 (max=${uploadProperties.maxSizeBytes} bytes)") + } + + val ext = guessExtension(file.originalFilename, file.contentType ?: "") + val safePrefix = sanitizePrefix(prefix) + val objectName = "$safePrefix/${UUID.randomUUID()}.$ext" + + try { + val request = PutObjectRequest.builder() + .namespaceName(namespace) + .bucketName(bucket) + .objectName(objectName) + .contentLength(file.size) + .contentType(file.contentType) + .putObjectBody(file.inputStream) + .build() + objectStorage.putObject(request) + } catch (e: Exception) { + throw DomainException(ErrorCode.UPLOAD_FAILED, cause = e) + } + + return buildObjectUrl(objectName) + } + + private fun buildObjectUrl(objectName: String): String { + val encoded = encodeObjectName(objectName) + return "https://objectstorage.$region.oraclecloud.com/n/$namespace/b/$bucket/o/$encoded" + } + + private fun encodeObjectName(objectName: String): String { + return objectName + .split('/') + .joinToString("/") { segment -> + URLEncoder.encode(segment, StandardCharsets.UTF_8) + .replace("+", "%20") + } + } + + private fun guessExtension(originalFilename: String?, contentType: String): String { + val fromName = originalFilename + ?.substringAfterLast('.', missingDelimiterValue = "") + ?.lowercase() + ?.takeIf { it.matches(Regex("[a-z0-9]{1,8}")) } + + if (!fromName.isNullOrBlank()) return fromName + + return when (contentType.lowercase()) { + "image/jpeg", "image/jpg" -> "jpg" + "image/png" -> "png" + "image/gif" -> "gif" + "image/webp" -> "webp" + else -> "bin" + } + } + + private fun sanitizePrefix(prefix: String?): String { + val trimmed = prefix?.trim()?.trim('/')?.ifBlank { null } ?: "uploads/tmp" + if (trimmed.contains("..")) { + throw DomainException(ErrorCode.INVALID_REQUEST, "Invalid path") + } + return trimmed + } +} diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/config/SecurityConfig.kt b/hangsha/src/main/kotlin/com/team1/hangsha/config/SecurityConfig.kt index 194540a..5021296 100644 --- a/hangsha/src/main/kotlin/com/team1/hangsha/config/SecurityConfig.kt +++ b/hangsha/src/main/kotlin/com/team1/hangsha/config/SecurityConfig.kt @@ -68,6 +68,7 @@ class SecurityConfig( "/admin/events/delete", // 파일 업로드 "/static/**", + "/api/v1/uploads/oci/**", ).permitAll() .anyRequest().authenticated() } @@ -80,4 +81,4 @@ class SecurityConfig( */ return http.build() } -} \ No newline at end of file +} diff --git a/hangsha/src/main/kotlin/com/team1/hangsha/upload/OciUploadController.kt b/hangsha/src/main/kotlin/com/team1/hangsha/upload/OciUploadController.kt new file mode 100644 index 0000000..391d632 --- /dev/null +++ b/hangsha/src/main/kotlin/com/team1/hangsha/upload/OciUploadController.kt @@ -0,0 +1,25 @@ +package com.team1.hangsha.upload + +import com.team1.hangsha.common.upload.OciUploadService +import com.team1.hangsha.common.upload.dto.UploadResponse +import org.springframework.http.MediaType +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile + +@RestController +@RequestMapping("/api/v1/uploads/oci") +class OciUploadController( + private val ociUploadService: OciUploadService, +) { + @PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE]) + fun upload( + @RequestParam("file") file: MultipartFile, + @RequestParam("prefix", required = false) prefix: String?, + ): UploadResponse { + val url = ociUploadService.uploadFile(prefix, file) + return UploadResponse(url) + } +} From d02417e9c811f36739f6aaeb88b597a9881f317f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 13:19:31 +0900 Subject: [PATCH 08/12] add: cicd workflows --- hangsha/.github/workflows/_deploy.yml | 69 +++++++++++++++++++ hangsha/.github/workflows/deploy-api-dev.yml | 17 +++++ hangsha/.github/workflows/deploy-api-prod.yml | 17 +++++ .../.github/workflows/deploy-batch-dev.yml | 17 +++++ .../.github/workflows/deploy-batch-prod.yml | 17 +++++ 5 files changed, 137 insertions(+) create mode 100644 hangsha/.github/workflows/_deploy.yml create mode 100644 hangsha/.github/workflows/deploy-api-dev.yml create mode 100644 hangsha/.github/workflows/deploy-api-prod.yml create mode 100644 hangsha/.github/workflows/deploy-batch-dev.yml create mode 100644 hangsha/.github/workflows/deploy-batch-prod.yml diff --git a/hangsha/.github/workflows/_deploy.yml b/hangsha/.github/workflows/_deploy.yml new file mode 100644 index 0000000..2f745a0 --- /dev/null +++ b/hangsha/.github/workflows/_deploy.yml @@ -0,0 +1,69 @@ +name: deploy-template + +on: + workflow_call: + inputs: + ocir_repository: + required: true + type: string + dockerfile: + required: true + type: string + description: "Dockerfile path (e.g., Dockerfile, batch/Dockerfile)" + gradle_task: + required: true + type: string + description: "Gradle task to build the jar (e.g., :bootJar, :batch:bootJar)" + platform: + required: false + type: string + default: "linux/arm64" + secrets: + OCI_AUTH_TOKEN: + required: true + DEPLOYER_APP_ID: + required: true + DEPLOYER_APP_PRIVATE_KEY: + required: true + +jobs: + deploy: + name: Build and Push + runs-on: ubuntu-24.04-arm + + env: + IMAGE_TAG: ${{ github.run_number }} + BUILD_NUMBER: ${{ github.run_number }} + OCIR_REGISTRY: yny.ocir.io + OCIR_NAMESPACE: ax1dvc8vmenm + OCIR_USERNAME: ax1dvc8vmenm/members/waffle-deployer + OCIR_REPOSITORY: ${{ inputs.ocir_repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + cache: gradle + + - name: Build Jar + run: ./gradlew ${{ inputs.gradle_task }} + + - name: Login to OCIR + run: echo "${{ secrets.OCI_AUTH_TOKEN }}" | docker login $OCIR_REGISTRY -u $OCIR_USERNAME --password-stdin + + - name: Docker build, tag, and push image to OCIR + id: build-push-image + run: | + docker build \ + -f ${{ inputs.dockerfile }} \ + -t $OCIR_REGISTRY/$OCIR_NAMESPACE/$OCIR_REPOSITORY:$IMAGE_TAG \ + . \ + --platform ${{ inputs.platform }} + docker push $OCIR_REGISTRY/$OCIR_NAMESPACE/$OCIR_REPOSITORY:$IMAGE_TAG + echo "image=$OCIR_REGISTRY/$OCIR_NAMESPACE/$OCIR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT diff --git a/hangsha/.github/workflows/deploy-api-dev.yml b/hangsha/.github/workflows/deploy-api-dev.yml new file mode 100644 index 0000000..1ec4aea --- /dev/null +++ b/hangsha/.github/workflows/deploy-api-dev.yml @@ -0,0 +1,17 @@ +name: Deploy-api-dev + +on: + push: + branches: [ develop ] + +jobs: + deploy: + uses: ./.github/workflows/_deploy.yml + with: + ocir_repository: hangsha-dev/hangsha-server + dockerfile: Dockerfile + gradle_task: :bootJar + secrets: + OCI_AUTH_TOKEN: ${{ secrets.OCI_AUTH_TOKEN }} + DEPLOYER_APP_ID: ${{ secrets.DEPLOYER_APP_ID }} + DEPLOYER_APP_PRIVATE_KEY: ${{ secrets.DEPLOYER_APP_PRIVATE_KEY }} diff --git a/hangsha/.github/workflows/deploy-api-prod.yml b/hangsha/.github/workflows/deploy-api-prod.yml new file mode 100644 index 0000000..0532efa --- /dev/null +++ b/hangsha/.github/workflows/deploy-api-prod.yml @@ -0,0 +1,17 @@ +name: Deploy-api-prod + +on: + push: + branches: [ main ] + +jobs: + deploy: + uses: ./.github/workflows/_deploy.yml + with: + ocir_repository: hangsha-prod/hangsha-server + dockerfile: Dockerfile + gradle_task: :bootJar + secrets: + OCI_AUTH_TOKEN: ${{ secrets.OCI_AUTH_TOKEN }} + DEPLOYER_APP_ID: ${{ secrets.DEPLOYER_APP_ID }} + DEPLOYER_APP_PRIVATE_KEY: ${{ secrets.DEPLOYER_APP_PRIVATE_KEY }} diff --git a/hangsha/.github/workflows/deploy-batch-dev.yml b/hangsha/.github/workflows/deploy-batch-dev.yml new file mode 100644 index 0000000..802ab55 --- /dev/null +++ b/hangsha/.github/workflows/deploy-batch-dev.yml @@ -0,0 +1,17 @@ +name: Deploy-batch-dev + +on: + push: + branches: [ develop ] + +jobs: + deploy: + uses: ./.github/workflows/_deploy.yml + with: + ocir_repository: hangsha-dev/hangsha-batch + dockerfile: batch/Dockerfile + gradle_task: :batch:bootJar + secrets: + OCI_AUTH_TOKEN: ${{ secrets.OCI_AUTH_TOKEN }} + DEPLOYER_APP_ID: ${{ secrets.DEPLOYER_APP_ID }} + DEPLOYER_APP_PRIVATE_KEY: ${{ secrets.DEPLOYER_APP_PRIVATE_KEY }} diff --git a/hangsha/.github/workflows/deploy-batch-prod.yml b/hangsha/.github/workflows/deploy-batch-prod.yml new file mode 100644 index 0000000..11f34d8 --- /dev/null +++ b/hangsha/.github/workflows/deploy-batch-prod.yml @@ -0,0 +1,17 @@ +name: Deploy-batch-prod + +on: + push: + branches: [ main ] + +jobs: + deploy: + uses: ./.github/workflows/_deploy.yml + with: + ocir_repository: hangsha-prod/hangsha-batch + dockerfile: batch/Dockerfile + gradle_task: :batch:bootJar + secrets: + OCI_AUTH_TOKEN: ${{ secrets.OCI_AUTH_TOKEN }} + DEPLOYER_APP_ID: ${{ secrets.DEPLOYER_APP_ID }} + DEPLOYER_APP_PRIVATE_KEY: ${{ secrets.DEPLOYER_APP_PRIVATE_KEY }} From 62807df2d679f1f94e7f107b1b668e3b0b5fa71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 14:03:08 +0900 Subject: [PATCH 09/12] chore: change dockerfile code --- hangsha/Dockerfile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/hangsha/Dockerfile b/hangsha/Dockerfile index 7f220c0..b5e64ab 100644 --- a/hangsha/Dockerfile +++ b/hangsha/Dockerfile @@ -1,15 +1,17 @@ -FROM eclipse-temurin:17-jdk-alpine +FROM eclipse-temurin:17-jdk WORKDIR /app # 컨테이너 기본 타임존을 KST로 맞추기 -RUN apk add --no-cache tzdata \ - && cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime \ - && echo "Asia/Seoul" > /etc/timezone +RUN apt-get update \ + && apt-get install -y --no-install-recommends tzdata \ + && ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime \ + && echo "Asia/Seoul" > /etc/timezone \ + && rm -rf /var/lib/apt/lists/* ENV TZ=Asia/Seoul ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/app/app.jar"] \ No newline at end of file +ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/app/app.jar"] From 83d0a533b11cbf34d24ffd40a11c6be355e6e41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Fri, 27 Mar 2026 12:59:05 +0900 Subject: [PATCH 10/12] fix: jwt secret --- hangsha/docker-compose.prod.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/hangsha/docker-compose.prod.yml b/hangsha/docker-compose.prod.yml index 39e896f..3ddd356 100644 --- a/hangsha/docker-compose.prod.yml +++ b/hangsha/docker-compose.prod.yml @@ -30,6 +30,7 @@ services: SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/${DB_NAME:-campus_db}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 SPRING_DATASOURCE_USERNAME: ${DB_USER:-user} SPRING_DATASOURCE_PASSWORD: ${DB_PASS:-password} + JWT_SECRET: ${JWT_SECRET} UPLOAD_DIR: /data/uploads UPLOAD_PUBLIC_BASE_URL: ${UPLOAD_PUBLIC_BASE_URL:-https://hangsha.site/static} volumes: From 56e4ff9bbf2ee85e9057881a28fbb38b4662748e Mon Sep 17 00:00:00 2001 From: ohsuhyeon0119 <141830897+ohsuhyeon0119@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:39:50 +0900 Subject: [PATCH 11/12] Delete .github/workflows/deploy.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 새 와플 cicd 배포 구축을 위해 삭제 --- .github/workflows/deploy.yml | 99 ------------------------------------ 1 file changed, 99 deletions(-) delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 2b68656..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Deploy to EC2 - -on: - push: - branches: ["main"] - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: "17" - distribution: "temurin" - - - name: Build with Gradle - working-directory: ./hangsha - run: | - chmod +x gradlew - ./gradlew clean build -x test - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push server image - uses: docker/build-push-action@v4 - with: - context: ./hangsha - file: ./hangsha/Dockerfile - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/campus-calendar-server:latest - - - name: Build and push batch image - uses: docker/build-push-action@v4 - with: - context: ./hangsha - file: ./hangsha/batch/Dockerfile - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/campus-calendar-batch:latest - - - name: Upload docker-compose.prod.yml to EC2 - uses: appleboy/scp-action@v1 - with: - host: ${{ secrets.EC2_HOST }} - username: ubuntu - key: ${{ secrets.EC2_KEY }} - source: "hangsha/docker-compose.prod.yml" - target: "/home/ubuntu/campus" - - - name: Deploy to EC2 - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.EC2_HOST }} - username: ubuntu - key: ${{ secrets.EC2_KEY }} - port: 22 - timeout: 30s - command_timeout: 20m - script: | - set -euo pipefail - - APP_DIR=/home/ubuntu/campus - mkdir -p "$APP_DIR" - - if [ -f "$APP_DIR/hangsha/docker-compose.prod.yml" ]; then - mv "$APP_DIR/hangsha/docker-compose.prod.yml" "$APP_DIR/docker-compose.prod.yml" - rm -rf "$APP_DIR/hangsha" - fi - - if [ -f "/home/ubuntu/.env" ]; then - cp /home/ubuntu/.env "$APP_DIR/.env" - fi - - cd "$APP_DIR" - - echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - export DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}" - export DB_NAME="campus_db" - export DB_USER="user" - export DB_PASS="password" - export DB_ROOT_PASSWORD="root" - - docker compose -f docker-compose.prod.yml pull - docker compose -f docker-compose.prod.yml up -d - docker compose -f docker-compose.prod.yml --profile batch pull batch - - docker compose -f docker-compose.prod.yml ps - docker logs --tail 200 campus-server || true - - docker image prune -f \ No newline at end of file From a10812461a1fb83306457b13b8a28eff255eccfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=98=A4=EC=88=98=ED=98=84?= Date: Sat, 28 Mar 2026 14:14:35 +0900 Subject: [PATCH 12/12] fix: mv workflow files to root dir --- {hangsha/.github => .github}/workflows/_deploy.yml | 2 ++ {hangsha/.github => .github}/workflows/deploy-api-dev.yml | 0 {hangsha/.github => .github}/workflows/deploy-api-prod.yml | 0 {hangsha/.github => .github}/workflows/deploy-batch-dev.yml | 0 {hangsha/.github => .github}/workflows/deploy-batch-prod.yml | 0 5 files changed, 2 insertions(+) rename {hangsha/.github => .github}/workflows/_deploy.yml (96%) rename {hangsha/.github => .github}/workflows/deploy-api-dev.yml (100%) rename {hangsha/.github => .github}/workflows/deploy-api-prod.yml (100%) rename {hangsha/.github => .github}/workflows/deploy-batch-dev.yml (100%) rename {hangsha/.github => .github}/workflows/deploy-batch-prod.yml (100%) diff --git a/hangsha/.github/workflows/_deploy.yml b/.github/workflows/_deploy.yml similarity index 96% rename from hangsha/.github/workflows/_deploy.yml rename to .github/workflows/_deploy.yml index 2f745a0..fbcf07d 100644 --- a/hangsha/.github/workflows/_deploy.yml +++ b/.github/workflows/_deploy.yml @@ -52,6 +52,7 @@ jobs: cache: gradle - name: Build Jar + working-directory: hangsha run: ./gradlew ${{ inputs.gradle_task }} - name: Login to OCIR @@ -59,6 +60,7 @@ jobs: - name: Docker build, tag, and push image to OCIR id: build-push-image + working-directory: hangsha run: | docker build \ -f ${{ inputs.dockerfile }} \ diff --git a/hangsha/.github/workflows/deploy-api-dev.yml b/.github/workflows/deploy-api-dev.yml similarity index 100% rename from hangsha/.github/workflows/deploy-api-dev.yml rename to .github/workflows/deploy-api-dev.yml diff --git a/hangsha/.github/workflows/deploy-api-prod.yml b/.github/workflows/deploy-api-prod.yml similarity index 100% rename from hangsha/.github/workflows/deploy-api-prod.yml rename to .github/workflows/deploy-api-prod.yml diff --git a/hangsha/.github/workflows/deploy-batch-dev.yml b/.github/workflows/deploy-batch-dev.yml similarity index 100% rename from hangsha/.github/workflows/deploy-batch-dev.yml rename to .github/workflows/deploy-batch-dev.yml diff --git a/hangsha/.github/workflows/deploy-batch-prod.yml b/.github/workflows/deploy-batch-prod.yml similarity index 100% rename from hangsha/.github/workflows/deploy-batch-prod.yml rename to .github/workflows/deploy-batch-prod.yml