desktop 앱 적용
This commit is contained in:
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env bash
|
||||
# GoldenChart Desktop — macOS Jenkins 빌드 (macOS dmg + Windows NSIS cross-build)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/scripts/desktop-dev-path.sh"
|
||||
# shellcheck disable=SC1091
|
||||
[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
|
||||
|
||||
BUILD_MAC=1
|
||||
BUILD_WIN=1
|
||||
BUMP_VERSION="${BUMP_VERSION:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mac) BUILD_WIN=0; shift ;;
|
||||
--win) BUILD_MAC=0; shift ;;
|
||||
--all) BUILD_MAC=1; BUILD_WIN=1; shift ;;
|
||||
--bump-version) BUMP_VERSION="$2"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "=== GoldenChart Desktop Build ==="
|
||||
echo "Root: $ROOT"
|
||||
echo "Git: $(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo 'n/a')"
|
||||
|
||||
need_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Missing: $1 — run ./scripts/install-desktop-build-deps.sh"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
need_cmd node
|
||||
need_cmd npm
|
||||
need_cmd cargo
|
||||
need_cmd rustc
|
||||
|
||||
if [[ "$BUILD_WIN" -eq 1 ]]; then
|
||||
need_cmd cargo-xwin
|
||||
rustup target add x86_64-pc-windows-msvc 2>/dev/null || true
|
||||
fi
|
||||
|
||||
CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
|
||||
if [[ -n "$BUMP_VERSION" ]]; then
|
||||
node -e "
|
||||
const fs=require('fs'); const p=process.argv[1]; const b=process.argv[2];
|
||||
const c=JSON.parse(fs.readFileSync(p,'utf8'));
|
||||
const [a,x,y]=c.version.split('.').map(Number);
|
||||
let nv=c.version;
|
||||
if(b==='patch') nv=[a,x,y+1].join('.');
|
||||
else if(b==='minor') nv=[a,x+1,0].join('.');
|
||||
else if(b==='major') nv=[a+1,0,0].join('.');
|
||||
else nv=b;
|
||||
c.version=nv; fs.writeFileSync(p, JSON.stringify(c,null,2)+'\n');
|
||||
console.log('version', nv);
|
||||
" "$CONF" "$BUMP_VERSION"
|
||||
fi
|
||||
|
||||
VERSION="$(node -p "require('$CONF').version")"
|
||||
echo "Version: $VERSION"
|
||||
|
||||
if [[ -f "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
|
||||
chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||
fi
|
||||
chmod +x "$ROOT/scripts/prepare-tauri-build.sh" 2>/dev/null || true
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/scripts/prepare-tauri-build.sh"
|
||||
|
||||
npm install
|
||||
|
||||
echo "--- Vite build (desktop) ---"
|
||||
npm run build -w @goldenchart/desktop
|
||||
|
||||
DESKTOP_DIR="$ROOT/desktop"
|
||||
TAURI_TARGET="$DESKTOP_DIR/src-tauri/target"
|
||||
ARTIFACT_DIR="$ROOT/dist-desktop"
|
||||
UPDATES_DIR="$ARTIFACT_DIR/updates"
|
||||
rm -rf "$ARTIFACT_DIR"
|
||||
mkdir -p "$ARTIFACT_DIR" "$UPDATES_DIR"
|
||||
|
||||
collect_updater_artifacts() {
|
||||
find "$TAURI_TARGET" \( \
|
||||
-name '*.app.tar.gz' -o \
|
||||
-name '*.tar.gz' -path '*/bundle/macos/*' -o \
|
||||
-name '*.nsis.zip' -o \
|
||||
-name '*-setup.nsis.zip' \
|
||||
\) -type f 2>/dev/null | while read -r f; do
|
||||
cp -f "$f" "$UPDATES_DIR/"
|
||||
echo " updater bundle: $(basename "$f")"
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "$BUILD_MAC" -eq 1 ]]; then
|
||||
echo "--- Tauri build (macOS) ---"
|
||||
(cd "$DESKTOP_DIR" && npm run tauri:build:mac)
|
||||
find "$TAURI_TARGET/release/bundle" -name '*.dmg' -exec cp -f {} "$ARTIFACT_DIR/" \; 2>/dev/null || true
|
||||
collect_updater_artifacts
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_WIN" -eq 1 ]]; then
|
||||
echo "--- Tauri build (Windows NSIS cross) ---"
|
||||
(cd "$DESKTOP_DIR" && npm run tauri:build:win)
|
||||
find "$TAURI_TARGET" -path '*x86_64-pc-windows-msvc*' -name '*-setup.exe' -exec cp -f {} "$ARTIFACT_DIR/" \; 2>/dev/null || true
|
||||
collect_updater_artifacts
|
||||
fi
|
||||
|
||||
GIT_SHA="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo '')"
|
||||
GIT_SHORT="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo '')"
|
||||
cat > "$ARTIFACT_DIR/build-info.json" <<EOF
|
||||
{
|
||||
"version": "$VERSION",
|
||||
"gitSha": "$GIT_SHA",
|
||||
"gitShort": "$GIT_SHORT",
|
||||
"builtAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"buildNumber": "${BUILD_NUMBER:-local}"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "--- Artifacts ($ARTIFACT_DIR) ---"
|
||||
ls -la "$ARTIFACT_DIR" || true
|
||||
echo "--- Updater bundles ($UPDATES_DIR) ---"
|
||||
ls -la "$UPDATES_DIR" 2>/dev/null || true
|
||||
echo "Done."
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# GoldenChart desktop 빌드용 PATH — source scripts/desktop-dev-path.sh
|
||||
prepend_path() {
|
||||
case ":${PATH}:" in
|
||||
*":$1:"*) ;;
|
||||
*) export PATH="$1:$PATH" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
prepend_path "$HOME/.cargo/bin"
|
||||
[[ -x "$HOME/homebrew/bin/brew" ]] && eval "$("$HOME/homebrew/bin/brew" shellenv)"
|
||||
[[ -x /opt/homebrew/bin/brew ]] && eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||
for llvm in /opt/homebrew/opt/llvm/bin "$HOME/homebrew/opt/llvm/bin"; do
|
||||
[[ -d "$llvm" ]] && prepend_path "$llvm"
|
||||
done
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* dist-desktop/updates → latest.json (Tauri updater v2)
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/generate-desktop-latest-json.mjs \
|
||||
* --version 0.1.0 \
|
||||
* --dir dist-desktop/updates \
|
||||
* --base-url http://exdev.co.kr/desktop/updates \
|
||||
* --notes "Release notes"
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
function arg(name, fallback) {
|
||||
const i = process.argv.indexOf(name);
|
||||
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
|
||||
const version = arg('--version', '0.1.0');
|
||||
const dir = path.resolve(arg('--dir', 'dist-desktop/updates'));
|
||||
const baseUrl = (arg('--base-url', 'http://exdev.co.kr/desktop/updates')).replace(/\/$/, '');
|
||||
const notes = arg('--notes', `GoldenChart Desktop ${version}`);
|
||||
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.error('Directory not found:', dir);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
/** @type {Record<string, { url: string; signature: string }>} */
|
||||
const platforms = {};
|
||||
|
||||
function addPlatform(platformKey, bundleName) {
|
||||
const bundlePath = path.join(dir, bundleName);
|
||||
if (!fs.existsSync(bundlePath)) return;
|
||||
const sigPath = `${bundlePath}.sig`;
|
||||
let signature = '';
|
||||
if (fs.existsSync(sigPath)) {
|
||||
signature = fs.readFileSync(sigPath, 'utf8').trim();
|
||||
} else {
|
||||
console.warn(`[warn] missing signature: ${sigPath}`);
|
||||
return;
|
||||
}
|
||||
platforms[platformKey] = {
|
||||
url: `${baseUrl}/${encodeURIComponent(bundleName)}`,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
for (const f of files) {
|
||||
if (f.endsWith('.sig')) continue;
|
||||
const lower = f.toLowerCase();
|
||||
if (lower.includes('aarch64') && (lower.endsWith('.tar.gz') || lower.endsWith('.app.tar.gz'))) {
|
||||
addPlatform('darwin-aarch64', f);
|
||||
} else if ((lower.includes('x64') || lower.includes('x86_64')) && lower.endsWith('.tar.gz')) {
|
||||
addPlatform('darwin-x86_64', f);
|
||||
} else if (lower.includes('aarch64') && lower.endsWith('.app.tar.gz')) {
|
||||
addPlatform('darwin-aarch64', f);
|
||||
} else if (lower.endsWith('.nsis.zip') || (lower.includes('setup') && lower.endsWith('.zip'))) {
|
||||
addPlatform('windows-x86_64', f);
|
||||
} else if (lower.endsWith('.app.tar.gz')) {
|
||||
// universal mac fallback
|
||||
if (!platforms['darwin-aarch64']) addPlatform('darwin-aarch64', f);
|
||||
}
|
||||
}
|
||||
|
||||
const manifest = {
|
||||
version,
|
||||
notes,
|
||||
pub_date: new Date().toISOString(),
|
||||
platforms,
|
||||
};
|
||||
|
||||
const outPath = path.join(dir, 'latest.json');
|
||||
fs.writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
console.log('Wrote', outPath);
|
||||
console.log('Platforms:', Object.keys(platforms).join(', ') || '(none — sign updater bundles first)');
|
||||
|
||||
if (Object.keys(platforms).length === 0) {
|
||||
process.exit(2);
|
||||
}
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# macOS Jenkins / 개발 Mac — GoldenChart Desktop 빌드 의존성 설치
|
||||
set -euo pipefail
|
||||
|
||||
MAC_ONLY=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mac-only) MAC_ONLY=1; shift ;;
|
||||
*) echo "Unknown arg: $1 (supported: --mac-only)"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() { echo "[install-desktop-deps] $*"; }
|
||||
|
||||
if [[ "$(uname -s)" != "Darwin" ]]; then
|
||||
log "이 스크립트는 macOS용입니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Homebrew / Rust PATH (표준 prefix + 사용자 prefix)
|
||||
prepend_path() {
|
||||
case ":${PATH}:" in
|
||||
*":$1:"*) ;;
|
||||
*) export PATH="$1:$PATH" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
for prefix in /opt/homebrew "$HOME/homebrew" /usr/local; do
|
||||
[[ -x "$prefix/bin/brew" ]] && prepend_path "$prefix/bin" && prepend_path "$prefix/sbin"
|
||||
[[ -d "$prefix/opt/llvm/bin" ]] && prepend_path "$prefix/opt/llvm/bin"
|
||||
done
|
||||
prepend_path "$HOME/.cargo/bin"
|
||||
|
||||
install_homebrew_user() {
|
||||
local prefix="$HOME/homebrew"
|
||||
if [[ -x "$prefix/bin/brew" ]]; then
|
||||
return 0
|
||||
fi
|
||||
log "Homebrew 사용자 설치 ($prefix) — sudo 불필요"
|
||||
mkdir -p "$prefix"
|
||||
curl -fsSL https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C "$prefix"
|
||||
prepend_path "$prefix/bin"
|
||||
prepend_path "$prefix/sbin"
|
||||
}
|
||||
|
||||
install_homebrew_system() {
|
||||
if [[ -x /opt/homebrew/bin/brew ]] || [[ -x /usr/local/bin/brew ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "${NONINTERACTIVE:-}" == "1" ]] && ! sudo -n true 2>/dev/null; then
|
||||
log "sudo 없음 — $HOME/homebrew 로 설치합니다."
|
||||
install_homebrew_user
|
||||
return 0
|
||||
fi
|
||||
log "Homebrew 시스템 설치 (/opt/homebrew)..."
|
||||
NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
}
|
||||
|
||||
ensure_homebrew() {
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
install_homebrew_system
|
||||
if ! command -v brew >/dev/null 2>&1; then
|
||||
install_homebrew_user
|
||||
fi
|
||||
if ! command -v brew >/dev/null 2>&1; then
|
||||
log "Homebrew 설치 실패. https://brew.sh 수동 설치 후 재실행."
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1091
|
||||
eval "$(brew shellenv)"
|
||||
}
|
||||
|
||||
ensure_rust() {
|
||||
if command -v rustc >/dev/null 2>&1; then
|
||||
log "Rust 이미 설치됨: $(rustc --version)"
|
||||
return 0
|
||||
fi
|
||||
log "Rust 설치 (rustup)..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
|
||||
# shellcheck disable=SC1091
|
||||
source "$HOME/.cargo/env"
|
||||
}
|
||||
|
||||
setup_shell_profile() {
|
||||
local marker="# GoldenChart desktop build PATH"
|
||||
local profile="${ZDOTDIR:-$HOME}/.zprofile"
|
||||
if [[ -f "$profile" ]] && grep -qF "$marker" "$profile" 2>/dev/null; then
|
||||
log "shell profile 이미 설정됨: $profile"
|
||||
return 0
|
||||
fi
|
||||
log "shell profile에 PATH 추가: $profile"
|
||||
{
|
||||
echo ""
|
||||
echo "$marker"
|
||||
echo 'export PATH="$HOME/.cargo/bin:$PATH"'
|
||||
if [[ -x "$HOME/homebrew/bin/brew" ]]; then
|
||||
echo 'eval "$("$HOME/homebrew/bin/brew" shellenv)"'
|
||||
elif [[ -x /opt/homebrew/bin/brew ]]; then
|
||||
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"'
|
||||
fi
|
||||
echo 'export PATH="/opt/homebrew/opt/llvm/bin:$HOME/homebrew/opt/llvm/bin:$PATH"'
|
||||
} >> "$profile"
|
||||
}
|
||||
|
||||
ensure_homebrew
|
||||
setup_shell_profile
|
||||
|
||||
if [[ "$MAC_ONLY" -eq 1 ]]; then
|
||||
log "macOS 전용 모드 — NSIS/LLVM/cargo-xwin 생략"
|
||||
else
|
||||
log "Homebrew 패키지 (NSIS, LLVM)..."
|
||||
brew install nsis llvm 2>/dev/null || brew upgrade nsis llvm 2>/dev/null || true
|
||||
fi
|
||||
|
||||
ensure_rust
|
||||
# shellcheck disable=SC1091
|
||||
[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
|
||||
|
||||
if [[ "$MAC_ONLY" -eq 0 ]]; then
|
||||
log "Windows 크로스 컴파일 타겟..."
|
||||
rustup target add x86_64-pc-windows-msvc 2>/dev/null || true
|
||||
|
||||
if ! command -v cargo-xwin >/dev/null 2>&1; then
|
||||
log "cargo-xwin 설치 (시간 소요)..."
|
||||
cargo install cargo-xwin --locked
|
||||
else
|
||||
log "cargo-xwin 이미 설치됨"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "완료. 검증:"
|
||||
node --version
|
||||
npm --version
|
||||
rustc --version
|
||||
cargo --version
|
||||
command -v makensis && makensis -VERSION || log "[INFO] makensis — Windows 빌드 시 필요"
|
||||
command -v cargo-xwin && cargo-xwin --version || log "[INFO] cargo-xwin — Windows 빌드 시 필요"
|
||||
log "새 터미널을 열거나: source ~/.zprofile"
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# @deprecated — scripts/jenkins-desktop-pipeline.sh + scripts/server-install-desktop-jenkins.sh 사용
|
||||
exec "$(dirname "$0")/jenkins-desktop-pipeline.sh" "$@"
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# Jenkins goldenChart-Desktop-Pipeline — 전체 빌드·배포 진입점
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
|
||||
export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
|
||||
for prefix in "$HOME/homebrew" /opt/homebrew; do
|
||||
[[ -x "$prefix/bin/brew" ]] && eval "$("$prefix/bin/brew" shellenv)"
|
||||
done
|
||||
# shellcheck disable=SC1091
|
||||
[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
|
||||
|
||||
log() { echo "[desktop-pipeline $(date '+%H:%M:%S')] $*"; }
|
||||
|
||||
log "=== goldenChart Desktop Pipeline #${BUILD_NUMBER:-local} ==="
|
||||
log "WORK_TREE=$WORK_TREE"
|
||||
|
||||
cd "$WORK_TREE"
|
||||
|
||||
# Git 동기화 (Jenkins SCM checkout 후에도 file:// bare 와 맞춤)
|
||||
if [[ -d "${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}" ]]; then
|
||||
git --git-dir="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}" \
|
||||
--work-tree="$WORK_TREE" checkout -f main 2>/dev/null || true
|
||||
fi
|
||||
|
||||
chmod +x "$ROOT/scripts/"*.sh 2>/dev/null || true
|
||||
|
||||
# 의존성 (최초 1회 — 이미 있으면 빠르게 통과)
|
||||
if ! command -v cargo-xwin >/dev/null 2>&1 || ! command -v rustc >/dev/null 2>&1; then
|
||||
log "Desktop build deps 설치..."
|
||||
"$ROOT/scripts/install-desktop-build-deps.sh"
|
||||
fi
|
||||
|
||||
# updater pubkey (repo에 desktop/updater.pub 있으면 patch만)
|
||||
if [[ -f "$ROOT/desktop/updater.pub" ]]; then
|
||||
"$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||
elif [[ -f "${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}" ]]; then
|
||||
"$ROOT/scripts/setup-desktop-updater-keys.sh"
|
||||
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
|
||||
|
||||
"$ROOT/scripts/build-desktop.sh" "${BUILD_ARGS[@]}"
|
||||
|
||||
WORK_TREE="$WORK_TREE" \
|
||||
ARTIFACT_DIR="$ROOT/dist-desktop" \
|
||||
DESKTOP_UPDATE_BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://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}}" \
|
||||
"$ROOT/scripts/publish-desktop-update.sh"
|
||||
|
||||
# Jenkins artifact archive 경로
|
||||
if [[ -d "$ROOT/dist-desktop" ]]; then
|
||||
log "Artifacts ready: $ROOT/dist-desktop"
|
||||
fi
|
||||
|
||||
# 웹 frontend Docker 재배포 (latest.json static 포함) — 선택
|
||||
if [[ "${DESKTOP_DEPLOY_WEB:-0}" == "1" ]] && [[ -x "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}" ]]; then
|
||||
log "Trigger web deploy (frontend only)..."
|
||||
SERVICE_NAME=frontend "${DEPLOY_SCRIPT:-/Volumes/ADATA/git/deploy.sh}"
|
||||
fi
|
||||
|
||||
log "Pipeline complete."
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
|
||||
CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
|
||||
|
||||
if [[ ! -f "$PUB_PATH" ]]; then
|
||||
echo "[patch-updater-pubkey] skip — no $PUB_PATH (run setup-desktop-updater-keys.sh)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PUBKEY="$(grep -v '^[[:space:]]*#' "$PUB_PATH" | grep -v '^[[:space:]]*$' | tr -d '\n\r' | head -c 500)"
|
||||
|
||||
if [[ -z "$PUBKEY" || "$PUBKEY" == "REPLACE_WITH_TAURI_SIGNER_PUBKEY" ]]; then
|
||||
echo "[patch-updater-pubkey] skip — invalid or placeholder pubkey in $PUB_PATH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CONF="$CONF" PUBKEY="$PUBKEY" node -e "
|
||||
const fs = require('fs');
|
||||
const confPath = process.env.CONF;
|
||||
const pubkey = process.env.PUBKEY;
|
||||
const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
|
||||
conf.plugins = conf.plugins || {};
|
||||
conf.plugins.updater = conf.plugins.updater || {};
|
||||
conf.plugins.updater.pubkey = pubkey;
|
||||
fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
|
||||
console.log('[patch-updater-pubkey] updated', confPath);
|
||||
"
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# tauri.conf.json — updater artifacts / pubkey (키 있을 때만 활성화)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONF="$ROOT/desktop/src-tauri/tauri.conf.json"
|
||||
KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
|
||||
PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
|
||||
|
||||
enable="false"
|
||||
if [[ -f "$KEY_PATH" && -f "$PUB_PATH" ]]; then
|
||||
enable="true"
|
||||
if [[ -x "$ROOT/scripts/patch-tauri-updater-pubkey.sh" ]]; then
|
||||
"$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const confPath = process.argv[1];
|
||||
const enable = process.argv[2] === 'true';
|
||||
const conf = JSON.parse(fs.readFileSync(confPath, 'utf8'));
|
||||
conf.bundle = conf.bundle || {};
|
||||
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'];
|
||||
}
|
||||
if (!conf.plugins.updater.pubkey) {
|
||||
conf.plugins.updater.pubkey = 'REPLACE_WITH_TAURI_SIGNER_PUBKEY';
|
||||
}
|
||||
fs.writeFileSync(confPath, JSON.stringify(conf, null, 2) + '\n');
|
||||
console.log('[prepare-tauri-build] createUpdaterArtifacts=' + enable);
|
||||
" "$CONF" "$enable"
|
||||
|
||||
if [[ "$enable" == "true" ]]; then
|
||||
export TAURI_SIGNING_PRIVATE_KEY="$(tr -d '\n\r' < "$KEY_PATH")"
|
||||
echo "[prepare-tauri-build] updater signing enabled"
|
||||
else
|
||||
unset TAURI_SIGNING_PRIVATE_KEY 2>/dev/null || true
|
||||
echo "[prepare-tauri-build] updater signing skipped (no key)"
|
||||
fi
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# GoldenChart Desktop — updater 서명 + latest.json + 웹 static 배포
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT/dist-desktop}"
|
||||
UPDATES_SRC="${UPDATES_SRC:-$ARTIFACT_DIR/updates}"
|
||||
UPDATES_DIR="${UPDATES_DIR:-${WORK_TREE:-$ROOT}/frontend/public/desktop/updates}"
|
||||
WEB_STATIC_DIR="${WEB_STATIC_DIR:-$UPDATES_DIR}"
|
||||
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}"
|
||||
KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
|
||||
DESKTOP_DIR="$ROOT/desktop"
|
||||
CONF="$DESKTOP_DIR/src-tauri/tauri.conf.json"
|
||||
VERSION="$(node -p "require('$CONF').version")"
|
||||
NOTES="${DESKTOP_RELEASE_NOTES:-GoldenChart Desktop $VERSION}"
|
||||
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"
|
||||
|
||||
log() { echo "[publish-desktop] $*"; }
|
||||
|
||||
log "=== Publish Desktop v$VERSION ==="
|
||||
log "Updates src: $UPDATES_SRC"
|
||||
log "Web static: $WEB_STATIC_DIR"
|
||||
|
||||
mkdir -p "$WEB_STATIC_DIR"
|
||||
|
||||
# 설치 파일(dist-desktop/*.dmg, *setup.exe) 복사
|
||||
if [[ -d "$ARTIFACT_DIR" ]]; then
|
||||
find "$ARTIFACT_DIR" -maxdepth 1 -type f \( -name '*.dmg' -o -name '*setup.exe' \) -exec cp -f {} "$WEB_STATIC_DIR/" \;
|
||||
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"/*.nsis.zip "$PUBLISH_UPDATES"/*.zip; do
|
||||
[[ -f "$bundle" ]] || continue
|
||||
[[ "$bundle" == *.sig ]] && continue
|
||||
log "Signing $(basename "$bundle")"
|
||||
(cd "$DESKTOP_DIR" && npx tauri signer sign -f "$KEY_PATH" "$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 복사
|
||||
if [[ -f "$BUILD_INFO" ]]; then
|
||||
cp -f "$BUILD_INFO" "$PUBLISH_UPDATES/build-info.json"
|
||||
fi
|
||||
|
||||
# latest.json
|
||||
set +e
|
||||
node "$ROOT/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 없음 — placeholder latest.json 작성"
|
||||
cat > "$PUBLISH_UPDATES/latest.json" <<EOF
|
||||
{
|
||||
"version": "$VERSION",
|
||||
"notes": "$NOTES",
|
||||
"pub_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"platforms": {}
|
||||
}
|
||||
EOF
|
||||
elif [[ "$GEN_RC" -ne 0 ]]; then
|
||||
exit "$GEN_RC"
|
||||
fi
|
||||
|
||||
log "Published:"
|
||||
ls -la "$PUBLISH_UPDATES"
|
||||
|
||||
log "nginx: /desktop/updates/ → frontend/public/desktop/updates (Docker rebuild 시 반영)"
|
||||
log "Done."
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# exdev Jenkins — goldenChart-Desktop-Pipeline Job 생성
|
||||
set -euo pipefail
|
||||
|
||||
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:${PATH:-/usr/bin:/bin}"
|
||||
|
||||
JENKINS_HOME="${JENKINS_HOME:-$HOME/.jenkins}"
|
||||
JENKINS_JOB="goldenChart-Desktop-Pipeline"
|
||||
GIT_DIR="${GIT_DIR:-/Volumes/ADATA/git/goldenChart.git}"
|
||||
WORK_TREE="${WORK_TREE:-/Users/aidev/apps/goldenChart}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
log() { echo "[install-desktop-jenkins] $*"; }
|
||||
|
||||
log "Jenkins job 생성: $JENKINS_JOB"
|
||||
mkdir -p "${JENKINS_HOME}/jobs/${JENKINS_JOB}"
|
||||
|
||||
cat > "${JENKINS_HOME}/jobs/${JENKINS_JOB}/config.xml" << JENKINS_XML
|
||||
<?xml version='1.1' encoding='UTF-8'?>
|
||||
<project>
|
||||
<description>GoldenChart Desktop — Tauri macOS dmg + Windows NSIS + updater</description>
|
||||
<keepDependencies>false</keepDependencies>
|
||||
<properties/>
|
||||
<scm class="hudson.plugins.git.GitSCM" plugin="git@5.0.0">
|
||||
<configVersion>2</configVersion>
|
||||
<userRemoteConfigs>
|
||||
<hudson.plugins.git.UserRemoteConfig>
|
||||
<url>file://${GIT_DIR}</url>
|
||||
</hudson.plugins.git.UserRemoteConfig>
|
||||
</userRemoteConfigs>
|
||||
<branches>
|
||||
<hudson.plugins.git.BranchSpec>
|
||||
<name>*/main</name>
|
||||
</hudson.plugins.git.BranchSpec>
|
||||
</branches>
|
||||
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
|
||||
<submoduleCfg class="empty-list"/>
|
||||
<extensions/>
|
||||
</scm>
|
||||
<canRoam>true</canRoam>
|
||||
<disabled>false</disabled>
|
||||
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
|
||||
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
|
||||
<triggers>
|
||||
<hudson.triggers.SCMTrigger>
|
||||
<spec>H/30 * * * *</spec>
|
||||
</hudson.triggers.SCMTrigger>
|
||||
</triggers>
|
||||
<concurrentBuild>false</concurrentBuild>
|
||||
<builders>
|
||||
<hudson.tasks.Shell>
|
||||
<command>#!/bin/bash
|
||||
set -euo pipefail
|
||||
export PATH="/opt/homebrew/opt/llvm/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:\$PATH"
|
||||
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"
|
||||
</command>
|
||||
</hudson.tasks.Shell>
|
||||
</builders>
|
||||
<publishers>
|
||||
<hudson.tasks.ArtifactArchiver>
|
||||
<artifacts>dist-desktop/**</artifacts>
|
||||
<allowEmptyArchive>true</allowEmptyArchive>
|
||||
<onlyIfSuccessful>false</onlyIfSuccessful>
|
||||
<fingerprint>false</fingerprint>
|
||||
<defaultExcludes>true</defaultExcludes>
|
||||
</hudson.tasks.ArtifactArchiver>
|
||||
</publishers>
|
||||
<buildWrappers/>
|
||||
</project>
|
||||
JENKINS_XML
|
||||
|
||||
if curl -sf -o /dev/null "http://127.0.0.1:8090/login" 2>/dev/null; then
|
||||
curl -sf -X POST "http://127.0.0.1:8090/reload" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "완료: http://127.0.0.1:8090/job/${JENKINS_JOB}/"
|
||||
log ""
|
||||
log "Jenkins Credentials (Secret file):"
|
||||
log " ID: tauri-signer-key → \$HOME/.tauri/goldenchart.key"
|
||||
log ""
|
||||
log "최초 1회 (서버):"
|
||||
log " ./scripts/install-desktop-build-deps.sh"
|
||||
log " ./scripts/setup-desktop-updater-keys.sh"
|
||||
log " git add desktop/updater.pub && git commit"
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tauri updater minisign 키쌍 생성 — 1회 (Jenkins secret 또는 서버 로컬)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
KEY_PATH="${TAURI_SIGNER_KEY:-$HOME/.tauri/goldenchart.key}"
|
||||
PUB_PATH="${TAURI_SIGNER_PUB:-$ROOT/desktop/updater.pub}"
|
||||
DESKTOP_DIR="$ROOT/desktop"
|
||||
|
||||
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"
|
||||
|
||||
mkdir -p "$(dirname "$KEY_PATH")"
|
||||
|
||||
if [[ -f "$KEY_PATH" ]]; then
|
||||
echo "기존 private key: $KEY_PATH"
|
||||
else
|
||||
echo "새 updater 키 생성: $KEY_PATH"
|
||||
GEN_OUT="$(cd "$DESKTOP_DIR" && npx tauri signer generate -w "$KEY_PATH" -f 2>&1)" || true
|
||||
echo "$GEN_OUT"
|
||||
if [[ ! -f "$KEY_PATH" ]]; then
|
||||
echo "키 생성 실패. Rust/tauri-cli 확인 후 재시도."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$PUB_PATH" ]]; then
|
||||
echo "Public key 추출 시도..."
|
||||
if echo "$GEN_OUT" | grep -qi 'public key'; then
|
||||
echo "$GEN_OUT" | grep -i 'public key' | tail -1 | sed -E 's/^[^:]*:[[:space:]]*//' > "$PUB_PATH"
|
||||
fi
|
||||
if [[ ! -s "$PUB_PATH" ]] && [[ -f "${KEY_PATH}.pub" ]]; then
|
||||
cp "${KEY_PATH}.pub" "$PUB_PATH"
|
||||
fi
|
||||
if [[ ! -s "$PUB_PATH" ]]; then
|
||||
echo "desktop/updater.pub 를 수동 생성하세요 (tauri signer generate 출력의 public key)."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
chmod +x "$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||
"$ROOT/scripts/patch-tauri-updater-pubkey.sh"
|
||||
|
||||
echo ""
|
||||
echo "=== updater 키 설정 완료 ==="
|
||||
echo " Private: $KEY_PATH (git/Jenkins secret — 커밋 금지)"
|
||||
echo " Public: $PUB_PATH"
|
||||
echo "Jenkins: TAURI_SIGNER_KEY=$KEY_PATH"
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# GoldenChart Desktop — smoke test (CI·로컬)
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BASE_URL="${DESKTOP_UPDATE_BASE_URL:-http://exdev.co.kr/desktop/updates}"
|
||||
FAIL=0
|
||||
|
||||
ok() { echo "[OK] $*"; }
|
||||
fail() { echo "[FAIL] $*"; FAIL=1; }
|
||||
|
||||
echo "=== Desktop Smoke Test ==="
|
||||
|
||||
# 1) Vite 번들
|
||||
if npm run build -w @goldenchart/desktop >/dev/null 2>&1; then
|
||||
ok "desktop vite build"
|
||||
else
|
||||
fail "desktop vite build"
|
||||
fi
|
||||
|
||||
# 2) updater pubkey
|
||||
if [[ -f "$ROOT/desktop/updater.pub" ]]; then
|
||||
ok "desktop/updater.pub exists"
|
||||
else
|
||||
echo "[WARN] desktop/updater.pub 없음 — setup-desktop-updater-keys.sh 필요"
|
||||
fi
|
||||
|
||||
# 3) tauri.conf pubkey not placeholder
|
||||
PUB="$(node -p "require('$ROOT/desktop/src-tauri/tauri.conf.json').plugins?.updater?.pubkey || ''")"
|
||||
if [[ -n "$PUB" && "$PUB" != "REPLACE_WITH_TAURI_SIGNER_PUBKEY" ]]; then
|
||||
ok "tauri.conf updater pubkey configured"
|
||||
else
|
||||
echo "[WARN] tauri.conf pubkey 미설정"
|
||||
fi
|
||||
|
||||
# 4) latest.json (local or remote)
|
||||
LOCAL_LATEST="$ROOT/frontend/public/desktop/updates/latest.json"
|
||||
if [[ -f "$LOCAL_LATEST" ]]; then
|
||||
ok "local latest.json"
|
||||
node -e "JSON.parse(require('fs').readFileSync('$LOCAL_LATEST','utf8'))" && ok "latest.json valid JSON"
|
||||
fi
|
||||
|
||||
if curl -sf "${BASE_URL}/latest.json" >/dev/null 2>&1; then
|
||||
ok "remote latest.json ${BASE_URL}/latest.json"
|
||||
else
|
||||
echo "[WARN] remote latest.json unreachable (배포 전 정상)"
|
||||
fi
|
||||
|
||||
# 5) 필수 스크립트
|
||||
for s in build-desktop.sh publish-desktop-update.sh jenkins-desktop-pipeline.sh; do
|
||||
if [[ -x "$ROOT/scripts/$s" ]]; then ok "scripts/$s"; else fail "scripts/$s missing"; fi
|
||||
done
|
||||
|
||||
if [[ "$FAIL" -eq 0 ]]; then
|
||||
echo "=== Smoke test passed ==="
|
||||
exit 0
|
||||
fi
|
||||
echo "=== Smoke test failed ==="
|
||||
exit 1
|
||||
Reference in New Issue
Block a user