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