diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json
index df1d1be..52a9556 100644
--- a/desktop/src-tauri/tauri.conf.json
+++ b/desktop/src-tauri/tauri.conf.json
@@ -50,7 +50,7 @@
"plugins": {
"updater": {
"endpoints": [
- "https://exdev.co.kr/desktop/updates/latest.json"
+ "http://exdev.co.kr/desktop/updates/latest.json"
],
"pubkey": "REPLACE_WITH_TAURI_SIGNER_PUBKEY"
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 2fe703b..10c943c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -112,5 +112,8 @@ services:
condition: service_healthy
ports:
- "${FRONTEND_PORT:-80}:80"
+ volumes:
+ # Desktop updater latest.json·설치 파일 — Jenkins publish + nginx reload
+ - ./frontend/public/desktop/updates:/usr/share/nginx/html/desktop/updates:ro
networks:
- goldenchart-net
diff --git a/docs/desktop-pilot.md b/docs/desktop-pilot.md
index 9ea350c..28aa964 100644
--- a/docs/desktop-pilot.md
+++ b/docs/desktop-pilot.md
@@ -32,10 +32,24 @@ $HOME/.cargo/bin
| 항목 | 값 |
|------|-----|
| SCM | `file:///Volumes/ADATA/git/goldenChart.git` main |
-| 스케줄 | 30분마다 SCM poll (또는 수동) |
+| **트리거** | **`git push main` 성공 시 post-receive → 자동 Jenkins 빌드** |
| 빌드 | `scripts/jenkins-desktop-pipeline.sh` |
| Artifact | `dist-desktop/**` |
+`main` push 흐름:
+
+```
+git push exdev main
+ → post-receive → deploy.sh (웹·백엔드 Docker)
+ → trigger-desktop-jenkins-build.sh (Jenkins 큐)
+ → macOS dmg + Windows exe + updater 번들
+ → publish-desktop-update.sh → latest.json
+ → nginx reload (frontend 볼륨 마운트)
+ → Desktop 앱 「업데이트」 버튼으로 자동 업데이트
+```
+
+CI 빌드 시 `latest.json` 버전 기준 **patch 자동 bump** (예: 0.1.3 → 0.1.4).
+
환경 변수 (선택):
| 변수 | 설명 |
diff --git a/scripts/bump-desktop-version-from-release.sh b/scripts/bump-desktop-version-from-release.sh
new file mode 100755
index 0000000..255a68d
--- /dev/null
+++ b/scripts/bump-desktop-version-from-release.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# published latest.json 기준 patch 버전 bump → tauri.conf.json (CI 자동 업데이트용)
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+WORK_TREE="${WORK_TREE:-$ROOT}"
+CONF="${WORK_TREE}/desktop/src-tauri/tauri.conf.json"
+LATEST="${WORK_TREE}/frontend/public/desktop/updates/latest.json"
+
+node -e "
+const fs = require('fs');
+const confPath = process.argv[1];
+const latestPath = process.argv[2];
+const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
+let base = conf.version || '0.1.0';
+if (fs.existsSync(latestPath)) {
+ try {
+ const pub = JSON.parse(fs.readFileSync(latestPath, 'utf8'));
+ if (pub.version) base = pub.version;
+ } catch { /* ignore */ }
+}
+const parts = base.split('.').map(n => parseInt(n, 10) || 0);
+while (parts.length < 3) parts.push(0);
+parts[2] += 1;
+const nv = parts.join('.');
+conf.version = nv;
+fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
+console.log(nv);
+" "$CONF" "$LATEST"
diff --git a/scripts/jenkins-desktop-pipeline.sh b/scripts/jenkins-desktop-pipeline.sh
index 19ed48a..7f31c49 100755
--- a/scripts/jenkins-desktop-pipeline.sh
+++ b/scripts/jenkins-desktop-pipeline.sh
@@ -42,7 +42,14 @@ fi
BUILD_ARGS=(--all)
if [[ -n "${DESKTOP_BUILD_MAC_ONLY:-}" ]]; then BUILD_ARGS=(--mac); fi
if [[ -n "${DESKTOP_BUILD_WIN_ONLY:-}" ]]; then BUILD_ARGS=(--win); fi
-if [[ -n "${BUMP_VERSION:-}" ]]; then BUILD_ARGS+=(--bump-version "$BUMP_VERSION"); fi
+if [[ -n "${BUMP_VERSION:-}" ]]; then
+ BUILD_ARGS+=(--bump-version "$BUMP_VERSION")
+elif [[ -n "${BUILD_NUMBER:-}" && "${DESKTOP_BUMP_ON_CI:-1}" == "1" ]]; then
+ log "CI patch bump (latest.json 기준)..."
+ chmod +x "$ROOT/scripts/bump-desktop-version-from-release.sh"
+ NEW_VER="$("$ROOT/scripts/bump-desktop-version-from-release.sh")"
+ log "Desktop version → $NEW_VER"
+fi
"$ROOT/scripts/build-desktop.sh" "${BUILD_ARGS[@]}"
@@ -53,6 +60,11 @@ TAURI_SIGNER_KEY="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" \
DESKTOP_RELEASE_NOTES="${DESKTOP_RELEASE_NOTES:-Build ${BUILD_NUMBER:-local}}" \
"$ROOT/scripts/publish-desktop-update.sh"
+# latest.json·설치 파일 nginx 즉시 반영 (볼륨 마운트)
+if [[ -x "$ROOT/scripts/reload-frontend-desktop-static.sh" ]]; then
+ WORK_TREE="$WORK_TREE" "$ROOT/scripts/reload-frontend-desktop-static.sh" || true
+fi
+
# Jenkins artifact archive 경로
if [[ -d "$ROOT/dist-desktop" ]]; then
log "Artifacts ready: $ROOT/dist-desktop"
diff --git a/scripts/post-receive b/scripts/post-receive
index 4c300ed..2467cf8 100755
--- a/scripts/post-receive
+++ b/scripts/post-receive
@@ -4,6 +4,7 @@
set -euo pipefail
GIT_DIR="/Volumes/ADATA/git/goldenChart.git"
+WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
DEPLOY_SCRIPT="/Volumes/ADATA/git/deploy.sh"
SUMMARY_SCRIPT="/Volumes/ADATA/git/git-push-summary.sh"
DEPLOY_LOG="/Volumes/ADATA/logs/deploy/deploy.log"
@@ -41,6 +42,20 @@ while read -r OLDREV NEWREV REFNAME; do
exit "$EXIT_CODE"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG 배포 완료"
+
+ # Desktop(macOS·Windows) Jenkins 빌드 — git push 성공 후 자동 (웹 배포와 독립)
+ WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
+ TRIGGER="${WORK_TREE}/scripts/trigger-desktop-jenkins-build.sh"
+ if [ -x "$TRIGGER" ]; then
+ echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG Desktop Jenkins 빌드 트리거"
+ if "$TRIGGER" >> "$DEPLOY_LOG" 2>&1; then
+ echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG Desktop 빌드 큐 등록 완료"
+ else
+ echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG [WARN] Desktop Jenkins 트리거 실패 (웹 배포는 성공)"
+ fi
+ else
+ echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG [WARN] trigger-desktop-jenkins-build.sh 없음: $TRIGGER"
+ fi
else
echo "$(date '+%Y-%m-%d %H:%M:%S') $HOOK_TAG '$BRANCH_NAME' — 배포 스킵 (main만)"
fi
diff --git a/scripts/prepare-tauri-build.sh b/scripts/prepare-tauri-build.sh
index 41b94e7..08b71b9 100644
--- a/scripts/prepare-tauri-build.sh
+++ b/scripts/prepare-tauri-build.sh
@@ -25,7 +25,7 @@ conf.bundle.createUpdaterArtifacts = enable;
conf.plugins = conf.plugins || {};
conf.plugins.updater = conf.plugins.updater || {};
if (!conf.plugins.updater.endpoints?.length) {
- conf.plugins.updater.endpoints = ['https://exdev.co.kr/desktop/updates/latest.json'];
+ conf.plugins.updater.endpoints = ['http://exdev.co.kr/desktop/updates/latest.json'];
}
if (!conf.plugins.updater.pubkey) {
conf.plugins.updater.pubkey = 'REPLACE_WITH_TAURI_SIGNER_PUBKEY';
diff --git a/scripts/reload-frontend-desktop-static.sh b/scripts/reload-frontend-desktop-static.sh
new file mode 100755
index 0000000..8748aa6
--- /dev/null
+++ b/scripts/reload-frontend-desktop-static.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# desktop/updates static 반영 — nginx reload (볼륨 마운트 시 재빌드 불필요)
+set -euo pipefail
+
+WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
+COMPOSE="${DOCKER_COMPOSE_FILE:-$WORK_TREE/docker-compose.yml}"
+CONTAINER="${FRONTEND_CONTAINER:-gc-frontend}"
+
+if [[ ! -f "$COMPOSE" ]]; then
+ echo "[reload-desktop-static] skip — no compose: $COMPOSE"
+ exit 0
+fi
+
+if docker ps --format '{{.Names}}' | grep -qx "$CONTAINER"; then
+ docker exec "$CONTAINER" nginx -s reload 2>/dev/null \
+ && echo "[reload-desktop-static] nginx reload OK ($CONTAINER)" \
+ && exit 0
+fi
+
+if docker compose -f "$COMPOSE" ps frontend 2>/dev/null | grep -q Up; then
+ docker compose -f "$COMPOSE" exec -T frontend nginx -s reload \
+ && echo "[reload-desktop-static] nginx reload OK (compose)" \
+ && exit 0
+fi
+
+echo "[reload-desktop-static] frontend container not running — skip"
diff --git a/scripts/server-install-desktop-jenkins.sh b/scripts/server-install-desktop-jenkins.sh
index cbc8fd8..81710e3 100755
--- a/scripts/server-install-desktop-jenkins.sh
+++ b/scripts/server-install-desktop-jenkins.sh
@@ -42,11 +42,7 @@ cat > "${JENKINS_HOME}/jobs/${JENKINS_JOB}/config.xml" << JENKINS_XML
false
false
false
-
-
- H/30 * * * *
-
-
+
false
@@ -57,8 +53,10 @@ export WORK_TREE="${WORK_TREE}"
export GIT_DIR="${GIT_DIR}"
export BUILD_NUMBER="\${BUILD_NUMBER}"
export TAURI_SIGNER_KEY="\${TAURI_SIGNER_KEY:-\$HOME/.tauri/goldenchart.key}"
-export DESKTOP_DEPLOY_WEB="\${DESKTOP_DEPLOY_WEB:-1}"
-"${REPO_ROOT}/scripts/jenkins-desktop-pipeline.sh"
+export DESKTOP_BUMP_ON_CI=1
+export DESKTOP_DEPLOY_WEB=0
+export DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}"
+"${WORK_TREE}/scripts/jenkins-desktop-pipeline.sh"
diff --git a/scripts/trigger-desktop-jenkins-build.sh b/scripts/trigger-desktop-jenkins-build.sh
new file mode 100755
index 0000000..d34a34f
--- /dev/null
+++ b/scripts/trigger-desktop-jenkins-build.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# Git push(main) 후 Jenkins goldenChart-Desktop-Pipeline 트리거
+set -euo pipefail
+
+JENKINS_URL="${JENKINS_URL:-${GC_DESKTOP_JENKINS_URL:-http://127.0.0.1:8090}}"
+JENKINS_JOB="${JENKINS_JOB:-${GC_DESKTOP_JENKINS_JOB:-goldenChart-Desktop-Pipeline}}"
+JENKINS_USER="${JENKINS_USER:-${GC_DESKTOP_JENKINS_USER:-}}"
+JENKINS_TOKEN="${JENKINS_TOKEN:-${GC_DESKTOP_JENKINS_TOKEN:-}}"
+LOG_TAG="[trigger-desktop]"
+
+log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_TAG $*"; }
+
+JENKINS_URL="${JENKINS_URL%/}"
+JOB_API="${JENKINS_URL}/job/${JENKINS_JOB// /%20}"
+
+curl_get() {
+ if [[ -n "$JENKINS_USER" && -n "$JENKINS_TOKEN" ]]; then
+ curl -sf -u "${JENKINS_USER}:${JENKINS_TOKEN}" "$@"
+ else
+ curl -sf "$@"
+ fi
+}
+
+curl_post() {
+ if [[ -n "$JENKINS_USER" && -n "$JENKINS_TOKEN" ]]; then
+ curl -sf -X POST -u "${JENKINS_USER}:${JENKINS_TOKEN}" "$@"
+ else
+ curl -sf -X POST "$@"
+ fi
+}
+
+log "Jenkins desktop build — $JENKINS_JOB @ $JENKINS_URL"
+
+if ! curl -sf -o /dev/null "${JENKINS_URL}/login" 2>/dev/null; then
+ log "[ERROR] Jenkins unreachable: $JENKINS_URL"
+ exit 1
+fi
+
+if curl_get "${JOB_API}/lastBuild/api/json?tree=building" 2>/dev/null | grep -q '"building":true'; then
+ log "[SKIP] 이미 빌드 진행 중"
+ exit 0
+fi
+
+CRUMB_HDR=()
+CRUMB_JSON="$(curl_get "${JENKINS_URL}/crumbIssuer/api/json" 2>/dev/null || true)"
+if [[ -n "$CRUMB_JSON" ]]; then
+ FIELD="$(node -e "try{console.log(JSON.parse(process.argv[1]).crumbRequestField||'')}catch{}" "$CRUMB_JSON" 2>/dev/null || true)"
+ VAL="$(node -e "try{console.log(JSON.parse(process.argv[1]).crumb||'')}catch{}" "$CRUMB_JSON" 2>/dev/null || true)"
+ if [[ -n "$FIELD" && -n "$VAL" ]]; then
+ CRUMB_HDR=(-H "${FIELD}: ${VAL}")
+ fi
+fi
+
+if curl_post "${CRUMB_HDR[@]}" -H "Content-Type: application/x-www-form-urlencoded" "${JOB_API}/build"; then
+ log "빌드 큐 등록 완료"
+ exit 0
+fi
+
+log "[ERROR] Jenkins build trigger failed"
+exit 1