Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d9b614d9b | ||
|
|
9977dea0d2 | ||
|
|
7962932726 | ||
|
|
643f1136a2 | ||
|
|
71f298962b | ||
|
|
e8919a2790 | ||
|
|
93b877e60a | ||
|
|
2c0b6e8123 | ||
|
|
fa3ac73fbe | ||
|
|
696ee79540 | ||
|
|
8503a7559a | ||
|
|
13504848d0 | ||
|
|
d01121306b | ||
|
|
b38db2357b | ||
|
|
50e6e90122 | ||
|
|
bfd426a77f | ||
|
|
f8dd8a7728 | ||
|
|
ca2fafa214 | ||
|
|
54289f22d3 | ||
|
|
0a7040bff6 |
@@ -0,0 +1,2 @@
|
||||
tests
|
||||
.DS_Store
|
||||
@@ -0,0 +1,73 @@
|
||||
name: 특정 저장소 체크아웃
|
||||
description: Git 인증 정보를 사용해 특정 저장소와 브랜치를 수동으로 체크아웃
|
||||
|
||||
inputs:
|
||||
repository-url:
|
||||
required: true
|
||||
description: 체크아웃할 Git 저장소 URL
|
||||
username:
|
||||
required: true
|
||||
description: Git 인증 사용자명
|
||||
access-token:
|
||||
required: true
|
||||
description: Git 인증 토큰
|
||||
branch:
|
||||
required: false
|
||||
default: develop
|
||||
description: 체크아웃할 브랜치
|
||||
path:
|
||||
required: false
|
||||
default: "."
|
||||
description: 저장소를 체크아웃할 경로
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: 특정 저장소 체크아웃
|
||||
shell: bash
|
||||
env:
|
||||
GIT_REPOSITORY_URL: ${{ inputs.repository-url }}
|
||||
GIT_USERNAME: ${{ inputs.username }}
|
||||
GIT_ACCESS_TOKEN: ${{ inputs.access-token }}
|
||||
GIT_BRANCH: ${{ inputs.branch }}
|
||||
CHECKOUT_PATH: ${{ inputs.path }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
SUDO=""
|
||||
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends git ca-certificates
|
||||
else
|
||||
echo "git이 없습니다. git을 설치한 러너가 필요합니다."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
ASKPASS_FILE="$(mktemp)"
|
||||
cleanup() {
|
||||
rm -f "$ASKPASS_FILE"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cat > "$ASKPASS_FILE" <<'EOF'
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
*Username*) echo "$GIT_USERNAME" ;;
|
||||
*Password*) echo "$GIT_ACCESS_TOKEN" ;;
|
||||
esac
|
||||
EOF
|
||||
chmod 700 "$ASKPASS_FILE"
|
||||
|
||||
export GIT_ASKPASS="$ASKPASS_FILE"
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
|
||||
mkdir -p "$CHECKOUT_PATH"
|
||||
git clone \
|
||||
--branch "$GIT_BRANCH" \
|
||||
--single-branch \
|
||||
"$GIT_REPOSITORY_URL" \
|
||||
"$CHECKOUT_PATH"
|
||||
@@ -0,0 +1,414 @@
|
||||
name: VPN 연결 (F5 BIG-IP)
|
||||
description: F5 BIG-IP VPN 클라이언트를 설치하고 웹 로그인/OTP를 자동화해 VPN에 연결
|
||||
|
||||
inputs:
|
||||
deb-path:
|
||||
required: false
|
||||
default: ""
|
||||
description: F5 VPN 클라이언트 .deb 경로. 비어있으면 deb-url에서 다운로드
|
||||
deb-url:
|
||||
required: false
|
||||
default: "https://gitea.pitap.at/public/commonDeploy/releases/download/setup01/linux_f5vpn.x86_64.deb"
|
||||
description: F5 VPN 클라이언트 .deb 다운로드 URL
|
||||
|
||||
vpn-host:
|
||||
required: true
|
||||
description: F5 VPN 호스트 (예 https://vpn.example.com)
|
||||
vpn-user:
|
||||
required: true
|
||||
description: VPN 사용자 ID
|
||||
vpn-password:
|
||||
required: true
|
||||
description: VPN 정적 비밀번호
|
||||
otp-seed:
|
||||
required: true
|
||||
description: TOTP 시드 키 (이 값으로 매 실행 OTP 코드를 로컬 생성)
|
||||
otp-seed-format:
|
||||
required: false
|
||||
default: "base64"
|
||||
description: >
|
||||
OTP 시드 포맷. base32=구글OTP류 문자열(대문자/공백 자동 정규화),
|
||||
hex=16진수 문자열, base64=raw secret bytes를 base64로 인코딩한 키,
|
||||
base64-base32=base32 문자열을 base64로 감싼 키.
|
||||
otp-mode:
|
||||
required: false
|
||||
default: "form"
|
||||
description: >
|
||||
호환성용 입력. 현재 웹 로그인 방식은 password 단계 후 OTP를 ga_code_attempt 폼에 별도 제출합니다.
|
||||
debug-otp:
|
||||
required: false
|
||||
default: "false"
|
||||
description: "true면 seed 검증용으로 생성된 OTP를 로그에 출력합니다. 확인 후 즉시 false로 되돌리세요."
|
||||
debug-otp-only:
|
||||
required: false
|
||||
default: "false"
|
||||
description: "true면 생성된 OTP만 출력하고 VPN 로그인은 시도하지 않습니다."
|
||||
|
||||
resource-name:
|
||||
required: false
|
||||
default: "/Common/dw_default_na"
|
||||
description: F5 Network Access 리소스 이름
|
||||
timeout:
|
||||
required: false
|
||||
default: "90"
|
||||
description: 연결 완료 대기 최대 시간(초)
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: 의존성/F5 VPN 클라이언트 설치
|
||||
shell: bash
|
||||
env:
|
||||
DEB_PATH: ${{ inputs.deb-path }}
|
||||
DEB_URL: ${{ inputs.deb-url }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
SUDO=""
|
||||
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo "apt 기반 Linux 러너가 필요합니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl python3 coreutils dbus-x11 xvfb xdotool x11-utils procps iproute2 \
|
||||
libxslt1.1 libsqlite3-0 libgl1 libglib2.0-0 libx11-xcb1 libxcb1 \
|
||||
libxcb-render0 libxcb-shape0 libxcb-xfixes0 libxcb-shm0 libxrender1 \
|
||||
libxi6 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 \
|
||||
libxrandr2 libxtst6 libfontconfig1 libfreetype6
|
||||
|
||||
DEB=""
|
||||
if [ -n "$DEB_PATH" ]; then
|
||||
DEB="$DEB_PATH"
|
||||
elif [ -n "$DEB_URL" ]; then
|
||||
DEB="/tmp/f5-vpn-client.deb"
|
||||
curl -fsSL -o "$DEB" "$DEB_URL"
|
||||
fi
|
||||
|
||||
if [ -z "$DEB" ] || [ ! -f "$DEB" ]; then
|
||||
echo ".deb를 찾을 수 없습니다 (deb-path/deb-url 확인)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "F5 VPN 클라이언트 설치: $DEB"
|
||||
$SUDO dpkg -i "$DEB" || $SUDO apt-get install -f -y
|
||||
|
||||
if [ ! -x /opt/f5/vpn/f5vpn ]; then
|
||||
echo "/opt/f5/vpn/f5vpn 이 설치되지 않았습니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: TUN 디바이스 확인
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
echo "/dev/net/tun 이 없습니다. 러너에 --device /dev/net/tun --cap-add=NET_ADMIN 필요"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: VPN 연결
|
||||
shell: bash
|
||||
env:
|
||||
VPN_HOST: ${{ inputs.vpn-host }}
|
||||
VPN_USER: ${{ inputs.vpn-user }}
|
||||
VPN_PASS: ${{ inputs.vpn-password }}
|
||||
OTP_SEED: ${{ inputs.otp-seed }}
|
||||
OTP_SEED_FORMAT: ${{ inputs.otp-seed-format }}
|
||||
DEBUG_OTP: ${{ inputs.debug-otp }}
|
||||
DEBUG_OTP_ONLY: ${{ inputs.debug-otp-only }}
|
||||
RESOURCE_NAME: ${{ inputs.resource-name }}
|
||||
TIMEOUT: ${{ inputs.timeout }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
WORKDIR="${RUNNER_TEMP:-/tmp}/f5-vpn-action"
|
||||
COOKIE_JAR="$WORKDIR/cookies.txt"
|
||||
LAUNCH_URL_FILE="$WORKDIR/f5-vpn-launch-url.txt"
|
||||
UA="Mozilla/5.0 (X11; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0"
|
||||
mkdir -p "$WORKDIR"
|
||||
rm -f "$COOKIE_JAR" "$LAUNCH_URL_FILE"
|
||||
|
||||
CONNECTED=0
|
||||
cleanup() {
|
||||
if [ "$CONNECTED" = "1" ]; then
|
||||
return
|
||||
fi
|
||||
if [ -n "${F5VPN_PID:-}" ] && kill -0 "$F5VPN_PID" 2>/dev/null; then
|
||||
kill "$F5VPN_PID" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "${XVFB_PID:-}" ] && kill -0 "$XVFB_PID" 2>/dev/null; then
|
||||
kill "$XVFB_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
totp() {
|
||||
python3 - "$OTP_SEED" "$OTP_SEED_FORMAT" "${1:-0}" <<'PY'
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
seed, fmt, offset_windows = sys.argv[1], sys.argv[2], int(sys.argv[3])
|
||||
seed = "".join(seed.split())
|
||||
|
||||
def decode_base32(value):
|
||||
normalized = value.replace(" ", "").upper().rstrip("=")
|
||||
normalized += "=" * ((8 - len(normalized) % 8) % 8)
|
||||
return base64.b32decode(normalized)
|
||||
|
||||
if fmt == "base64":
|
||||
raw = base64.b64decode(seed)
|
||||
elif fmt == "base64-base32":
|
||||
raw = decode_base32(base64.b64decode(seed).decode("ascii"))
|
||||
elif fmt == "base32":
|
||||
raw = decode_base32(seed)
|
||||
elif fmt == "hex":
|
||||
raw = bytes.fromhex(seed)
|
||||
else:
|
||||
raise SystemExit(f"잘못된 otp-seed-format: {fmt}")
|
||||
|
||||
counter = int(time.time() // 30) + offset_windows
|
||||
digest = hmac.new(raw, struct.pack(">Q", counter), hashlib.sha1).digest()
|
||||
offset = digest[-1] & 0x0f
|
||||
code = (struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7fffffff) % 1000000
|
||||
print(f"{code:06d}")
|
||||
PY
|
||||
}
|
||||
|
||||
curl_common() {
|
||||
curl -k -sS --http1.1 -A "$UA" "$@"
|
||||
}
|
||||
|
||||
build_otp_post_body() {
|
||||
python3 - "$1" "$2" "$3" "$4" <<'PY'
|
||||
from html.parser import HTMLParser
|
||||
import sys
|
||||
import urllib.parse
|
||||
|
||||
html_file, otp_code, action_file, body_file = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
||||
|
||||
class FormParser(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.forms = []
|
||||
self.current = None
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
attrs = {k.lower(): (v if v is not None else "") for k, v in attrs}
|
||||
if tag.lower() == "form":
|
||||
self.current = {"attrs": attrs, "inputs": []}
|
||||
self.forms.append(self.current)
|
||||
elif tag.lower() == "input" and self.current is not None:
|
||||
self.current["inputs"].append(attrs)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag.lower() == "form":
|
||||
self.current = None
|
||||
|
||||
parser = FormParser()
|
||||
with open(html_file, encoding="utf-8", errors="ignore") as f:
|
||||
parser.feed(f.read())
|
||||
|
||||
form = None
|
||||
for candidate in parser.forms:
|
||||
names = {field.get("name", "") for field in candidate["inputs"]}
|
||||
if "ga_code_attempt" in names:
|
||||
form = candidate
|
||||
break
|
||||
if form is None:
|
||||
raise SystemExit("OTP form not found")
|
||||
|
||||
data = []
|
||||
seen = set()
|
||||
for field in form["inputs"]:
|
||||
name = field.get("name", "")
|
||||
if not name:
|
||||
continue
|
||||
field_type = field.get("type", "text").lower()
|
||||
if field_type in {"button", "image", "file"}:
|
||||
continue
|
||||
if field_type in {"checkbox", "radio"} and "checked" not in field:
|
||||
continue
|
||||
value = field.get("value", "")
|
||||
if name == "ga_code_attempt":
|
||||
value = otp_code
|
||||
data.append((name, value))
|
||||
seen.add(name)
|
||||
|
||||
if "ga_code_attempt" not in seen:
|
||||
data.append(("ga_code_attempt", otp_code))
|
||||
if "vhost" not in seen:
|
||||
data.append(("vhost", "standard"))
|
||||
|
||||
action = form["attrs"].get("action") or "/my.policy"
|
||||
with open(action_file, "w", encoding="utf-8") as f:
|
||||
f.write(action)
|
||||
with open(body_file, "w", encoding="utf-8") as f:
|
||||
f.write(urllib.parse.urlencode(data))
|
||||
PY
|
||||
}
|
||||
|
||||
debug_print_otps() {
|
||||
echo "DEBUG OTP 확인용 코드 (unix-time=$(date +%s))"
|
||||
for otp_offset in 0 -1 1; do
|
||||
echo "DEBUG generated OTP offset=${otp_offset}: $(totp "$otp_offset")"
|
||||
done
|
||||
}
|
||||
|
||||
summarize_html() {
|
||||
page="$1"
|
||||
if [ ! -f "$page" ]; then
|
||||
return
|
||||
fi
|
||||
title="$(sed -n 's/.*<title>\(.*\)<\/title>.*/\1/ip' "$page" | head -1 | tr -d '\r' || true)"
|
||||
if [ -n "$title" ]; then
|
||||
echo "응답 title: $title"
|
||||
fi
|
||||
if grep -q "ga_code_attempt" "$page"; then
|
||||
echo "응답 상태: OTP 입력 폼이 다시 표시됨"
|
||||
fi
|
||||
sed 's/<[^>]*>/\n/g' "$page" |
|
||||
sed 's/ / /g; s/&/\&/g; s/^[[:space:]]*//; s/[[:space:]]*$//' |
|
||||
grep -Eai 'incorrect|invalid|denied|failed|failure|error|otp|password|인증|실패|오류|잘못|거부' |
|
||||
grep -Ev '^$' |
|
||||
head -10 || true
|
||||
}
|
||||
|
||||
if [ "$DEBUG_OTP_ONLY" = "true" ]; then
|
||||
debug_print_otps
|
||||
echo "debug-otp-only=true 이므로 VPN 로그인 시도 없이 종료합니다."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "F5 웹 로그인 시작: ${VPN_HOST} (user=${VPN_USER})"
|
||||
curl_common -L -c "$COOKIE_JAR" "$VPN_HOST/" -o "$WORKDIR/01-login.html"
|
||||
|
||||
curl_common -L -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
|
||||
-X POST "$VPN_HOST/my.policy" \
|
||||
--data-urlencode "username=$VPN_USER" \
|
||||
--data-urlencode "password=$VPN_PASS" \
|
||||
--data-urlencode "vhost=standard" \
|
||||
-o "$WORKDIR/02-otp.html"
|
||||
|
||||
if ! grep -q "ga_code_attempt" "$WORKDIR/02-otp.html"; then
|
||||
echo "OTP 입력 폼에 도달하지 못했습니다. 사용자명/비밀번호 또는 APM 정책을 확인하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host_no_scheme="${VPN_HOST#https://}"
|
||||
host_no_scheme="${host_no_scheme#http://}"
|
||||
host_no_scheme="${host_no_scheme%%/*}"
|
||||
webtop_path="/vdesk/webtop.eui?z=$RESOURCE_NAME&webtop=/Common/dw_webtop&webtop_type=webtop_na_only"
|
||||
|
||||
TOKEN_HEADERS="$WORKDIR/04-token.headers"
|
||||
otc=""
|
||||
while [ $(( $(date +%s) % 30 )) -gt 22 ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
for otp_offset in 0 -1 1; do
|
||||
echo "OTP 제출 시도 (time-window offset=${otp_offset})"
|
||||
OTP_CODE="$(totp "$otp_offset")"
|
||||
if [ "$DEBUG_OTP" = "true" ]; then
|
||||
echo "DEBUG generated OTP offset=${otp_offset}: ${OTP_CODE} (unix-time=$(date +%s))"
|
||||
fi
|
||||
OTP_POST_BODY="$WORKDIR/03-otp-post-${otp_offset}.body"
|
||||
OTP_ACTION_FILE="$WORKDIR/03-otp-action-${otp_offset}.txt"
|
||||
build_otp_post_body "$WORKDIR/02-otp.html" "$OTP_CODE" "$OTP_ACTION_FILE" "$OTP_POST_BODY"
|
||||
OTP_ACTION="$(cat "$OTP_ACTION_FILE")"
|
||||
case "$OTP_ACTION" in
|
||||
http://*|https://*) OTP_URL="$OTP_ACTION" ;;
|
||||
/*) OTP_URL="$VPN_HOST$OTP_ACTION" ;;
|
||||
*) OTP_URL="$VPN_HOST/$OTP_ACTION" ;;
|
||||
esac
|
||||
rm -f "$TOKEN_HEADERS" "$WORKDIR/04-token.body"
|
||||
|
||||
curl_common -L -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
|
||||
-X POST "$OTP_URL" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
--data-binary "@$OTP_POST_BODY" \
|
||||
-o "$WORKDIR/03-webtop-${otp_offset}.html"
|
||||
|
||||
curl_common -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
|
||||
-D "$TOKEN_HEADERS" \
|
||||
-H "X-Requested-With: XMLHttpRequest" \
|
||||
-H "Accept: */*" \
|
||||
-e "$VPN_HOST$webtop_path" \
|
||||
"$VPN_HOST/vdesk/get_token_for_sessid.php3" \
|
||||
-o "$WORKDIR/04-token.body"
|
||||
|
||||
otc="$(
|
||||
awk 'BEGIN{IGNORECASE=1} /^X-ACCESS-Session-Token:/ { sub(/\r$/, ""); print substr($0, index($0, ":") + 2) }' "$TOKEN_HEADERS" |
|
||||
tail -n 1
|
||||
)"
|
||||
if [ -n "$otc" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if ! grep -q "ga_code_attempt" "$WORKDIR/03-webtop-${otp_offset}.html"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$otc" ]; then
|
||||
echo "F5 session token 획득 실패"
|
||||
for page in "$WORKDIR"/03-webtop-*.html; do
|
||||
summarize_html "$page"
|
||||
done
|
||||
summarize_html "$WORKDIR/04-token.body"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
base_url="f5-vpn://$host_no_scheme?server=$host_no_scheme&resourcename=$RESOURCE_NAME&resourcetype=network_access&cmd=launch&protocol=https&port=443&sid=nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
|
||||
token="$(printf '%s' "$base_url" | md5sum | awk '{print $1}')"
|
||||
printf '%s\n' "$base_url&token=$token&otc=$otc" > "$LAUNCH_URL_FILE"
|
||||
|
||||
# F5 번들 Qt 5.5는 Xvfb RandR refresh rate에서 SIGFPE가 나므로 RandR 확장을 끈다.
|
||||
Xvfb :99 -screen 0 1280x800x24 -extension RANDR >"$WORKDIR/xvfb.log" 2>&1 &
|
||||
XVFB_PID=$!
|
||||
sleep 1
|
||||
|
||||
export DISPLAY=:99
|
||||
export HOME="$WORKDIR/home"
|
||||
mkdir -p "$HOME"
|
||||
|
||||
echo "F5 VPN 클라이언트 실행"
|
||||
/opt/f5/vpn/f5vpn "$(cat "$LAUNCH_URL_FILE")" >"$WORKDIR/f5vpn.out" 2>"$WORKDIR/f5vpn.err" &
|
||||
F5VPN_PID=$!
|
||||
|
||||
for _ in $(seq 1 20); do
|
||||
if xwininfo -root -tree 2>/dev/null | grep -q "F5 VPN - Security Warning"; then
|
||||
xdotool mousemove 650 398 click 1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "연결 완료 대기 중 (최대 ${TIMEOUT}초)..."
|
||||
for _ in $(seq 1 "$TIMEOUT"); do
|
||||
if ip link show tun0 >/dev/null 2>&1; then
|
||||
CONNECTED=1
|
||||
echo "VPN 연결 완료"
|
||||
ip -br addr show tun0
|
||||
exit 0
|
||||
fi
|
||||
if ! kill -0 "$F5VPN_PID" 2>/dev/null; then
|
||||
echo "f5vpn이 tun0 생성 전에 종료되었습니다."
|
||||
sed -n '1,120p' "$WORKDIR/f5vpn.err" || true
|
||||
tail -120 "$HOME/.F5Networks/vpn.log" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "VPN 연결 실패 (${TIMEOUT}초 초과)."
|
||||
sed -n '1,120p' "$WORKDIR/f5vpn.err" || true
|
||||
tail -120 "$HOME/.F5Networks/vpn.log" 2>/dev/null || true
|
||||
exit 1
|
||||
+21
-2
@@ -20,10 +20,29 @@ runs:
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
SUDO=""
|
||||
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
|
||||
if ! command -v ssh-keyscan >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends openssh-client
|
||||
else
|
||||
echo "ssh-keyscan이 없습니다. openssh-client를 설치한 러너가 필요합니다."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
printf '%s\n' "${{ inputs.ssh-key }}" > ${{ inputs.key-path }}
|
||||
chmod 600 ${{ inputs.key-path }}
|
||||
KEY_PATH="${{ inputs.key-path }}"
|
||||
case "$KEY_PATH" in
|
||||
"~/"*) KEY_PATH="$HOME/${KEY_PATH#\~/}" ;;
|
||||
esac
|
||||
mkdir -p "$(dirname "$KEY_PATH")"
|
||||
|
||||
printf '%s\n' "${{ inputs.ssh-key }}" > "$KEY_PATH"
|
||||
chmod 600 "$KEY_PATH"
|
||||
|
||||
ssh-keyscan -p ${{ inputs.port }} -H "${{ inputs.host }}" >> ~/.ssh/known_hosts
|
||||
@@ -22,10 +22,26 @@ runs:
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
SUDO=""
|
||||
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
|
||||
if ! command -v ssh >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends openssh-client
|
||||
else
|
||||
echo "ssh가 없습니다. openssh-client를 설치한 러너가 필요합니다."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
KEY_PATH="${{ inputs.key-path }}"
|
||||
if [ -z "$KEY_PATH" ]; then
|
||||
KEY_PATH="$HOME/.ssh/deploy_key"
|
||||
fi
|
||||
case "$KEY_PATH" in
|
||||
"~/"*) KEY_PATH="$HOME/${KEY_PATH#\~/}" ;;
|
||||
esac
|
||||
|
||||
ssh -p "${{ inputs.port }}" -i "$KEY_PATH" \
|
||||
-o IdentitiesOnly=yes \
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
name: JDK 설정
|
||||
description: Adoptium Temurin JDK를 수동으로 설치하고 JAVA_HOME과 PATH를 설정
|
||||
|
||||
inputs:
|
||||
distribution:
|
||||
required: false
|
||||
default: temurin
|
||||
description: 설치할 JDK 배포판 (현재 temurin만 지원)
|
||||
java-version:
|
||||
required: false
|
||||
default: "21"
|
||||
description: 설치할 Java 메이저 버전
|
||||
architecture:
|
||||
required: false
|
||||
default: auto
|
||||
description: 설치할 CPU 아키텍처 (auto, x64, aarch64)
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: JDK 설정
|
||||
shell: bash
|
||||
env:
|
||||
JAVA_DISTRIBUTION: ${{ inputs.distribution }}
|
||||
JAVA_VERSION: ${{ inputs.java-version }}
|
||||
JAVA_ARCHITECTURE: ${{ inputs.architecture }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$JAVA_DISTRIBUTION" != "temurin" ]; then
|
||||
echo "지원하지 않는 JDK 배포판입니다: $JAVA_DISTRIBUTION"
|
||||
echo "현재는 temurin만 지원합니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
|
||||
install_packages() {
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends ca-certificates curl tar
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
$SUDO apk add --no-cache ca-certificates curl tar
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
$SUDO yum install -y ca-certificates curl tar
|
||||
else
|
||||
echo "지원하지 않는 러너입니다. ca-certificates, curl, tar를 설치할 수 없습니다."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
for command_name in curl tar; do
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
install_packages
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$JAVA_ARCHITECTURE" = "auto" ]; then
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) JAVA_ARCHITECTURE="x64" ;;
|
||||
aarch64|arm64) JAVA_ARCHITECTURE="aarch64" ;;
|
||||
*)
|
||||
echo "지원하지 않는 CPU 아키텍처입니다: $(uname -m)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$JAVA_ARCHITECTURE" in
|
||||
x64|aarch64) ;;
|
||||
*)
|
||||
echo "지원하지 않는 architecture 입력값입니다: $JAVA_ARCHITECTURE"
|
||||
echo "사용 가능: auto, x64, aarch64"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
DOWNLOAD_URL="https://api.adoptium.net/v3/binary/latest/${JAVA_VERSION}/ga/linux/${JAVA_ARCHITECTURE}/jdk/hotspot/normal/eclipse"
|
||||
INSTALL_ROOT="/opt/hostedtoolcache/manual-java"
|
||||
INSTALL_DIR="${INSTALL_ROOT}/temurin-${JAVA_VERSION}-${JAVA_ARCHITECTURE}"
|
||||
ARCHIVE_FILE="$(mktemp)"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$ARCHIVE_FILE"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ ! -x "$INSTALL_DIR/bin/java" ]; then
|
||||
$SUDO mkdir -p "$INSTALL_DIR"
|
||||
curl --fail --location --show-error --silent "$DOWNLOAD_URL" --output "$ARCHIVE_FILE"
|
||||
$SUDO tar -xzf "$ARCHIVE_FILE" --strip-components=1 -C "$INSTALL_DIR"
|
||||
fi
|
||||
|
||||
echo "JAVA_HOME=$INSTALL_DIR" >> "$GITHUB_ENV"
|
||||
echo "$INSTALL_DIR/bin" >> "$GITHUB_PATH"
|
||||
|
||||
"$INSTALL_DIR/bin/java" -version
|
||||
Reference in New Issue
Block a user