stock.exdev.co.kr로 접속 url 변경

This commit is contained in:
Macbook
2026-06-17 10:00:34 +09:00
parent 58cf1e1ec7
commit 37533764ee
46 changed files with 354 additions and 75 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
# VITE_API_BASE_URL=http://localhost:8080/api
# 모바일 APK / exdev (HTTPS 443 미사용 — http 만)
VITE_API_BASE_URL=http://exdev.co.kr/api
VITE_API_BASE_URL=https://stock.exdev.co.kr/api
+2 -2
View File
@@ -26,7 +26,7 @@ npm run cap:android # or cap:ios
./scripts/upload-android-apk.sh --build --version 1.0.0
```
`app/.env` 예: `VITE_API_BASE_URL=http://exdev.co.kr/api`
`app/.env` 예: `VITE_API_BASE_URL=https://stock.exdev.co.kr/api`
자세한 내용: [`data/mobile-releases/README.md`](../data/mobile-releases/README.md)
@@ -36,7 +36,7 @@ Copy `.env.example` to `.env` and set `VITE_API_BASE_URL`.
### 로그인 시 "Failed to fetch" / "서버 응답이 느립니다"
- API는 **`http://exdev.co.kr/api`** 만 사용 (`https://exdev.co.kr` 은 443 미개방).
- API는 **`https://stock.exdev.co.kr/api`** 만 사용 (`https://stock.exdev.co.kr` 은 443 미개방).
- exdev 로그인은 **10~30초** 걸릴 수 있음 — 2분 타임아웃까지 대기.
- APK는 `app/.env``VITE_API_BASE_URL`이 빌드에 포함되어야 함.
- `androidScheme`**`http`**. `CapacitorHttp`는 비활성(로그인 fetch 안정화).
@@ -34,8 +34,8 @@ class ChartFragment : Fragment() {
private fun getFrontendBaseUrl(): String {
val prefs = requireActivity().getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
return prefs.getString("frontend_url", "http://exdev.co.kr/mobile/chart")
?: "http://exdev.co.kr/mobile/chart"
return prefs.getString("frontend_url", "https://stock.exdev.co.kr/mobile/chart")
?: "https://stock.exdev.co.kr/mobile/chart"
}
/** 알림에서 저장한 심볼이 있으면 쿼리 gaSymbol 붙이고 prefs 에서 제거 (프론트 연동 시 사용). */
@@ -39,8 +39,8 @@ class SettingsFragment : Fragment() {
binding.switchDarkMode.isChecked = prefs.getBoolean("dark_mode", false)
// Frontend URL 설정 (차트 페이지)
// 로컬 Docker: http://192.168.219.106/mobile/chart
// 운영: https://exdev.co.kr/mobile/chart
val defaultFrontendUrl = "https://exdev.co.kr/mobile/chart"
// 운영: https://stock.exdev.co.kr/mobile/chart
val defaultFrontendUrl = "https://stock.exdev.co.kr/mobile/chart"
binding.etApiServer.setText(prefs.getString("frontend_url", defaultFrontendUrl))
binding.etApiBackend.setText(prefs.getString("api_base_url", ""))
@@ -24,8 +24,8 @@ object ApiPrefs {
if (!b.endsWith("/api")) b = "$b/api"
return "$b/"
}
val front = p.getString(KEY_FRONTEND, "https://exdev.co.kr/mobile/chart")
?: "https://exdev.co.kr/mobile/chart"
val front = p.getString(KEY_FRONTEND, "https://stock.exdev.co.kr/mobile/chart")
?: "https://stock.exdev.co.kr/mobile/chart"
val uri = Uri.parse(front)
val scheme = uri.scheme ?: "https"
val host = uri.host ?: "exdev.co.kr"
+2 -2
View File
@@ -35,11 +35,11 @@ goldenApp과 동일 프로젝트 **goldenanalysisapp** 서비스 계정 JSON 사
## 확인
```bash
curl http://exdev.co.kr/api/fcm/status
curl https://stock.exdev.co.kr/api/fcm/status
# {"available":true}
# 앱에서 FCM 푸시 ON 후
curl -X POST http://exdev.co.kr/api/fcm/test \
curl -X POST https://stock.exdev.co.kr/api/fcm/test \
-H "x-user-id: 1" \
-H "x-device-id: <앱 device id>"
```
@@ -320,12 +320,12 @@ public class DesktopAppReleaseService {
if (requestBaseUrl != null && !requestBaseUrl.isBlank()) {
return requestBaseUrl.trim();
}
return "http://exdev.co.kr";
return "https://stock.exdev.co.kr";
}
private static String normalizeBase(String publicBaseUrl) {
if (publicBaseUrl == null || publicBaseUrl.isBlank()) {
return "http://exdev.co.kr";
return "https://stock.exdev.co.kr";
}
return publicBaseUrl.replaceAll("/+$", "");
}
@@ -131,12 +131,12 @@ public class MobileAppReleaseService {
if (requestBaseUrl != null && !requestBaseUrl.isBlank()) {
return requestBaseUrl.trim();
}
return "http://exdev.co.kr";
return "https://stock.exdev.co.kr";
}
private static String normalizeBase(String publicBaseUrl) {
if (publicBaseUrl == null || publicBaseUrl.isBlank()) {
return "http://exdev.co.kr";
return "https://stock.exdev.co.kr";
}
return publicBaseUrl.replaceAll("/+$", "");
}
@@ -29,7 +29,7 @@ goldenchart:
desktop-app:
release-dir: ${GC_DESKTOP_APP_RELEASE_DIR:/app/data/desktop-releases}
updates-dir: ${GC_DESKTOP_UPDATES_DIR:/app/data/desktop-updates}
public-base-url: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:http://exdev.co.kr}
public-base-url: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:https://stock.exdev.co.kr}
jenkins:
enabled: ${GC_DESKTOP_JENKINS_ENABLED:true}
# Docker 컨테이너 → 호스트 Jenkins (127.0.0.1:8090 은 컨테이너 자신을 가리킴)
+2 -2
View File
@@ -111,7 +111,7 @@ goldenchart:
release-dir: ${GC_MOBILE_APP_RELEASE_DIR:data/mobile-releases}
version: ${GC_MOBILE_APP_VERSION:}
# QR·APK 다운로드 절대 URL (로컬 개발 시에도 exdev 서버 APK 사용)
public-base-url: ${GC_MOBILE_APP_PUBLIC_BASE_URL:http://exdev.co.kr}
public-base-url: ${GC_MOBILE_APP_PUBLIC_BASE_URL:https://stock.exdev.co.kr}
ios-testflight-url: ${GC_MOBILE_APP_IOS_TESTFLIGHT_URL:}
jenkins:
enabled: ${GC_MOBILE_JENKINS_ENABLED:true}
@@ -123,7 +123,7 @@ goldenchart:
desktop-app:
release-dir: ${GC_DESKTOP_APP_RELEASE_DIR:data/desktop-releases}
updates-dir: ${GC_DESKTOP_UPDATES_DIR:}
public-base-url: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:http://exdev.co.kr}
public-base-url: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:https://stock.exdev.co.kr}
git-bare-dir: ${GC_GIT_BARE_DIR:}
work-tree: ${GC_WORK_TREE:}
jenkins:
+1 -1
View File
@@ -9,4 +9,4 @@ Jenkins 빌드 후 `scripts/publish-desktop-update.sh`가 이 디렉터리와
| `GoldenChart_*-setup.exe` | Windows |
- API: `GET /api/desktop-app/info`
- 다운로드 URL: `http://exdev.co.kr/desktop/updates/<파일명>`
- 다운로드 URL: `https://stock.exdev.co.kr/desktop/updates/<파일명>`
+4 -4
View File
@@ -39,7 +39,7 @@ chmod +x scripts/upload-android-apk.sh
```bash
REMOTE_PROJECT_DIR=/Users/aidev/apps/goldenChart
PUBLIC_URL=http://exdev.co.kr
PUBLIC_URL=https://stock.exdev.co.kr
```
## 서버 파일 위치
@@ -51,8 +51,8 @@ PUBLIC_URL=http://exdev.co.kr
Docker backend가 `/app/data/mobile-releases`로 마운트해 제공합니다.
- API: `GET /api/mobile-app/info`
- 다운로드: `GET /api/mobile-app/download/android` (절대 URL: `http://exdev.co.kr`, HTTPS 443 미사용)
- 설치 페이지: `http://exdev.co.kr/install.html`
- 다운로드: `GET /api/mobile-app/download/android` (절대 URL: `https://stock.exdev.co.kr`, HTTPS 443 미사용)
- 설치 페이지: `https://stock.exdev.co.kr/install.html`
exdev.co.kr은 **HTTP(80)만** 열려 있습니다. QR URL에 `https://` 를 쓰면 휴대폰에서 연결되지 않습니다.
@@ -76,7 +76,7 @@ docker compose up -d backend # 볼륨 마운트 후
모바일 앱은 `app/.env``VITE_API_BASE_URL`**빌드 시** 박힙니다. exdev 배포용:
```env
VITE_API_BASE_URL=http://exdev.co.kr/api
VITE_API_BASE_URL=https://stock.exdev.co.kr/api
```
변경 후 `npm run cap:sync` → APK 재빌드 → 업로드.
+50 -11
View File
@@ -90,8 +90,8 @@ GoldenChart 배포는 **공통 스크립트 `deploy.sh`** 를 중심으로 두
| 서비스 | 컨테이너 | 포트 (기본) | URL |
|--------|---------|------------|-----|
| Frontend (Nginx) | `gc-frontend` | 80, **443** | http://exdev.co.kr/ · https://exdev.co.kr/ |
| Backend (Spring Boot) | `gc-backend` | 8080 | http://exdev.co.kr:8080 (직접) 또는 `/api` (Nginx 경유) |
| Frontend (Nginx) | `gc-frontend` | **80** (443 금지) | http://stock.exdev.co.kr/ · https://stock.exdev.co.kr/ (Caddy 경유) |
| Backend (Spring Boot) | `gc-backend` | 8080 | http://stock.exdev.co.kr:8080 (직접) 또는 `/api` (Nginx 경유) |
| MySQL | `gc-mysql` | 3306 | localhost:3306 (서버 내부) |
브라우저에서는 Nginx가 `/api``backend:8080` 으로 프록시합니다.
@@ -541,7 +541,7 @@ curl -s -o /dev/null -w "Frontend HTTP: %{http_code}\n" http://localhost:80/
# 기대: 200
```
브라우저: http://exdev.co.kr/
브라우저: http://stock.exdev.co.kr/
### 10-3. Backend API
@@ -604,22 +604,33 @@ docker system prune -f
## HTTPS (HTTP + HTTPS 동시 접속)
### exdev.co.kr (운영 — Caddy + gc-frontend)
### exdev.co.kr (운영 — Caddy + stock / chatbot 서브도메인)
이 서버는 **443 포트ex-chatbot Caddy** 사용합니다. GoldenChart HTTPS는 Caddy → `host.docker.internal:80` → **gc-frontend** 경로입니다.
443 포트**ex-chatbot Caddy** 사용. GoldenChart·Chatbot은 **443 직접 바인딩 금지**.
| URL | 경로 |
| URL | 처리 |
|-----|------|
| `http://exdev.co.kr/` | gc-frontend `:80` 직접 |
| `https://exdev.co.kr/` | Caddy `:443` (TLS) → gc-frontend `:80` |
| https://stock.exdev.co.kr | Caddy → gc-frontend `:80` (GoldenChart) |
| https://chatbot.exdev.co.kr/chatbot/* | Caddy → rag-api + Admin |
| https://exdev.co.kr/chatbot/* | → chatbot.exdev.co.kr 301 |
| https://exdev.co.kr/* (그 외) | → stock.exdev.co.kr 301 |
**주의:** `docker-compose.yml` 에 frontend `443:443` 매핑을 넣으면 Caddy 와 충돌해 **frontend 가 기동되지 않습니다** (HTTP·HTTPS 모두 502). frontend 는 **80만** 노출합니다.
Caddyfile: `exAIChatbot2.0/docker/caddy/Caddyfile.exdev`
절차: [docs/exdev-subdomain-setup.md](../docs/exdev-subdomain-setup.md)
```bash
curl -sS -o /dev/null -w "stock %{size_download}B\n" https://stock.exdev.co.kr/
curl -sS -o /dev/null -w "chatbot %{size_download}B\n" https://chatbot.exdev.co.kr/chatbot/health
DESKTOP_UPDATE_BASE_URL=https://stock.exdev.co.kr/desktop/updates ./scripts/verify-desktop-updater-endpoint.sh
```
**주의:** gc-frontend `443:443` 매핑 금지 (Caddy 충돌). frontend는 **80만** 노출.
```bash
# frontend 기동 확인
docker ps --filter name=gc-frontend
curl -s -o /dev/null -w "HTTP:%{http_code}\n" http://exdev.co.kr/
curl -sk -o /dev/null -w "HTTPS:%{http_code}\n" https://exdev.co.kr/
curl -s -o /dev/null -w "HTTP:%{http_code}\n" http://stock.exdev.co.kr/
curl -sk -o /dev/null -w "HTTPS:%{http_code}\n" https://stock.exdev.co.kr/
```
Caddy TLS 인증서: `exAIChatbot2.0/docker/nginx/certs/` (`obtain-cert-exdev.sh`)
@@ -688,6 +699,34 @@ docker compose up -d --no-deps backend
docker compose up -d --no-deps frontend
```
### 12-5a. PC 앱 자동 업데이트 — latest.json 형식 오류 / HTTPS 빈 응답
데스크톱 앱(Tauri)은 **HTTPS** `https://stock.exdev.co.kr/desktop/updates/latest.json` 만 사용합니다.
**증상:** 앱 업데이트 모달에 `latest.json 형식 오류` 또는 JSON parse 실패.
**원인 1 (가장 흔함):** ex-chatbot **Caddy(443)** 가 gc-frontend 로 프록시할 때 **HTTP 200 + 본문 0바이트** 를 반환. nginx(:80) 직접 접속은 정상.
```bash
# 진단 (로컬·서버 공통)
DESKTOP_UPDATE_BASE_URL=https://stock.exdev.co.kr/desktop/updates ./scripts/verify-desktop-updater-endpoint.sh
# 수동 비교
curl -sS -o /dev/null -w "HTTPS size=%{size_download}\n" https://stock.exdev.co.kr/desktop/updates/latest.json
curl -sS -o /dev/null -w "HTTP size=%{size_download}\n" http://stock.exdev.co.kr/desktop/updates/latest.json
curl -sS http://stock.exdev.co.kr/desktop/updates/latest.json | head
```
| 결과 | 의미 |
|------|------|
| HTTPS 0B, HTTP 1KB+ | **Caddy 역프록시** 설정 오류 — `stock.exdev.co.kr` 블록에서 `reverse_proxy host.docker.internal:80``flush_interval -1` 확인 |
| HTTPS·HTTP 모두 0B | `frontend/public/desktop/updates/latest.json` 미생성 — Desktop Jenkins `goldenChart-Desktop-Pipeline` publish 실행 |
| HTTPS OK, platforms `{}` | manifest 는 있으나 서명 bundle 없음 — `publish-desktop-update.sh` 재실행 |
**Caddy 수정 후:** `curl -sS https://stock.exdev.co.kr/desktop/updates/latest.json | jq .version` 로 v0.1.x 확인.
**임시 우회:** PC 프로그램 탭에서 dmg/exe 수동 설치.
### 12-6. deploy-home.sh SSH 실패
```bash
+1 -1
View File
@@ -1 +1 @@
VITE_API_BASE_URL=http://exdev.co.kr/api
VITE_API_BASE_URL=https://stock.exdev.co.kr/api
+1 -1
View File
@@ -8,7 +8,7 @@
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>exdev.co.kr</key>
<key>stock.exdev.co.kr</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
+2 -2
View File
@@ -35,10 +35,10 @@
{
"identifier": "http:default",
"allow": [
{ "url": "http://exdev.co.kr/**" },
{ "url": "https://stock.exdev.co.kr/**" },
{ "url": "http://localhost:8080/**" },
{ "url": "http://127.0.0.1:8080/**" },
{ "url": "https://exdev.co.kr/**" },
{ "url": "https://stock.exdev.co.kr/**" },
{ "url": "https://api.upbit.com/**" }
]
}
+3 -3
View File
@@ -25,7 +25,7 @@
}
],
"security": {
"csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src 'self' http://localhost:8080 http://127.0.0.1:8080 http://exdev.co.kr https://exdev.co.kr ws://exdev.co.kr wss://exdev.co.kr ws://localhost:8080 https://api.upbit.com wss://api.upbit.com https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;"
"csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; connect-src 'self' http://localhost:8080 http://127.0.0.1:8080 https://stock.exdev.co.kr wss://stock.exdev.co.kr ws://localhost:8080 https://api.upbit.com wss://api.upbit.com https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data: blob: https:; font-src 'self' data: https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;"
},
"trayIcon": {
"iconPath": "icons/icon.png",
@@ -44,7 +44,7 @@
"icons/icon.ico"
],
"macOS": {
"exceptionDomain": "exdev.co.kr",
"exceptionDomain": "stock.exdev.co.kr",
"infoPlist": "Info.plist",
"signingIdentity": "-",
"entitlements": "./Entitlements.plist"
@@ -58,7 +58,7 @@
"plugins": {
"updater": {
"endpoints": [
"https://exdev.co.kr/desktop/updates/latest.json"
"https://stock.exdev.co.kr/desktop/updates/latest.json"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDg3MUJCMjIyRkIyNzdFMUUKUldRZWZpZjdJckliaDlMS3pvRHpmQ2lnSFNjaDQrT1pEZTdDZEJ5S2RhOXV5aU1QY3JObEEwaFUK",
"windows": {
+2 -2
View File
@@ -149,8 +149,8 @@ async function formatUpdateError(e: unknown): Promise<string> {
if (!msg.trim()) return '업데이트 확인에 실패했습니다.';
if (/unexpected end of json|eof|empty|content-length.*0|^$/.test(lower) && msg.trim().length < 80) {
return onMac
? '서버 latest.json 이 비어 있습니다. PC 프로그램 탭에서 dmg를 받아 다시 설치해 주세요. (관리자: Desktop Jenkins publish 필요)'
: '서버 latest.json 이 비어 있습니다. PC 프로그램 탭에서 exe를 받아 다시 설치해 주세요. (관리자: Desktop Jenkins publish 필요)';
? '서버 latest.json 이 비어 있습니다. HTTPS(Caddy) 역프록시가 본문 없이 200을 반환하는 경우가 많습니다. 관리자에게 Caddy→gc-frontend:80 설정 점검을 요청하거나, PC 프로그램 탭에서 dmg를 받아 다시 설치해 주세요.'
: '서버 latest.json 이 비어 있습니다. HTTPS(Caddy) 역프록시가 본문 없이 200을 반환하는 경우가 많습니다. 관리자에게 Caddy→gc-frontend:80 설정 점검을 요청하거나, PC 프로그램 탭에서 exe를 받아 다시 설치해 주세요.';
}
if (/could not fetch a valid release json|release not found/.test(lower)) {
return onMac
+1 -1
View File
@@ -47,7 +47,7 @@ export default defineConfig({
define: {
global: 'globalThis',
/** desktop은 항상 exdev 서버 (localhost 금지) */
'import.meta.env.VITE_API_BASE_URL': JSON.stringify('http://exdev.co.kr/api'),
'import.meta.env.VITE_API_BASE_URL': JSON.stringify('https://stock.exdev.co.kr/api'),
__DESKTOP_CLIENT__: 'true',
__DESKTOP_APP_VERSION__: JSON.stringify(readDesktopAppVersion()),
},
+2 -2
View File
@@ -68,10 +68,10 @@ services:
GC_VERIFICATION_UPLOAD_DIR: /app/data/verification-images
GC_MOBILE_APP_RELEASE_DIR: /app/data/mobile-releases
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:-https://stock.exdev.co.kr}
GC_MOBILE_APP_IOS_TESTFLIGHT_URL: ${GC_MOBILE_APP_IOS_TESTFLIGHT_URL:-}
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_APP_PUBLIC_BASE_URL: ${GC_DESKTOP_APP_PUBLIC_BASE_URL:-https://stock.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}
+1 -1
View File
@@ -83,7 +83,7 @@ CI 빌드 시 `latest.json` 버전 기준 **patch 자동 bump** (예: 0.1.3 →
## 4. 앱 기능
- **서버**: `http://exdev.co.kr/api` (웹과 동일 계정·DB)
- **서버**: `https://stock.exdev.co.kr/api` (웹과 동일 계정·DB)
- **위젯**: OS 별도 창 (`WebviewWindow`)
- **알림**: 창 닫기 → 트레이 상주, STOMP + OS 알림
- **업데이트**: 설정 → 일반 → PC 앱 → **업데이트 확인**
+72
View File
@@ -0,0 +1,72 @@
# exdev.co.kr 서브도메인 분리 — stock / chatbot
| 서비스 | URL |
|--------|-----|
| **GoldenChart** | https://stock.exdev.co.kr |
| **Chatbot** | https://chatbot.exdev.co.kr/chatbot/* |
| **Legacy apex** | https://exdev.co.kr → stock 또는 chatbot으로 301 |
## 1. DNS (Gabia 등)
| 호스트 | 타입 | 값 |
|--------|------|-----|
| `stock` | A | 서버 공인 IP |
| `chatbot` | A | 서버 공인 IP |
## 2. SSL 인증서 (exAIChatbot2.0 서버)
```bash
cd /path/to/exAIChatbot2.0
# env: STOCK_DOMAIN, CHATBOT_DOMAIN (env.exdev 참고)
bash scripts/exdev/obtain-cert-exdev.sh # exdev + stock + chatbot SAN
bash scripts/exdev/start-https-caddy.sh
```
Caddyfile: `docker/caddy/Caddyfile.exdev`
## 3. GoldenChart
서버 `.env`:
```env
GC_DESKTOP_APP_PUBLIC_BASE_URL=https://stock.exdev.co.kr
GC_MOBILE_APP_PUBLIC_BASE_URL=https://stock.exdev.co.kr
```
Desktop updater / Jenkins:
```bash
export DESKTOP_UPDATE_BASE_URL=https://stock.exdev.co.kr/desktop/updates
```
검증:
```bash
curl -sS https://stock.exdev.co.kr/ | head
DESKTOP_UPDATE_BASE_URL=https://stock.exdev.co.kr/desktop/updates ./scripts/verify-desktop-updater-endpoint.sh
```
## 4. Chatbot
`.env` (env.exdev):
```env
PUBLIC_BASE_URL=https://chatbot.exdev.co.kr
CHATBOT_DOMAIN=chatbot.exdev.co.kr
```
카카오 스킬 URL 예:
- Q&A: `https://chatbot.exdev.co.kr/chatbot/kakao/skill`
- 챗봇홈: `https://chatbot.exdev.co.kr/chatbot/app/`
검증:
```bash
curl -sS https://chatbot.exdev.co.kr/chatbot/health
curl -sS https://chatbot.exdev.co.kr/chatbot/app/ | head
```
## 5. SSH / git remote
서버 호스트명 `exdev.co.kr`·`REMOTE_HOST=exdev.co.kr`**변경하지 않음** (배포·SSH용).
+1 -1
View File
@@ -49,7 +49,7 @@ git push main
|------|------|
| `GC_MOBILE_APP_VERSION` | API·앱 업데이트 비교용 버전 |
| `GC_MOBILE_JENKINS_*` | Jenkins mobile job |
| `GC_MOBILE_APP_PUBLIC_BASE_URL` | 다운로드 URL base (기본 `http://exdev.co.kr`) |
| `GC_MOBILE_APP_PUBLIC_BASE_URL` | 다운로드 URL base (기본 `https://stock.exdev.co.kr`) |
## 수동 빌드
+1 -1
View File
@@ -5,4 +5,4 @@
VITE_API_BASE_URL=/api
# 로컬 dev: /api/mobile-app → exdev (QR·APK 다운로드 URL). vite.config.ts 기본값과 동일
# VITE_MOBILE_APP_PROXY_TARGET=http://exdev.co.kr
# VITE_MOBILE_APP_PROXY_TARGET=https://stock.exdev.co.kr
+1 -1
View File
@@ -82,7 +82,7 @@ export default defineConfig({
proxy: {
// ── 모바일 APK 정보/다운로드 → exdev (로컬 QR·PC 다운로드가 exdev APK 사용) ──
'/api/mobile-app': {
target: process.env.VITE_MOBILE_APP_PROXY_TARGET || 'http://exdev.co.kr',
target: process.env.VITE_MOBILE_APP_PROXY_TARGET || 'https://stock.exdev.co.kr',
changeOrigin: true,
secure: true,
},
+2 -2
View File
@@ -12,10 +12,10 @@ import { getRuntimePlatform } from '../platform.js';
export type StrategyFlowLayoutStore = Record<string, unknown>;
/** 배포 APK·exdev (모바일 APK — http) */
export const PRODUCTION_API_BASE = 'http://exdev.co.kr/api';
export const PRODUCTION_API_BASE = 'https://stock.exdev.co.kr/api';
/** Tauri desktop — exdev HTTP (HTTPS /api는 Caddy에서 빈 응답) */
export const DESKTOP_API_BASE = 'http://exdev.co.kr/api';
export const DESKTOP_API_BASE = 'https://stock.exdev.co.kr/api';
function viteEnv(): Record<string, string> | undefined {
return typeof import.meta !== 'undefined'
+1 -1
View File
@@ -5,7 +5,7 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORK_TREE="${WORK_TREE:-$ROOT}"
export WORK_TREE
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
CONF="$WORK_TREE/desktop/src-tauri/tauri.conf.json"
CARGO="$WORK_TREE/desktop/src-tauri/Cargo.toml"
+1 -1
View File
@@ -4,7 +4,7 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export WORK_TREE="${WORK_TREE:-$ROOT}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
export DESKTOP_BUMP_IGNORE_BUILD_INFO=1
"$ROOT/scripts/bump-desktop-version-from-release.sh"
+1 -1
View File
@@ -10,7 +10,7 @@ UPDATES_DIR="${WORK_TREE}/frontend/public/desktop/updates"
BUILD_INFO="${WORK_TREE}/dist-desktop/build-info.json"
RELEASE_DIR="${GC_DESKTOP_APP_RELEASE_DIR:-$WORK_TREE/data/desktop-releases}"
PUBLISHED_LATEST_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}/latest.json}"
PUBLISHED_LATEST_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}/latest.json}"
REMOTE_PUB_VER=""
if REMOTE_JSON="$(curl -sf --connect-timeout 5 --max-time 12 "$PUBLISHED_LATEST_URL" 2>/dev/null)"; then
REMOTE_PUB_VER="$(printf '%s' "$REMOTE_JSON" | node -e "
+1 -1
View File
@@ -15,4 +15,4 @@ APK_REMOTE_SUBDIR=data/mobile-releases
APK_REMOTE_NAME=goldenchart-android.apk
# 업로드 후 API 확인 URL
PUBLIC_URL=http://exdev.co.kr
PUBLIC_URL=https://stock.exdev.co.kr
+1 -1
View File
@@ -4,4 +4,4 @@
# REMOTE_PROJECT_DIR=/Users/aidev/apps/goldenChart
# REMOTE_HOST=exdev.co.kr
# REMOTE_USER=aidev
# PUBLIC_URL=https://exdev.co.kr
# PUBLIC_URL=https://stock.exdev.co.kr
+9 -1
View File
@@ -85,7 +85,7 @@ UPDATES_DIR="$WORK_TREE/frontend/public/desktop/updates"
if [[ -f "$ENSURE" && -d "$UPDATES_DIR" ]]; then
DESKTOP_VER="$(node -p "require('$WORK_TREE/desktop/src-tauri/tauri.conf.json').version" 2>/dev/null || echo '0.1.0')"
if ! node "$ENSURE" --dir "$UPDATES_DIR" --version "$DESKTOP_VER" \
--base-url "${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}" 2>/dev/null; then
--base-url "${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}" 2>/dev/null; then
warn "latest.json platforms 비어 있음 — Desktop Jenkins 파이프라인 publish 필요"
fi
LATEST="$UPDATES_DIR/latest.json"
@@ -169,6 +169,14 @@ case "$SERVICE_NAME" in
;;
esac
VERIFY_UPDATER="$WORK_TREE/scripts/verify-desktop-updater-endpoint.sh"
if [[ -x "$VERIFY_UPDATER" ]]; then
info "[Phase 4b] Desktop updater latest.json (HTTPS)"
if ! WORK_TREE="$WORK_TREE" "$VERIFY_UPDATER"; then
warn "Desktop 자동 업데이트 엔드포인트 이상 — 배포는 완료됐으나 Caddy HTTPS 또는 Jenkins desktop publish 점검 필요"
fi
fi
log "======================================================"
log " 배포 완료: $(date '+%Y-%m-%d %H:%M:%S')"
log " 커밋: $(git --git-dir="$GIT_DIR" log --oneline -1 2>/dev/null || echo N/A)"
+1 -1
View File
@@ -16,7 +16,7 @@ function arg(name, fallback) {
const dir = path.resolve(arg('--dir', 'frontend/public/desktop/updates'));
const version = arg('--version', '0.1.0');
const rawBaseUrl = arg('--base-url', 'https://exdev.co.kr/desktop/updates');
const rawBaseUrl = arg('--base-url', 'https://stock.exdev.co.kr/desktop/updates');
const baseUrl = rawBaseUrl.replace(/\/$/, '').replace(/^http:\/\//i, 'https://');
const notes = arg('--notes', `GoldenChart Desktop ${version}`);
const latestPath = path.join(dir, 'latest.json');
+79
View File
@@ -0,0 +1,79 @@
# exdev.co.kr — GoldenChart + Chatbot HTTPS 공존 (Caddy v2 예시)
#
# 원칙:
# - 443 포트는 Caddy만 사용 (gc-frontend·chatbot은 443 바인딩 금지)
# - GoldenChart: 호스트/ Docker gc-frontend :80
# - Chatbot: 별도 컨테이너 내부 포트 (아래 CHATBOT_UPSTREAM 수정)
#
# 설치 위치 (ex-chatbot 프로젝트): docker/nginx/Caddyfile 또는 Caddyfile
# 적용 후: docker compose restart caddy (프로젝트명에 맞게 조정)
#
# 검증:
# curl -sS -o /dev/null -w "GC HTTPS %{size_download}\n" https://exdev.co.kr/
# curl -sS -o /dev/null -w "CB HTTPS %{size_download}\n" https://exdev.co.kr/chatbot/
# ./scripts/verify-desktop-updater-endpoint.sh
{
# HTTP/2·HTTP/3 — 본문 버퍼링 이슈 완화
servers {
protocols h1 h2 h3
}
}
(exdev_tls) {
# Let's Encrypt — ex-chatbot obtain-cert-exdev.sh 로 발급한 인증서 경로에 맞게 수정
tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
}
# ── Chatbot upstream (실제 포트·호스트명은 환경에 맞게) ─────────────────────
# Docker: chatbot 컨테이너명:내부포트 / 호스트: host.docker.internal:PORT
(chatbot_upstream) {
reverse_proxy host.docker.internal:8081 {
flush_interval -1
transport http {
versions 1.1 2
}
header_up Host {host}
header_up X-Forwarded-Proto https
header_up X-Forwarded-Prefix /chatbot
}
}
# ── GoldenChart (gc-frontend nginx :80) ─────────────────────────────────────
(goldenchart_upstream) {
reverse_proxy host.docker.internal:80 {
flush_interval -1
transport http {
versions 1.1 2
}
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto https
}
}
exdev.co.kr {
import exdev_tls
# 1) Chatbot — /chatbot/* (경로 유지; 앱이 /chatbot base path 를 기대할 때)
@chatbot path /chatbot /chatbot/*
handle @chatbot {
import chatbot_upstream
}
# 2) GoldenChart — 나머지 전부 (SPA, /api 는 nginx→backend, /desktop/updates 등)
handle {
import goldenchart_upstream
}
}
# ── 대안: 서브도메인 분리 (경로 충돌이 없어 운영이 단순함) ─────────────────────
# chat.exdev.co.kr {
# import exdev_tls
# import chatbot_upstream
# }
# chart.exdev.co.kr {
# import exdev_tls
# import goldenchart_upstream
# }
# }
+6
View File
@@ -0,0 +1,6 @@
# GoldenChart + Chatbot 서브도메인 (ex-chatbot Caddy에 통합됨)
# 실제 운영 Caddyfile: exAIChatbot2.0/docker/caddy/Caddyfile.exdev
#
# stock.exdev.co.kr → gc-frontend :80
# chatbot.exdev.co.kr → rag-api + Admin
# exdev.co.kr → /chatbot/* 리다이렉트 + 나머지 stock 리다이렉트
+2 -2
View File
@@ -6,7 +6,7 @@
* node scripts/generate-desktop-latest-json.mjs \
* --version 0.1.0 \
* --dir dist-desktop/updates \
* --base-url https://exdev.co.kr/desktop/updates \
* --base-url https://stock.exdev.co.kr/desktop/updates \
* --notes "Release notes"
*/
import fs from 'node:fs';
@@ -19,7 +19,7 @@ function arg(name, fallback) {
const version = arg('--version', '0.1.0');
const dir = path.resolve(arg('--dir', 'dist-desktop/updates'));
const rawBaseUrl = arg('--base-url', 'https://exdev.co.kr/desktop/updates');
const rawBaseUrl = arg('--base-url', 'https://stock.exdev.co.kr/desktop/updates');
const baseUrl = rawBaseUrl.replace(/\/$/, '').replace(/^http:\/\//i, 'https://');
const notes = arg('--notes', `GoldenChart Desktop ${version}`);
+2 -2
View File
@@ -58,7 +58,7 @@ fi
export WORK_TREE
export BUILD_NUMBER="${BUILD_NUMBER:-}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
GIT_VER="$(node -p "require('$WORK_TREE/desktop/src-tauri/tauri.conf.json').version" 2>/dev/null || echo '?')"
log "Desktop git tauri.conf: v${GIT_VER}"
@@ -71,7 +71,7 @@ log "Built version: v${BUILT_VER}"
WORK_TREE="$WORK_TREE" \
ARTIFACT_DIR="$WORK_TREE/dist-desktop" \
DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}" \
DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}" \
TAURI_SIGNER_KEY="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" \
DESKTOP_RELEASE_NOTES="${DESKTOP_RELEASE_NOTES:-Build ${BUILD_NUMBER:-local} v${BUILT_VER}}" \
"$WORK_TREE/scripts/publish-desktop-update.sh"
+2 -2
View File
@@ -27,9 +27,9 @@ conf.bundle.createUpdaterArtifacts = enable;
conf.plugins = conf.plugins || {};
conf.plugins.updater = conf.plugins.updater || {};
conf.plugins.updater.endpoints = (conf.plugins.updater.endpoints || [])
.map((u) => String(u).replace(/^http:\\/\\/exdev\\.co\\.kr\\//, 'https://exdev.co.kr/'));
.map((u) => String(u).replace(/^http:\\/\\/exdev\\.co\\.kr\\//, 'https://stock.exdev.co.kr/'));
if (!conf.plugins.updater.endpoints.length) {
conf.plugins.updater.endpoints = ['https://exdev.co.kr/desktop/updates/latest.json'];
conf.plugins.updater.endpoints = ['https://stock.exdev.co.kr/desktop/updates/latest.json'];
}
if (!conf.plugins.updater.pubkey) {
conf.plugins.updater.pubkey = 'REPLACE_WITH_TAURI_SIGNER_PUBKEY';
+1 -1
View File
@@ -8,7 +8,7 @@ ARTIFACT_DIR="${ARTIFACT_DIR:-$WORK_TREE/dist-desktop}"
UPDATES_SRC="${UPDATES_SRC:-$ARTIFACT_DIR/updates}"
UPDATES_DIR="${UPDATES_DIR:-$WORK_TREE/frontend/public/desktop/updates}"
WEB_STATIC_DIR="${WEB_STATIC_DIR:-$UPDATES_DIR}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
BASE_URL="${BASE_URL/http:\/\//https:\/\/}"
DESKTOP_RELEASE_DIR="${GC_DESKTOP_APP_RELEASE_DIR:-$WORK_TREE/data/desktop-releases}"
KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
+1 -1
View File
@@ -5,7 +5,7 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export PATH="/opt/homebrew/bin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
UPDATES_DIR="${UPDATES_DIR:-${WORK_TREE:-$ROOT}/frontend/public/desktop/updates}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
BASE_URL="${BASE_URL/http:\/\//https:\/\/}"
if [[ ! -d "$UPDATES_DIR" ]]; then
+1 -1
View File
@@ -54,7 +54,7 @@ export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/us
export WORK_TREE="${WORK_TREE}"
export GIT_DIR="${GIT_DIR}"
export BUILD_NUMBER="\${BUILD_NUMBER}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
export TAURI_SIGNER_KEY="\${TAURI_SIGNER_KEY:-\$HOME/.tauri/goldenchart.key}"
export DESKTOP_DEPLOY_WEB=0
"${WORK_TREE}/scripts/jenkins-desktop-pipeline.sh"
+1 -1
View File
@@ -62,6 +62,6 @@ export GC_MOBILE_JENKINS_TOKEN="${DESKTOP_TOKEN:-}"
log "API 확인"
sleep 4
curl -fsSL http://127.0.0.1:8080/api/mobile-app/info 2>/dev/null | python3 -m json.tool 2>/dev/null || \
curl -fsSL http://exdev.co.kr/api/mobile-app/info | python3 -m json.tool 2>/dev/null || true
curl -fsSL https://stock.exdev.co.kr/api/mobile-app/info | python3 -m json.tool 2>/dev/null || true
log "완료 (Android APK · iPad는 향후 TestFlight)"
+7 -3
View File
@@ -3,7 +3,7 @@
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
FAIL=0
ok() { echo "[OK] $*"; }
@@ -43,8 +43,12 @@ fi
if curl -sf "${BASE_URL}/latest.json" >/dev/null 2>&1; then
REMOTE="$(curl -sf "${BASE_URL}/latest.json" 2>/dev/null || true)"
if [[ -z "$REMOTE" || "${#REMOTE}" -lt 8 ]]; then
echo "[FAIL] remote latest.json empty (0-byte)"
exit 1
HTTP_BASE="$(printf '%s' "$BASE_URL" | sed 's|^https://|http://|')"
HTTP_REMOTE="$(curl -sf "${HTTP_BASE}/latest.json" 2>/dev/null || true)"
if [[ -n "$HTTP_REMOTE" && "${#HTTP_REMOTE}" -ge 8 ]]; then
fail "remote HTTPS latest.json empty — HTTP는 정상 (${#HTTP_REMOTE}B). Caddy→gc-frontend:80 역프록시 점검"
fi
fail "remote latest.json empty (0-byte)"
fi
node -e "JSON.parse(process.argv[1])" "$REMOTE" && ok "remote latest.json ${BASE_URL}/latest.json"
else
+2 -2
View File
@@ -26,7 +26,7 @@ REMOTE_USER="${REMOTE_USER:-aidev}"
REMOTE_PORT="${REMOTE_PORT:-22}"
APK_REMOTE_SUBDIR="${APK_REMOTE_SUBDIR:-data/mobile-releases}"
APK_REMOTE_NAME="${APK_REMOTE_NAME:-goldenchart-android.apk}"
PUBLIC_URL="${PUBLIC_URL:-http://exdev.co.kr}"
PUBLIC_URL="${PUBLIC_URL:-https://stock.exdev.co.kr}"
LOCAL_APK="${LOCAL_APK:-$ROOT/data/mobile-releases/$APK_REMOTE_NAME}"
DO_BUILD=false
@@ -169,7 +169,7 @@ build_apk_locally() {
"$ROOT/scripts/ensure-android-google-services.sh"
if [[ ! -f "$ROOT/app/.env" ]] || ! grep -q 'VITE_API_BASE_URL=http' "$ROOT/app/.env" 2>/dev/null; then
echo -e "${YELLOW}⚠ app/.env 에 VITE_API_BASE_URL=http://exdev.co.kr/api 확인 (HTTPS 아님)${NC}"
echo -e "${YELLOW}⚠ app/.env 에 VITE_API_BASE_URL=https://stock.exdev.co.kr/api 확인 (HTTPS 아님)${NC}"
fi
echo -e "${CYAN}[빌드] npm run cap:sync${NC}"
+6 -1
View File
@@ -3,7 +3,7 @@
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://exdev.co.kr/desktop/updates}"
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
LATEST_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${BASE_URL}/latest.json}"
UPDATES_DIR="${UPDATES_DIR:-$ROOT/frontend/public/desktop/updates}"
RELEASE_DIR="${GC_DESKTOP_APP_RELEASE_DIR:-$ROOT/data/desktop-releases}"
@@ -19,6 +19,11 @@ REMOTE_PLATFORMS="0"
if REMOTE_JSON="$(curl -sf --connect-timeout 5 --max-time 12 "$LATEST_URL" 2>/dev/null)"; then
if [[ -z "$REMOTE_JSON" || "${#REMOTE_JSON}" -lt 8 ]]; then
log "remote latest.json → EMPTY (0-byte — updater JSON parse 실패 원인)"
HTTP_URL="$(printf '%s' "$LATEST_URL" | sed 's|^https://|http://|')"
HTTP_JSON="$(curl -sf --connect-timeout 5 --max-time 12 "$HTTP_URL" 2>/dev/null || true)"
if [[ -n "$HTTP_JSON" && "${#HTTP_JSON}" -ge 8 ]]; then
log "remote HTTP latest → OK (${#HTTP_JSON}B) — HTTPS(Caddy) 역프록시가 빈 200을 반환 중일 가능성"
fi
else
read -r REMOTE_VER REMOTE_PLATFORMS <<< "$(printf '%s' "$REMOTE_JSON" | node -e "
let d=''; process.stdin.on('data',c=>d+=c);
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Desktop updater latest.json — HTTPS·HTTP 무결성 (Caddy 역프록시 빈 응답 감지)
set -euo pipefail
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-https://stock.exdev.co.kr/desktop/updates}"
BASE_URL="${BASE_URL%/}"
HTTPS_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${BASE_URL}/latest.json}"
HTTP_URL="$(printf '%s' "$HTTPS_URL" | sed 's|^https://|http://|')"
log() { echo "[verify-updater-endpoint] $*"; }
warn() { log "WARN: $*"; }
fail() { log "FAIL: $*"; exit 1; }
fetch_body() {
local url="$1"
curl -sf --connect-timeout 8 --max-time 15 "$url" 2>/dev/null || true
}
parse_manifest() {
local body="$1"
printf '%s' "$body" | node -e "
let d=''; process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>{
try {
const j=JSON.parse(d);
const n=Object.keys(j.platforms||{}).length;
console.log((j.version||'')+' '+n);
} catch { console.log('PARSE_ERR 0'); }
});
" 2>/dev/null || echo "PARSE_ERR 0"
}
log "HTTPS → $HTTPS_URL"
log "HTTP → $HTTP_URL"
HTTPS_BODY="$(fetch_body "$HTTPS_URL")"
HTTP_BODY="$(fetch_body "$HTTP_URL")"
HTTPS_SIZE="${#HTTPS_BODY}"
HTTP_SIZE="${#HTTP_BODY}"
log "HTTPS size=${HTTPS_SIZE}B HTTP size=${HTTP_SIZE}B"
if [[ "$HTTPS_SIZE" -lt 8 && "$HTTP_SIZE" -ge 8 ]]; then
read -r HTTP_VER HTTP_PLAT <<< "$(parse_manifest "$HTTP_BODY")"
fail "HTTPS latest.json 이 비어 있습니다 (${HTTPS_SIZE}B). HTTP는 v${HTTP_VER} (${HTTP_SIZE}B, platforms=${HTTP_PLAT}) — Caddy(443) → gc-frontend:80 역프록시를 점검하세요."
fi
if [[ "$HTTPS_SIZE" -lt 8 && "$HTTP_SIZE" -lt 8 ]]; then
fail "latest.json 이 HTTPS·HTTP 모두 비어 있습니다 — Desktop Jenkins publish 후 scripts/publish-desktop-update.sh 실행 필요"
fi
BODY="$HTTPS_BODY"
[[ "$HTTPS_SIZE" -lt 8 ]] && BODY="$HTTP_BODY"
read -r VER PLAT <<< "$(parse_manifest "$BODY")"
if [[ "$VER" == "PARSE_ERR" || -z "$VER" ]]; then
fail "latest.json JSON 파싱 실패 (HTTPS ${HTTPS_SIZE}B / HTTP ${HTTP_SIZE}B)"
fi
if [[ "${PLAT:-0}" -eq 0 ]]; then
warn "latest.json v${VER} — platforms 비어 있음 (서명된 updater bundle 없음). 자동 업데이트는 실패할 수 있습니다."
exit 2
fi
log "OK v${VER} platforms=${PLAT} (HTTPS ${HTTPS_SIZE}B)"
exit 0