205 lines
7.7 KiB
Bash
Executable File
205 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# GoldenChart Desktop — updater 서명 + latest.json + 웹 static 배포
|
|
set -euo pipefail
|
|
|
|
SCRIPT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
WORK_TREE="${WORK_TREE:-$SCRIPT_ROOT}"
|
|
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="${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}"
|
|
DESKTOP_DIR="$WORK_TREE/desktop"
|
|
CONF="$DESKTOP_DIR/src-tauri/tauri.conf.json"
|
|
BUILD_INFO="$ARTIFACT_DIR/build-info.json"
|
|
|
|
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
|
|
# shellcheck disable=SC1091
|
|
[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
|
|
# shellcheck disable=SC1091
|
|
source "$WORK_TREE/scripts/desktop-installer-version.sh"
|
|
|
|
log() { echo "[publish-desktop] $*"; }
|
|
|
|
# build-info 가 단일 버전 소스 (tauri.conf 는 git checkout 으로 구버전일 수 있음)
|
|
VERSION="$(node -p "require('$CONF').version" 2>/dev/null || echo '0.1.0')"
|
|
if [[ -f "$BUILD_INFO" ]]; then
|
|
VERSION="$(node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')}catch{}" "$BUILD_INFO" 2>/dev/null || echo "$VERSION")"
|
|
[[ -z "$VERSION" ]] && VERSION="$(node -p "require('$CONF').version" 2>/dev/null || echo '0.1.0')"
|
|
fi
|
|
NOTES="${DESKTOP_RELEASE_NOTES:-GoldenChart Desktop $VERSION}"
|
|
|
|
# 동일 버전 재배포 방지 (updater 가 '이미 최신' 으로 보이는 원인)
|
|
PUBLISHED_LATEST_URL="${DESKTOP_PUBLISHED_LATEST_URL:-${BASE_URL}/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 "
|
|
let d=''; process.stdin.on('data',c=>d+=c);
|
|
process.stdin.on('end',()=>{ try { console.log(JSON.parse(d).version||''); } catch { console.log(''); } });
|
|
" 2>/dev/null || true)"
|
|
fi
|
|
if [[ -n "$REMOTE_PUB_VER" ]]; then
|
|
if ! node -e "
|
|
const parse = v => String(v||'0').replace(/^v/i,'').split('.').map(n=>parseInt(n,10)||0);
|
|
const a=parse(process.argv[1]), b=parse(process.argv[2]);
|
|
for(let i=0;i<3;i++){const d=(a[i]||0)-(b[i]||0);if(d){process.exit(d>0?0:1)}}
|
|
process.exit(1);
|
|
" "$VERSION" "$REMOTE_PUB_VER"; then
|
|
log "ERROR: publish v${VERSION} <= remote v${REMOTE_PUB_VER} — bump 가 실행되지 않았습니다." >&2
|
|
log " build-desktop.sh 가 bump-desktop-version-before-build.sh 를 호출하는지 확인" >&2
|
|
exit 1
|
|
fi
|
|
log "remote v${REMOTE_PUB_VER} → publish v${VERSION} OK"
|
|
fi
|
|
|
|
log "=== Publish Desktop v$VERSION ==="
|
|
log "Work tree: $WORK_TREE"
|
|
log "Updates src: $UPDATES_SRC"
|
|
log "Web static: $WEB_STATIC_DIR"
|
|
log "Release dir: $DESKTOP_RELEASE_DIR"
|
|
|
|
mkdir -p "$WEB_STATIC_DIR"
|
|
|
|
prune_old_installers() {
|
|
local dir="$1"
|
|
[[ -d "$dir" ]] || return 0
|
|
find "$dir" -maxdepth 1 -type f \( \
|
|
-name 'GoldenChart_*.dmg' -o \
|
|
-name 'GoldenChart_*setup.exe' -o \
|
|
-name 'GoldenChart_*-setup.exe' \
|
|
\) -delete 2>/dev/null || true
|
|
}
|
|
|
|
# 설치 파일(dist-desktop/*.dmg, *setup.exe) 복사 — nginx static + backend API 디렉터리
|
|
MAC_VER="" WIN_VER=""
|
|
INSTALLER_COUNT=0
|
|
|
|
if [[ -d "$ARTIFACT_DIR" ]]; then
|
|
mkdir -p "$DESKTOP_RELEASE_DIR"
|
|
prune_old_installers "$DESKTOP_RELEASE_DIR"
|
|
prune_old_installers "$WEB_STATIC_DIR"
|
|
|
|
while IFS= read -r f; do
|
|
[[ -z "$f" ]] && continue
|
|
base="$(basename "$f")"
|
|
if ! desktop_installer_matches_version "$base" "$VERSION"; then
|
|
log "[WARN] skip installer (version mismatch): $base (expected v${VERSION})"
|
|
continue
|
|
fi
|
|
cp -f "$f" "$WEB_STATIC_DIR/"
|
|
cp -f "$f" "$DESKTOP_RELEASE_DIR/"
|
|
log "Installer: $base"
|
|
INSTALLER_COUNT=$((INSTALLER_COUNT + 1))
|
|
case "$base" in
|
|
*.dmg) MAC_VER="$(desktop_extract_version_from_name "$base")" ;;
|
|
*setup.exe) WIN_VER="$(desktop_extract_version_from_name "$base")" ;;
|
|
esac
|
|
done < <(find "$ARTIFACT_DIR" -maxdepth 1 -type f \( -name '*.dmg' -o -name '*setup.exe' \) 2>/dev/null)
|
|
|
|
if [[ "$INSTALLER_COUNT" -eq 0 ]]; then
|
|
log "ERROR: v${VERSION} 설치 파일(dmg/exe) 없음 — latest.json 만 갱신하면 UI 버전이 어긋납니다." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "$MAC_VER" && "$MAC_VER" != "$VERSION" ]]; then
|
|
log "ERROR: macOS installer v${MAC_VER} != publish v${VERSION}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$WIN_VER" && "$WIN_VER" != "$VERSION" ]]; then
|
|
log "ERROR: Windows installer v${WIN_VER} != publish v${VERSION}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$MAC_VER" && -n "$WIN_VER" && "$MAC_VER" != "$WIN_VER" ]]; then
|
|
log "ERROR: macOS v${MAC_VER} != Windows v${WIN_VER}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f "$BUILD_INFO" ]]; then
|
|
cp -f "$BUILD_INFO" "$DESKTOP_RELEASE_DIR/build-info.json"
|
|
fi
|
|
fi
|
|
|
|
PUBLISH_UPDATES="$WEB_STATIC_DIR"
|
|
mkdir -p "$PUBLISH_UPDATES"
|
|
|
|
if [[ -d "$UPDATES_SRC" ]]; then
|
|
cp -f "$UPDATES_SRC"/* "$PUBLISH_UPDATES/" 2>/dev/null || true
|
|
fi
|
|
|
|
# updater 번들 서명
|
|
if [[ -f "$KEY_PATH" ]]; then
|
|
shopt -s nullglob
|
|
for bundle in "$PUBLISH_UPDATES"/*.tar.gz "$PUBLISH_UPDATES"/*-setup.exe "$PUBLISH_UPDATES"/*.nsis.zip "$PUBLISH_UPDATES"/*.zip; do
|
|
[[ -f "$bundle" ]] || continue
|
|
[[ "$bundle" == *.sig ]] && continue
|
|
log "Signing $(basename "$bundle")"
|
|
unset TAURI_SIGNING_PRIVATE_KEY TAURI_SIGNING_PRIVATE_KEY_PASSWORD 2>/dev/null || true
|
|
(cd "$DESKTOP_DIR" && npx tauri signer sign -f "$KEY_PATH" -p "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD-}" "$bundle")
|
|
done
|
|
shopt -u nullglob
|
|
else
|
|
log "[WARN] TAURI_SIGNER_KEY 없음 ($KEY_PATH) — updater 서명 생략"
|
|
log " ./scripts/setup-desktop-updater-keys.sh 실행 후 Jenkins Credential 등록"
|
|
fi
|
|
|
|
# build-info 복사 (빌드 시 기록된 버전 유지 — tauri.conf로 덮어쓰지 않음)
|
|
if [[ -f "$BUILD_INFO" ]]; then
|
|
cp -f "$BUILD_INFO" "$PUBLISH_UPDATES/build-info.json"
|
|
fi
|
|
|
|
# latest.json — 설치 파일과 동일 semver
|
|
set +e
|
|
node "$WORK_TREE/scripts/generate-desktop-latest-json.mjs" \
|
|
--version "$VERSION" \
|
|
--dir "$PUBLISH_UPDATES" \
|
|
--base-url "$BASE_URL" \
|
|
--notes "$NOTES"
|
|
GEN_RC=$?
|
|
set -e
|
|
|
|
if [[ "$GEN_RC" -eq 2 ]]; then
|
|
log "[WARN] signed updater bundle 없음 — latest.json 은 v${VERSION} build-info 기준 유지"
|
|
if [[ -f "$BUILD_INFO" ]]; then
|
|
node -e "
|
|
const fs=require('fs'); const p=process.argv[1]; const v=process.argv[2]; const u=process.argv[3]; const n=process.argv[4];
|
|
const latestPath=p+'/latest.json';
|
|
let cur={version:v,notes:n,pub_date:new Date().toISOString(),platforms:{}};
|
|
if(fs.existsSync(latestPath)){try{cur=JSON.parse(fs.readFileSync(latestPath,'utf8'));cur.version=v;cur.notes=n;}catch{}}
|
|
fs.writeFileSync(latestPath, JSON.stringify(cur,null,2)+'\n');
|
|
console.log('latest.json version →', v);
|
|
" "$PUBLISH_UPDATES" "$VERSION" "$BASE_URL" "$NOTES"
|
|
fi
|
|
elif [[ "$GEN_RC" -ne 0 ]]; then
|
|
exit "$GEN_RC"
|
|
fi
|
|
|
|
# 빈·손상 latest.json 방지
|
|
node "$WORK_TREE/scripts/ensure-desktop-latest-json.mjs" \
|
|
--version "$VERSION" \
|
|
--dir "$PUBLISH_UPDATES" \
|
|
--base-url "$BASE_URL" \
|
|
--notes "$NOTES" || {
|
|
log "ERROR: latest.json 무결성 검증 실패" >&2
|
|
exit 1
|
|
}
|
|
|
|
LATEST_SIZE="$(wc -c < "$PUBLISH_UPDATES/latest.json" | tr -d ' ')"
|
|
if [[ "${LATEST_SIZE:-0}" -lt 8 ]]; then
|
|
log "ERROR: latest.json is empty (${LATEST_SIZE} bytes)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
log "Published v${VERSION}:"
|
|
ls -la "$PUBLISH_UPDATES"
|
|
|
|
log "nginx: /desktop/updates/ → frontend/public/desktop/updates (Docker rebuild 시 반영)"
|
|
|
|
if [[ -x "$WORK_TREE/scripts/update-desktop-source-state.sh" ]]; then
|
|
WORK_TREE="$WORK_TREE" "$WORK_TREE/scripts/update-desktop-source-state.sh" || true
|
|
fi
|
|
|
|
log "Done."
|