Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/actions/deploy-to-ec2/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Deploy to EC2
description: Validate config, setup SSH, upload files, execute remote command

inputs:
ssh-private-key:
required: true
description: SSH key for remote machine
host:
required: true
description: SSH host
username:
required: true
description: SSH username
files:
description: Space-separated list of local files to upload
required: false
remote-command:
description: Command to execute on the remote host
required: true

runs:
using: composite
steps:
- name: 설정값 누락 확인
shell: bash
env:
_SSH_PRIVATE_KEY: ${{ inputs.ssh-private-key }}
_HOST: ${{ inputs.host }}
_USERNAME: ${{ inputs.username }}
_REMOTE_COMMAND: ${{ inputs.remote-command }}
run: |
missing=()
for var in _SSH_PRIVATE_KEY _HOST _USERNAME _REMOTE_COMMAND; do
if [ -z "${!var}" ]; then
missing+=("${var#_}")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Missing: ${missing[*]}"
exit 1
fi
echo "모든 필수 설정값이 확인되었습니다."

- name: SSH 연결 준비
shell: bash
run: |
mkdir -p ~/.ssh
echo "${{ inputs.ssh-private-key }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ inputs.host }}" >> ~/.ssh/known_hosts 2>/dev/null

- name: 파일 업로드
if: ${{ inputs.files != '' }}
shell: bash
run: |
scp -i ~/.ssh/deploy_key \
${{ inputs.files }} \
"${{ inputs.username }}@${{ inputs.host }}:/app/"

- name: 원격 명령 실행
shell: bash
run: |
ssh -i ~/.ssh/deploy_key \
"${{ inputs.username }}@${{ inputs.host }}" \
"${{ inputs.remote-command }}"
25 changes: 25 additions & 0 deletions .github/actions/notify-failure/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Notify Failure
description: Send failure notification via webhook

inputs:
webhook-url:
description: Notification webhook URL (optional - skips if empty)
required: false
message:
description: Failure message to send
required: true

runs:
using: composite
steps:
- name: 실패 알림
shell: bash
env:
WEBHOOK_URL: ${{ inputs.webhook-url }}
MESSAGE: ${{ inputs.message }}
run: |
if [ -n "$WEBHOOK_URL" ]; then
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"${MESSAGE}\"}"
fi
77 changes: 36 additions & 41 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CD
name: Sofia CD Pipeline

on:
push:
Expand All @@ -11,6 +11,11 @@ concurrency:
permissions:
contents: write

# 이 파이프라인이 필요로 하는 설정값 목록:
# Required Variables (비민감): EC2_HOST, EC2_USERNAME
# Required Secrets (민감): EC2_SSH_PRIVATE_KEY, SOFIA_DATASOURCE_URL, SOFIA_DATASOURCE_PASSWORD, SOFIA_DATASOURCE_USERNAME
# Optional Secrets (민감): NOTIFICATION_WEBHOOK_URL

jobs:
deploy:
runs-on: ubuntu-latest
Expand All @@ -24,63 +29,53 @@ jobs:
distribution: temurin
java-version: "21"

- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle.kts', 'gradle.properties') }}
restore-keys: gradle-
- uses: gradle/actions/setup-gradle@v4

- name: Read version
- name: 버전 읽기
id: version
run: |
VERSION=$(grep 'ywcheong.sofia.version=' gradle.properties | cut -d'=' -f2)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Build
- name: 빌드
run: ./gradlew build

- name: Create and push tag
run: |
git tag "v${{ steps.version.outputs.version }}"
git push origin "v${{ steps.version.outputs.version }}"

- name: Setup SSH
- name: 시크릿 환경변수 파일 생성
env:
SOFIA_DATASOURCE_URL: ${{ secrets.SOFIA_DATASOURCE_URL }}
SOFIA_DATASOURCE_PASSWORD: ${{ secrets.SOFIA_DATASOURCE_PASSWORD }}
SOFIA_DATASOURCE_USERNAME: ${{ secrets.SOFIA_DATASOURCE_USERNAME }}
run: |
mkdir -p ~/.ssh
echo "${{ secrets.EC2_SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null
{
echo "SOFIA_DATASOURCE_URL=${SOFIA_DATASOURCE_URL}"
echo "SOFIA_DATASOURCE_PASSWORD=${SOFIA_DATASOURCE_PASSWORD}"
echo "SOFIA_DATASOURCE_USERNAME=${SOFIA_DATASOURCE_USERNAME}"
} > deploy/secrets.env

- name: Upload jar to EC2
run: |
scp -i ~/.ssh/deploy_key \
"build/libs/sofia-${{ steps.version.outputs.version }}.jar" \
"${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:${{ secrets.EC2_DEPLOY_DIR }}/"
- name: EC2 배포
uses: ./.github/actions/deploy-to-ec2
with:
ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
host: ${{ vars.EC2_HOST }}
username: ${{ vars.EC2_USERNAME }}
files: deploy/deploy.sh deploy/secrets.env build/libs/sofia-${{ steps.version.outputs.version }}.jar
remote-command: "/app/deploy.sh '/app/sofia-${{ steps.version.outputs.version }}.jar'"

- name: Run deploy script
- name: Git 태그 생성 및 푸시
run: |
ssh -i ~/.ssh/deploy_key \
"${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}" \
"${{ secrets.EC2_DEPLOY_SCRIPT }} '${{ secrets.EC2_DEPLOY_DIR }}/sofia-${{ steps.version.outputs.version }}.jar'"
git tag "v${{ steps.version.outputs.version }}"
git push origin "v${{ steps.version.outputs.version }}"

- name: Create GitHub Release
- name: GitHub Release 생성
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ steps.version.outputs.version }}"
files: build/libs/sofia-${{ steps.version.outputs.version }}.jar
generate_release_notes: true

- name: Notify on failure
- name: 실패 알림
if: failure()
env:
WEBHOOK_URL: ${{ secrets.NOTIFICATION_WEBHOOK_URL }}
VERSION: ${{ steps.version.outputs.version }}
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
run: |
if [ -n "$WEBHOOK_URL" ]; then
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"Sofia v${VERSION} 배포 실패\n${RUN_URL}\"}"
fi
uses: ./.github/actions/notify-failure
with:
webhook-url: ${{ secrets.NOTIFICATION_WEBHOOK_URL }}
message: "Sofia v${{ steps.version.outputs.version }} 배포 실패\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
20 changes: 11 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: Sofia CI Pipeline

on:
pull_request:
Expand All @@ -15,13 +15,15 @@ jobs:
distribution: temurin
java-version: "21"

- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle.kts', 'gradle.properties') }}
restore-keys: gradle-
- uses: gradle/actions/setup-gradle@v4

- name: 버전 태그 중복 확인
run: |
VERSION=$(grep 'ywcheong.sofia.version=' gradle.properties | cut -d'=' -f2)
if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q .; then
echo "::error::Tag v${VERSION} already exists. Update ywcheong.sofia.version in gradle.properties."
exit 1
fi

- name: Run checks
- name: 검사 수행
run: ./gradlew check
43 changes: 43 additions & 0 deletions .github/workflows/init.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Sofia Remote Init (Run only once)

on:
workflow_dispatch:

concurrency:
group: init
cancel-in-progress: false

# 이 파이프라인이 필요로 하는 설정값 목록:
# Required Variables (비민감): EC2_HOST, EC2_USERNAME
# Required Secrets (민감): EC2_SSH_PRIVATE_KEY
# Optional Secrets (민감): NOTIFICATION_WEBHOOK_URL

jobs:
init:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: 인증서 파일 생성
env:
CF_CERT: ${{ secrets.CLOUDFLARE_CERTS_CERT }}
CF_KEY: ${{ secrets.CLOUDFLARE_CERTS_KEY }}
run: |
printf '%s' "$CF_CERT" > deploy/cert.pem
printf '%s' "$CF_KEY" > deploy/key.pem

- name: EC2 초기화
uses: ./.github/actions/deploy-to-ec2
with:
ssh-private-key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
host: ${{ vars.EC2_HOST }}
username: ${{ vars.EC2_USERNAME }}
files: deploy/init.sh deploy/nginx-sofia.conf deploy/cert.pem deploy/key.pem
remote-command: "bash /app/init.sh"

- name: 실패 알림
if: failure()
uses: ./.github/actions/notify-failure
with:
webhook-url: ${{ secrets.NOTIFICATION_WEBHOOK_URL }}
message: "Sofia 초기화 실패\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
98 changes: 73 additions & 25 deletions deploy/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,83 @@ JAR_PATH="$1"
PID_FILE="/app/sofia.pid"
LOG_DIR="/app/log"

# 기존 프로세스 종료
if [ -f "$PID_FILE" ]; then
OLD_PID=$(cat "$PID_FILE")
if kill -0 "$OLD_PID" 2>/dev/null; then
echo "Stopping existing process (PID: $OLD_PID)"
kill "$OLD_PID"
timeout 30 bash -c "while kill -0 $OLD_PID 2>/dev/null; do sleep 1; done" || true
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/secrets.env"

stop_existing_process() {
if [ ! -f "$PID_FILE" ]; then
return
fi

local old_pid
old_pid=$(cat "$PID_FILE")

if kill -0 "$old_pid" 2>/dev/null; then
echo "Stopping existing process (PID: $old_pid)"
kill "$old_pid"
timeout 30 bash -c "while kill -0 $old_pid 2>/dev/null; do sleep 1; done" || true
fi

rm -f "$PID_FILE"
fi
}

# 로그 디렉토리 생성
mkdir -p "$LOG_DIR"
build_log_file() {
mkdir -p "$LOG_DIR"
local version
version=$(basename "$JAR_PATH" | sed 's/sofia-\(.*\)\.jar/\1/')
echo "${LOG_DIR}/$(date +%Y%m%d-%H%M%S)-${version}.log"
}

# 로그 파일명: {launch-date}-{version}.log
VERSION=$(basename "$JAR_PATH" | sed 's/sofia-\(.*\)\.jar/\1/')
LOG_FILE="${LOG_DIR}/$(date +%Y%m%d-%H%M%S)-${VERSION}.log"
refresh_secrets_from_env() {
local secret_keys=("SOFIA_DATASOURCE_URL" "SOFIA_DATASOURCE_PASSWORD" "SOFIA_DATASOURCE_USERNAME")
local available=false

# 환경변수 로드 후 실행
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/secrets.env"
for key in "${secret_keys[@]}"; do
if [ -n "${!key:-}" ]; then
available=true
break
fi
done

if [ "$available" = true ]; then
echo "Refreshing secrets.env from environment variables"
> "$ENV_FILE"
for key in "${secret_keys[@]}"; do
if [ -n "${!key:-}" ]; then
echo "${key}=${!key}" >> "$ENV_FILE"
fi
done
fi
}

load_env_file() {
if [ -f "$ENV_FILE" ]; then
set -a
source "$ENV_FILE"
set +a
fi
}

start_application() {
local log_file="$1"
local version
version=$(basename "$JAR_PATH" | sed 's/sofia-\(.*\)\.jar/\1/')

echo "Starting Sofia v${version}"
java \
-Xms128m -Xmx256m \
-XX:MetaspaceSize=64m \
-XX:MaxMetaspaceSize=128m \
-XX:+UseSerialGC \
-jar "$JAR_PATH" > "$log_file" 2>&1 &

if [ -f "$ENV_FILE" ]; then
set -a
source "$ENV_FILE"
set +a
fi
echo $! > "$PID_FILE"
echo "Started (PID: $(cat "$PID_FILE"), Log: $log_file)"
}

echo "Starting Sofia v${VERSION}"
java -jar "$JAR_PATH" > "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
echo "Started (PID: $(cat "$PID_FILE"), Log: $LOG_FILE)"
# --- Main ---
stop_existing_process
log_file=$(build_log_file)
refresh_secrets_from_env
load_env_file
start_application "$log_file"
Loading
Loading