mobile download
This commit is contained in:
Executable
+250
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
# GoldenChart Android APK — 로컬 빌드 → exdev.co.kr 서버 업로드
|
||||
#
|
||||
# 사용:
|
||||
# ./scripts/upload-android-apk.sh # 로컬 APK 업로드
|
||||
# ./scripts/upload-android-apk.sh --build # 빌드+서명 후 업로드
|
||||
# ./scripts/upload-android-apk.sh --version 1.2 # UI 표시 버전 (서버 .env 반영)
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
|
||||
# ── 배포 대상 설정 ───────────────────────────────────────────────────────
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/scripts/deploy-target.defaults"
|
||||
if [[ -f "$ROOT/scripts/deploy-target.local.env" ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/scripts/deploy-target.local.env"
|
||||
fi
|
||||
|
||||
REMOTE_HOST="${REMOTE_HOST:-exdev.co.kr}"
|
||||
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}"
|
||||
|
||||
LOCAL_APK="${LOCAL_APK:-$ROOT/data/mobile-releases/$APK_REMOTE_NAME}"
|
||||
DO_BUILD=false
|
||||
APK_VERSION=""
|
||||
SKIP_VERIFY=false
|
||||
SSH_TARGET="${REMOTE_USER}@${REMOTE_HOST}"
|
||||
|
||||
SSH_CMD=(ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -p "$REMOTE_PORT")
|
||||
SCP_CMD=(scp -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -P "$REMOTE_PORT")
|
||||
|
||||
print_usage() {
|
||||
cat <<EOF
|
||||
사용법: $0 [옵션]
|
||||
|
||||
(기본) 로컬 data/mobile-releases/$APK_REMOTE_NAME → 서버 업로드
|
||||
--build cap:sync + assembleRelease + 서명 후 업로드
|
||||
--apk PATH 업로드할 APK 경로
|
||||
--version VER 서버 GC_MOBILE_APP_VERSION (.env) 설정
|
||||
--project-dir PATH 서버 프로젝트 경로 (자동 탐색 생략)
|
||||
--host HOST 서버 호스트 (기본: $REMOTE_HOST)
|
||||
--user USER SSH 사용자 (기본: $REMOTE_USER)
|
||||
--no-verify 업로드 후 API 확인 생략
|
||||
-h, --help 도움말
|
||||
|
||||
서버 경로 자동 탐색 순서:
|
||||
1) --project-dir / REMOTE_PROJECT_DIR
|
||||
2) SSH → deploy.sh 의 WORK_TREE
|
||||
3) 후보: /Users/aidev/apps/goldenChart, /Users/aidev/dev/goldenChart
|
||||
|
||||
설정 파일: scripts/deploy-target.defaults
|
||||
scripts/deploy-target.local.env (선택, gitignore)
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--build) DO_BUILD=true; shift ;;
|
||||
--apk) LOCAL_APK="$2"; shift 2 ;;
|
||||
--version) APK_VERSION="$2"; shift 2 ;;
|
||||
--project-dir) REMOTE_PROJECT_DIR="$2"; shift 2 ;;
|
||||
--host) REMOTE_HOST="$2"; SSH_TARGET="${REMOTE_USER}@${REMOTE_HOST}"; shift 2 ;;
|
||||
--user) REMOTE_USER="$2"; SSH_TARGET="${REMOTE_USER}@${REMOTE_HOST}"; shift 2 ;;
|
||||
--no-verify) SKIP_VERIFY=true; shift ;;
|
||||
-h|--help) print_usage; exit 0 ;;
|
||||
*) echo -e "${RED}알 수 없는 옵션: $1${NC}"; print_usage; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
remote_exec() {
|
||||
"${SSH_CMD[@]}" "$SSH_TARGET" "bash -lc $(printf '%q' "$*")"
|
||||
}
|
||||
|
||||
resolve_remote_project_dir() {
|
||||
if [[ -n "${REMOTE_PROJECT_DIR:-}" ]]; then
|
||||
echo "$REMOTE_PROJECT_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}서버 프로젝트 경로 탐색 중...${NC}" >&2
|
||||
|
||||
# deploy.sh WORK_TREE
|
||||
local scripts="${REMOTE_DEPLOY_SCRIPTS:-/Volumes/ADATA/git/deploy.sh /Users/aidev/git/deploy.sh}"
|
||||
local wt
|
||||
wt=$("${SSH_CMD[@]}" "$SSH_TARGET" "for f in $scripts; do
|
||||
[ -f \"\$f\" ] || continue
|
||||
grep -E '^WORK_TREE=' \"\$f\" | head -1 | sed 's/^WORK_TREE=//' | tr -d '\"'
|
||||
break
|
||||
done" 2>/dev/null || true)
|
||||
if [[ -n "$wt" ]] && remote_exec "test -f \"$wt/docker-compose.yml\"" >/dev/null 2>&1; then
|
||||
echo "$wt"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# git remote origin → bare repo 인접 work-tree 추정
|
||||
local origin_url
|
||||
origin_url=$(git -C "$ROOT" remote get-url origin 2>/dev/null || true)
|
||||
if [[ "$origin_url" == *goldenChart* ]]; then
|
||||
for candidate in \
|
||||
/Users/aidev/apps/goldenChart \
|
||||
/Users/aidev/dev/goldenChart \
|
||||
/Volumes/ADATA/apps/goldenChart; do
|
||||
if remote_exec "test -f \"$candidate/docker-compose.yml\"" >/dev/null 2>&1; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "${RED}서버 프로젝트 경로를 찾지 못했습니다.${NC}" >&2
|
||||
echo " scripts/deploy-target.local.env 에 REMOTE_PROJECT_DIR=... 를 설정하거나" >&2
|
||||
echo " $0 --project-dir /path/to/goldenChart 를 사용하세요." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
sign_release_apk() {
|
||||
local unsigned="$ROOT/app/android/app/build/outputs/apk/release/app-release-unsigned.apk"
|
||||
local signed="$ROOT/app/android/app/build/outputs/apk/release/app-release.apk"
|
||||
local keystore="$ROOT/app/android/goldenchart-release.keystore"
|
||||
|
||||
if [[ ! -f "$unsigned" ]]; then
|
||||
echo -e "${RED}unsigned APK 없음: $unsigned${NC}"
|
||||
echo " 먼저: cd app/android && ./gradlew assembleRelease"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$keystore" ]]; then
|
||||
echo -e "${YELLOW}keystore 생성 중...${NC}"
|
||||
"$JAVA_HOME/bin/keytool" -genkeypair -v -keystore "$keystore" -alias goldenchart \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 \
|
||||
-storepass goldenchart -keypass goldenchart \
|
||||
-dname "CN=GoldenChart, OU=Mobile, O=GoldenChart, L=Seoul, ST=Seoul, C=KR"
|
||||
fi
|
||||
|
||||
local apksigner
|
||||
apksigner="${ANDROID_HOME:-$HOME/Library/Android/sdk}/build-tools/35.0.0/apksigner"
|
||||
if [[ ! -x "$apksigner" ]]; then
|
||||
apksigner=$(command -v apksigner 2>/dev/null || true)
|
||||
fi
|
||||
if [[ -z "$apksigner" || ! -x "$apksigner" ]]; then
|
||||
echo -e "${RED}apksigner 없음. Android SDK build-tools 설치 필요.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$unsigned" "$signed"
|
||||
"$apksigner" sign --ks "$keystore" --ks-key-alias goldenchart \
|
||||
--ks-pass pass:goldenchart --key-pass pass:goldenchart "$signed"
|
||||
cp "$signed" "$LOCAL_APK"
|
||||
echo -e "${GREEN}✓ 서명 완료: $LOCAL_APK${NC}"
|
||||
}
|
||||
|
||||
build_apk_locally() {
|
||||
if [[ -z "${JAVA_HOME:-}" ]] && [[ -x "$HOME/.local/jdk-21.0.11+10/Contents/Home/bin/java" ]]; then
|
||||
export JAVA_HOME="$HOME/.local/jdk-21.0.11+10/Contents/Home"
|
||||
fi
|
||||
if [[ -z "${JAVA_HOME:-}" ]]; then
|
||||
echo -e "${RED}JAVA_HOME 미설정. JDK 21 설치 후 다시 시도하세요.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}[빌드] npm run cap:sync${NC}"
|
||||
npm run cap:sync
|
||||
|
||||
echo -e "${CYAN}[빌드] ./gradlew assembleRelease${NC}"
|
||||
(cd "$ROOT/app/android" && ./gradlew assembleRelease --no-daemon)
|
||||
|
||||
mkdir -p "$(dirname "$LOCAL_APK")"
|
||||
sign_release_apk
|
||||
}
|
||||
|
||||
echo -e "${CYAN}╔══════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ GoldenChart APK → exdev.co.kr 업로드 ║${NC}"
|
||||
echo -e "${CYAN}╚══════════════════════════════════════════════╝${NC}"
|
||||
echo -e " 서버: ${YELLOW}${SSH_TARGET}${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ "$DO_BUILD" == true ]]; then
|
||||
build_apk_locally
|
||||
fi
|
||||
|
||||
if [[ ! -f "$LOCAL_APK" ]]; then
|
||||
echo -e "${RED}APK 없음: $LOCAL_APK${NC}"
|
||||
echo " ./scripts/copy-android-apk.sh 또는 $0 --build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}[연결] SSH 테스트...${NC}"
|
||||
if ! "${SSH_CMD[@]}" "$SSH_TARGET" "exit" >/dev/null 2>&1; then
|
||||
echo -e "${RED}SSH 연결 실패: ${SSH_TARGET}${NC}"
|
||||
echo " ssh-copy-id -p ${REMOTE_PORT} ${SSH_TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓ SSH 연결 성공${NC}"
|
||||
|
||||
PROJECT_DIR=$(resolve_remote_project_dir)
|
||||
REMOTE_APK_DIR="${PROJECT_DIR}/${APK_REMOTE_SUBDIR}"
|
||||
REMOTE_APK_PATH="${REMOTE_APK_DIR}/${APK_REMOTE_NAME}"
|
||||
|
||||
echo -e " 프로젝트: ${YELLOW}${PROJECT_DIR}${NC}"
|
||||
echo -e " APK 경로: ${YELLOW}${REMOTE_APK_PATH}${NC}"
|
||||
echo ""
|
||||
|
||||
APK_SIZE=$(du -h "$LOCAL_APK" | cut -f1)
|
||||
echo -e "${CYAN}[업로드] ${LOCAL_APK} (${APK_SIZE})${NC}"
|
||||
remote_exec "mkdir -p \"$REMOTE_APK_DIR\""
|
||||
"${SCP_CMD[@]}" "$LOCAL_APK" "${SSH_TARGET}:${REMOTE_APK_PATH}"
|
||||
echo -e "${GREEN}✓ 업로드 완료${NC}"
|
||||
|
||||
if [[ -n "$APK_VERSION" ]]; then
|
||||
echo -e "${CYAN}[버전] GC_MOBILE_APP_VERSION=${APK_VERSION}${NC}"
|
||||
remote_exec "cd \"$PROJECT_DIR\" && \
|
||||
{ [ -f .env ] && grep -v '^GC_MOBILE_APP_VERSION=' .env || true; echo 'GC_MOBILE_APP_VERSION=${APK_VERSION}'; } > .env.tmp && \
|
||||
mv .env.tmp .env"
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}[재시작] backend 컨테이너 (APK 볼륨 반영)${NC}"
|
||||
if remote_exec "cd \"$PROJECT_DIR\" && docker compose ps -q backend" | grep -q .; then
|
||||
remote_exec "cd \"$PROJECT_DIR\" && docker compose up -d --no-deps backend"
|
||||
echo -e "${GREEN}✓ backend 재시작 완료${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}backend 컨테이너 없음 — docker compose up -d 가 필요할 수 있습니다.${NC}"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_VERIFY" != true ]]; then
|
||||
echo -e "${CYAN}[확인] ${PUBLIC_URL}/api/mobile-app/info${NC}"
|
||||
sleep 2
|
||||
INFO=$(curl -fsSL "${PUBLIC_URL}/api/mobile-app/info" 2>/dev/null || true)
|
||||
if echo "$INFO" | grep -q '"available":true'; then
|
||||
echo -e "${GREEN}✓ 서버에서 APK 제공 중${NC}"
|
||||
echo "$INFO" | python3 -m json.tool 2>/dev/null || echo "$INFO"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ API에서 APK를 아직 찾지 못했습니다.${NC}"
|
||||
echo " docker-compose.yml 에 mobile-releases 볼륨이 있는지 확인 후 git push → Jenkins 재배포"
|
||||
echo " 응답: ${INFO:-(연결 실패)}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}완료${NC}"
|
||||
echo -e " 다운로드: ${PUBLIC_URL} → 상단 앱 다운로드 버튼"
|
||||
echo -e " QR/설치: ${PUBLIC_URL}/install.html"
|
||||
echo -e " 직접 URL: ${PUBLIC_URL}/api/mobile-app/download/android"
|
||||
Reference in New Issue
Block a user