desktop 다운로드 기능 수정
This commit is contained in:
@@ -78,25 +78,35 @@ public class DesktopAppBuildService {
|
|||||||
if (!jenkinsEnabled) {
|
if (!jenkinsEnabled) {
|
||||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Jenkins 연동이 비활성화되어 있습니다.");
|
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Jenkins 연동이 비활성화되어 있습니다.");
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
syncGitLatest();
|
||||||
|
|
||||||
syncGitLatest();
|
DesktopAppBuildStatusDto current = fetchLastBuildStatus();
|
||||||
|
if (current.isBuilding()) {
|
||||||
|
return DesktopAppBuildTriggerDto.builder()
|
||||||
|
.accepted(false)
|
||||||
|
.buildNumber(current.getBuildNumber())
|
||||||
|
.message("이미 빌드가 진행 중입니다 (#" + current.getBuildNumber() + ").")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
DesktopAppBuildStatusDto current = fetchLastBuildStatus();
|
triggerJenkinsBuild();
|
||||||
if (current.isBuilding()) {
|
DesktopAppBuildStatusDto after = fetchLastBuildStatus();
|
||||||
return DesktopAppBuildTriggerDto.builder()
|
return DesktopAppBuildTriggerDto.builder()
|
||||||
.accepted(false)
|
.accepted(true)
|
||||||
.buildNumber(current.getBuildNumber())
|
.buildNumber(after.getBuildNumber())
|
||||||
.message("이미 빌드가 진행 중입니다 (#" + current.getBuildNumber() + ").")
|
.message("Jenkins에서 macOS·Windows 데스크톱 앱 빌드를 시작했습니다. 완료 후 설치 파일이 자동으로 반영됩니다.")
|
||||||
.build();
|
.build();
|
||||||
|
} catch (ResponseStatusException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (WebClientResponseException e) {
|
||||||
|
log.warn("[desktop-build] Jenkins HTTP {}: {}", e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, jenkinsHttpError(e));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[desktop-build] trigger failed: {}", e.getMessage());
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY,
|
||||||
|
"Jenkins 빌드 요청 실패: " + rootMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerJenkinsBuild();
|
|
||||||
DesktopAppBuildStatusDto after = fetchLastBuildStatus();
|
|
||||||
return DesktopAppBuildTriggerDto.builder()
|
|
||||||
.accepted(true)
|
|
||||||
.buildNumber(after.getBuildNumber())
|
|
||||||
.message("Jenkins에서 macOS·Windows 데스크톱 앱 빌드를 시작했습니다. 완료 후 설치 파일이 자동으로 반영됩니다.")
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DesktopAppBuildStatusDto fetchLastBuildStatus() {
|
private DesktopAppBuildStatusDto fetchLastBuildStatus() {
|
||||||
@@ -156,16 +166,28 @@ public class DesktopAppBuildService {
|
|||||||
private void triggerJenkinsBuild() {
|
private void triggerJenkinsBuild() {
|
||||||
WebClient client = jenkinsClient();
|
WebClient client = jenkinsClient();
|
||||||
String buildUrl = jobApiPath() + "/build";
|
String buildUrl = jobApiPath() + "/build";
|
||||||
|
Map<String, String> crumb = fetchCrumbOptional(client);
|
||||||
try {
|
try {
|
||||||
postBuild(client, buildUrl, null);
|
|
||||||
} catch (WebClientResponseException.Forbidden e) {
|
|
||||||
Map<String, String> crumb = fetchCrumb(client);
|
|
||||||
postBuild(client, buildUrl, crumb);
|
postBuild(client, buildUrl, crumb);
|
||||||
|
} catch (WebClientResponseException.Forbidden e) {
|
||||||
|
if (crumb != null) throw e;
|
||||||
|
postBuild(client, buildUrl, fetchCrumb(client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> fetchCrumbOptional(WebClient client) {
|
||||||
|
try {
|
||||||
|
return fetchCrumb(client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("[desktop-build] crumb optional skip: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void postBuild(WebClient client, String buildUrl, Map<String, String> crumb) {
|
private void postBuild(WebClient client, String buildUrl, Map<String, String> crumb) {
|
||||||
var spec = client.post().uri(buildUrl);
|
var spec = client.post()
|
||||||
|
.uri(buildUrl)
|
||||||
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
if (crumb != null) {
|
if (crumb != null) {
|
||||||
spec = spec.header(crumb.get("field"), crumb.get("value"));
|
spec = spec.header(crumb.get("field"), crumb.get("value"));
|
||||||
}
|
}
|
||||||
@@ -215,6 +237,27 @@ public class DesktopAppBuildService {
|
|||||||
return t == null || t.isBlank() ? null : t;
|
return t == null || t.isBlank() ? null : t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String jenkinsHttpError(WebClientResponseException e) {
|
||||||
|
int code = e.getStatusCode().value();
|
||||||
|
if (code == 401 || code == 403) {
|
||||||
|
return "Jenkins 인증 실패 — GC_DESKTOP_JENKINS_USER·TOKEN을 확인하세요.";
|
||||||
|
}
|
||||||
|
if (code == 404) {
|
||||||
|
return "Jenkins Job을 찾을 수 없습니다 — goldenChart-Desktop-Pipeline Job을 등록하세요.";
|
||||||
|
}
|
||||||
|
return "Jenkins HTTP " + code + ": " + e.getStatusText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String rootMessage(Throwable e) {
|
||||||
|
Throwable t = e;
|
||||||
|
while (t.getCause() != null) t = t.getCause();
|
||||||
|
String msg = t.getMessage();
|
||||||
|
if (msg != null && msg.contains("Connection refused")) {
|
||||||
|
return "Jenkins에 연결할 수 없습니다. Docker 환경이면 GC_DESKTOP_JENKINS_URL=http://host.docker.internal:8090 를 설정하세요.";
|
||||||
|
}
|
||||||
|
return msg != null ? msg : e.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private void syncGitLatest() {
|
private void syncGitLatest() {
|
||||||
if (!StringUtils.hasText(gitBareDir)) {
|
if (!StringUtils.hasText(gitBareDir)) {
|
||||||
log.debug("[desktop-build] git-bare-dir 미설정 — Jenkins SCM checkout에 위임");
|
log.debug("[desktop-build] git-bare-dir 미설정 — Jenkins SCM checkout에 위임");
|
||||||
|
|||||||
@@ -22,6 +22,16 @@ goldenchart:
|
|||||||
cors:
|
cors:
|
||||||
allowed-origins:
|
allowed-origins:
|
||||||
- "*"
|
- "*"
|
||||||
|
desktop-app:
|
||||||
|
release-dir: ${GC_DESKTOP_APP_RELEASE_DIR:/app/data/desktop-releases}
|
||||||
|
public-base-url: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:http://exdev.co.kr}
|
||||||
|
jenkins:
|
||||||
|
enabled: ${GC_DESKTOP_JENKINS_ENABLED:true}
|
||||||
|
# Docker 컨테이너 → 호스트 Jenkins (127.0.0.1:8090 은 컨테이너 자신을 가리킴)
|
||||||
|
url: ${GC_DESKTOP_JENKINS_URL:http://host.docker.internal:8090}
|
||||||
|
job: ${GC_DESKTOP_JENKINS_JOB:goldenChart-Desktop-Pipeline}
|
||||||
|
user: ${GC_DESKTOP_JENKINS_USER:}
|
||||||
|
token: ${GC_DESKTOP_JENKINS_TOKEN:}
|
||||||
|
|
||||||
# FCM — exdev: FIREBASE_ENABLED=true + 서비스 계정 JSON 마운트
|
# FCM — exdev: FIREBASE_ENABLED=true + 서비스 계정 JSON 마운트
|
||||||
firebase:
|
firebase:
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ services:
|
|||||||
GC_MOBILE_APP_RELEASE_DIR: /app/data/mobile-releases
|
GC_MOBILE_APP_RELEASE_DIR: /app/data/mobile-releases
|
||||||
GC_MOBILE_APP_VERSION: ${GC_MOBILE_APP_VERSION:-}
|
GC_MOBILE_APP_VERSION: ${GC_MOBILE_APP_VERSION:-}
|
||||||
GC_MOBILE_APP_PUBLIC_BASE_URL: ${GC_MOBILE_APP_PUBLIC_BASE_URL:-http://exdev.co.kr}
|
GC_MOBILE_APP_PUBLIC_BASE_URL: ${GC_MOBILE_APP_PUBLIC_BASE_URL:-http://exdev.co.kr}
|
||||||
|
GC_DESKTOP_APP_RELEASE_DIR: /app/data/desktop-releases
|
||||||
|
GC_DESKTOP_APP_PUBLIC_BASE_URL: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:-http://exdev.co.kr}
|
||||||
|
GC_DESKTOP_JENKINS_ENABLED: ${GC_DESKTOP_JENKINS_ENABLED:-true}
|
||||||
|
GC_DESKTOP_JENKINS_URL: ${GC_DESKTOP_JENKINS_URL:-http://host.docker.internal:8090}
|
||||||
|
GC_DESKTOP_JENKINS_JOB: ${GC_DESKTOP_JENKINS_JOB:-goldenChart-Desktop-Pipeline}
|
||||||
|
GC_DESKTOP_JENKINS_USER: ${GC_DESKTOP_JENKINS_USER:-}
|
||||||
|
GC_DESKTOP_JENKINS_TOKEN: ${GC_DESKTOP_JENKINS_TOKEN:-}
|
||||||
FIREBASE_ENABLED: ${FIREBASE_ENABLED:-false}
|
FIREBASE_ENABLED: ${FIREBASE_ENABLED:-false}
|
||||||
FIREBASE_SERVICE_ACCOUNT: ${FIREBASE_SERVICE_ACCOUNT:-firebase-service-account.json}
|
FIREBASE_SERVICE_ACCOUNT: ${FIREBASE_SERVICE_ACCOUNT:-firebase-service-account.json}
|
||||||
ports:
|
ports:
|
||||||
@@ -76,6 +83,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- verification-images:/app/data/verification-images
|
- verification-images:/app/data/verification-images
|
||||||
- ./data/mobile-releases:/app/data/mobile-releases:ro
|
- ./data/mobile-releases:/app/data/mobile-releases:ro
|
||||||
|
- ./data/desktop-releases:/app/data/desktop-releases:ro
|
||||||
|
- ./frontend/public/desktop/updates:/app/data/desktop-updates:ro
|
||||||
# FCM: .env 에 FIREBASE_SERVICE_ACCOUNT_PATH=/path/to/firebase-service-account.json 설정 시 주석 해제
|
# FCM: .env 에 FIREBASE_SERVICE_ACCOUNT_PATH=/path/to/firebase-service-account.json 설정 시 주석 해제
|
||||||
# - ${FIREBASE_SERVICE_ACCOUNT_PATH}:/app/firebase-service-account.json:ro
|
# - ${FIREBASE_SERVICE_ACCOUNT_PATH}:/app/firebase-service-account.json:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -86,6 +95,8 @@ services:
|
|||||||
start_period: 60s
|
start_period: 60s
|
||||||
networks:
|
networks:
|
||||||
- goldenchart-net
|
- goldenchart-net
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
# ── React Frontend (Nginx) ───────────────────────────────────────────────
|
# ── React Frontend (Nginx) ───────────────────────────────────────────────
|
||||||
frontend:
|
frontend:
|
||||||
|
|||||||
Reference in New Issue
Block a user