update 03-22 09:28
This commit is contained in:
770
deploy.sh
Normal file
770
deploy.sh
Normal file
@@ -0,0 +1,770 @@
|
||||
#!/bin/bash
|
||||
#====================================================================
|
||||
# 솔메카 Smart Deploy v4.0
|
||||
# - 서버 자동 스캔 (CPU/RAM/포트/기존 배포)
|
||||
# - 최적 서버 자동 선택
|
||||
# - SSH + rsync + systemd 직접 배포 (Docker 없음)
|
||||
# - 배포 레지스트리 관리 (충돌 회피)
|
||||
#
|
||||
# 사용법:
|
||||
# deploy.sh . 현재 프로젝트 배포
|
||||
# deploy.sh status 전체 상태 확인
|
||||
# deploy.sh list 배포 목록
|
||||
# deploy.sh stop <이름> 서비스 중지
|
||||
# deploy.sh rm <이름> 서비스 삭제
|
||||
# deploy.sh logs <이름> 로그 보기
|
||||
#====================================================================
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
# ── 설정 ──────────────────────────────────────────────────────────
|
||||
SSH_KEY="$HOME/.ssh/id_ed25519"
|
||||
REGISTRY_FILE="C:/Users/User/.solmeca-deploy.json"
|
||||
PORT_RANGE_START=9100
|
||||
PORT_RANGE_END=9300
|
||||
|
||||
# 서버 목록: name|ip|ssh_user|sudo_pass|cpus|has_gpu|tags
|
||||
SERVERS=(
|
||||
"kakao-main|100.125.85.86|kakao|2828fire!!|8|yes|web,bot,dashboard"
|
||||
"worker-ai|100.118.136.45|server|9220fire!!|20|yes|ai,ml,bot,dashboard,heavy"
|
||||
"worker-downsys|100.121.159.128|downsys|2828fire!!|8|no|web,bot,dashboard,light"
|
||||
"worker-kakao2|100.70.23.63|kakao|2828fire!!|12|yes|ai,gpu,bot,dashboard,automation"
|
||||
)
|
||||
|
||||
# ── 색상 ──────────────────────────────────────────────────────────
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
|
||||
|
||||
log() { echo -e "${BLUE}$1${NC}"; }
|
||||
ok() { echo -e " ${GREEN}✓ $1${NC}"; }
|
||||
warn() { echo -e " ${YELLOW}! $1${NC}"; }
|
||||
err() { echo -e " ${RED}✗ $1${NC}"; }
|
||||
info() { echo -e " ${CYAN}$1${NC}"; }
|
||||
|
||||
# ── SSH 헬퍼 ──────────────────────────────────────────────────────
|
||||
run_ssh() {
|
||||
local user=$1 ip=$2; shift 2
|
||||
ssh -o ConnectTimeout=8 -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||
-i "$SSH_KEY" "${user}@${ip}" "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
# ── 레지스트리 관리 ───────────────────────────────────────────────
|
||||
|
||||
init_registry() {
|
||||
[ -f "$REGISTRY_FILE" ] || echo '{"deployments":{}}' > "$REGISTRY_FILE"
|
||||
}
|
||||
|
||||
get_registry() {
|
||||
cat "$REGISTRY_FILE"
|
||||
}
|
||||
|
||||
save_deployment() {
|
||||
local name=$1 server=$2 ip=$3 port=$4 type=$5 path=$6 user=$7
|
||||
local now
|
||||
now=$(date -Iseconds)
|
||||
python3 -c "
|
||||
import json,sys
|
||||
reg = json.load(open('$REGISTRY_FILE'))
|
||||
reg['deployments']['$name'] = {
|
||||
'server': '$server', 'ip': '$ip', 'port': $port, 'type': '$type',
|
||||
'remote_path': '$path', 'ssh_user': '$user',
|
||||
'deployed_at': '$now', 'status': 'running'
|
||||
}
|
||||
json.dump(reg, open('$REGISTRY_FILE','w'), indent=2, ensure_ascii=False)
|
||||
print('saved')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
remove_deployment() {
|
||||
local name=$1
|
||||
python3 -c "
|
||||
import json
|
||||
reg = json.load(open('$REGISTRY_FILE'))
|
||||
if '$name' in reg['deployments']:
|
||||
del reg['deployments']['$name']
|
||||
json.dump(reg, open('$REGISTRY_FILE','w'), indent=2, ensure_ascii=False)
|
||||
print('removed')
|
||||
else: print('not_found')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
update_md() {
|
||||
local md_file="C:/Users/User/Desktop/SERVER_ 운영/서버_배포_현황.md"
|
||||
python3 -c "
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
reg = json.load(open('$REGISTRY_FILE'))
|
||||
deps = reg.get('deployments', {})
|
||||
|
||||
servers = {}
|
||||
for name, d in sorted(deps.items()):
|
||||
srv = d.get('server', '?')
|
||||
if srv not in servers:
|
||||
servers[srv] = []
|
||||
port = d.get('port', 0)
|
||||
url = f\"http://{d['ip']}:{port}\" if port else '-'
|
||||
servers[srv].append({
|
||||
'name': name, 'type': d.get('type','?'),
|
||||
'port': port, 'url': url,
|
||||
'path': d.get('remote_path','?'),
|
||||
'date': d.get('deployed_at','?')[:10]
|
||||
})
|
||||
|
||||
lines = ['# 서버 배포 현황', f'', f'> Updated: {datetime.now().strftime(\"%Y-%m-%d %H:%M\")}', f'> Total: {len(deps)} services', '']
|
||||
|
||||
for srv in ['worker-ai','worker-downsys','worker-kakao2','kakao-main']:
|
||||
if srv not in servers:
|
||||
continue
|
||||
lines.append(f'## {srv}')
|
||||
lines.append('')
|
||||
lines.append('| Name | Type | Port | URL | Path | Date |')
|
||||
lines.append('|------|------|------|-----|------|------|')
|
||||
for s in servers[srv]:
|
||||
lines.append(f\"| {s['name']} | {s['type']} | {s['port'] or '-'} | {s['url']} | {s['path']} | {s['date']} |\")
|
||||
lines.append('')
|
||||
|
||||
with open('$md_file', 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_deployment_info() {
|
||||
local name=$1
|
||||
python3 -c "
|
||||
import json
|
||||
reg = json.load(open('$REGISTRY_FILE'))
|
||||
d = reg['deployments'].get('$name')
|
||||
if d: print(f\"{d['server']}|{d['ip']}|{d['port']}|{d['ssh_user']}|{d['remote_path']}\")
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
get_server_deployments() {
|
||||
local ip=$1
|
||||
python3 -c "
|
||||
import json
|
||||
reg = json.load(open('$REGISTRY_FILE'))
|
||||
ports = []
|
||||
for name, d in reg['deployments'].items():
|
||||
if d['ip'] == '$ip':
|
||||
ports.append(str(d['port']))
|
||||
print(' '.join(ports) if ports else '')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
# ── 프로젝트 타입 감지 ───────────────────────────────────────────
|
||||
|
||||
detect_project() {
|
||||
local dir=$1
|
||||
local type="unknown" cmd="" install=""
|
||||
|
||||
if [ -f "$dir/requirements.txt" ]; then
|
||||
type="python"
|
||||
install="pip install -r requirements.txt"
|
||||
# main 파일 찾기
|
||||
if [ -f "$dir/main.py" ]; then
|
||||
if grep -q "uvicorn\|fastapi\|flask" "$dir/main.py" 2>/dev/null; then
|
||||
cmd="python3 -m uvicorn main:app --host 0.0.0.0 --port __PORT__"
|
||||
else
|
||||
cmd="python3 main.py"
|
||||
fi
|
||||
elif [ -f "$dir/app.py" ]; then
|
||||
cmd="python3 app.py"
|
||||
elif [ -f "$dir/bot.py" ]; then
|
||||
cmd="python3 bot.py"
|
||||
elif [ -f "$dir/dashboard.py" ]; then
|
||||
cmd="python3 dashboard.py --port __PORT__"
|
||||
else
|
||||
# 아무 .py 찾기
|
||||
local pyfile
|
||||
pyfile=$(ls "$dir"/*.py 2>/dev/null | head -1)
|
||||
[ -n "$pyfile" ] && cmd="python3 $(basename "$pyfile")"
|
||||
fi
|
||||
elif [ -f "$dir/pyproject.toml" ]; then
|
||||
type="python"
|
||||
install="pip install ."
|
||||
cmd="python3 -m uvicorn main:app --host 0.0.0.0 --port __PORT__"
|
||||
elif [ -f "$dir/package.json" ]; then
|
||||
type="node"
|
||||
install="npm install"
|
||||
if grep -q '"start"' "$dir/package.json" 2>/dev/null; then
|
||||
cmd="npm start"
|
||||
elif [ -f "$dir/server.js" ]; then
|
||||
cmd="node server.js"
|
||||
elif [ -f "$dir/index.js" ]; then
|
||||
cmd="node index.js"
|
||||
elif [ -f "$dir/bot.js" ]; then
|
||||
cmd="node bot.js"
|
||||
else
|
||||
cmd="npm start"
|
||||
fi
|
||||
elif [ -f "$dir/go.mod" ]; then
|
||||
type="go"
|
||||
install="go build -o server ."
|
||||
cmd="./server"
|
||||
elif [ -f "$dir/Cargo.toml" ]; then
|
||||
type="rust"
|
||||
install="cargo build --release"
|
||||
cmd="./target/release/$(basename "$dir")"
|
||||
elif [ -f "$dir/index.html" ]; then
|
||||
type="static"
|
||||
install=""
|
||||
cmd="python3 -m http.server __PORT__"
|
||||
fi
|
||||
|
||||
echo "${type}|${cmd}|${install}"
|
||||
}
|
||||
|
||||
# ── 사용 가능한 포트 찾기 ─────────────────────────────────────────
|
||||
|
||||
find_available_port() {
|
||||
local ssh_user=$1 ip=$2
|
||||
|
||||
# 서버의 사용 중 포트
|
||||
local used_ports
|
||||
used_ports=$(run_ssh "$ssh_user" "$ip" "ss -tlnp | awk 'NR>1{print \$4}' | grep -oP ':\K[0-9]+' | sort -n | uniq" 2>/dev/null)
|
||||
|
||||
# 레지스트리에 기록된 포트
|
||||
local reg_ports
|
||||
reg_ports=$(get_server_deployments "$ip")
|
||||
|
||||
local all_used="${used_ports} ${reg_ports}"
|
||||
|
||||
for port in $(seq $PORT_RANGE_START $PORT_RANGE_END); do
|
||||
if ! echo "$all_used" | grep -qw "$port"; then
|
||||
echo "$port"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "0"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── 서버 리소스 조회 (병렬) ──────────────────────────────────────
|
||||
|
||||
declare -A SERVER_LOADS=()
|
||||
|
||||
fetch_all_server_loads() {
|
||||
local tmpdir
|
||||
tmpdir=$(mktemp -d)
|
||||
|
||||
for server_info in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r name ip ssh_user _ _ _ <<< "$server_info"
|
||||
(
|
||||
result=$(run_ssh "$ssh_user" "$ip" '
|
||||
read -r _ u1 n1 s1 i1 _ < /proc/stat; sleep 1; read -r _ u2 n2 s2 i2 _ < /proc/stat
|
||||
total=$(( (u2+n2+s2+i2) - (u1+n1+s1+i1) )); idle=$(( i2 - i1 ))
|
||||
[ "$total" -gt 0 ] && cpu_free=$(( idle * 100 / total )) || cpu_free=50
|
||||
read -r mem_total mem_avail <<< $(awk "/MemTotal/{t=int(\$2/1024)} /MemAvailable/{a=int(\$2/1024)} END{print t,a}" /proc/meminfo)
|
||||
containers=$(docker ps -q 2>/dev/null | wc -l)
|
||||
procs=$(ps aux --no-heading 2>/dev/null | wc -l)
|
||||
disk_pct=$(df / | awk "NR==2{gsub(/%/,\"\",\$5); print \$5}")
|
||||
echo "$cpu_free $mem_total $mem_avail $containers $procs $disk_pct"
|
||||
' 2>/dev/null || echo "50 0 0 0 0 0")
|
||||
echo "$result" > "${tmpdir}/${name}"
|
||||
) &
|
||||
done
|
||||
wait
|
||||
|
||||
for server_info in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r name _ _ _ _ _ <<< "$server_info"
|
||||
[ -f "${tmpdir}/${name}" ] && SERVER_LOADS["$name"]=$(cat "${tmpdir}/${name}") || SERVER_LOADS["$name"]="50 0 0 0 0 0"
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
|
||||
# ── 최적 서버 선택 ───────────────────────────────────────────────
|
||||
|
||||
select_best_server() {
|
||||
local app_type=$1
|
||||
local best_server="" best_score=-999
|
||||
|
||||
fetch_all_server_loads
|
||||
|
||||
for server_info in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r name ip ssh_user sudo_pass cpus has_gpu tags <<< "$server_info"
|
||||
local cpu_free mem_total mem_avail containers procs disk_pct
|
||||
read -r cpu_free mem_total mem_avail containers procs disk_pct <<< "${SERVER_LOADS[$name]:-50 0 0 0 0 0}"
|
||||
|
||||
local cpu_score=$((cpu_free))
|
||||
local mem_score=50
|
||||
[ "${mem_total:-0}" -gt 0 ] && mem_score=$((mem_avail * 100 / mem_total))
|
||||
|
||||
local disk_score=0
|
||||
[ "${disk_pct:-0}" -gt 90 ] && disk_score=-50
|
||||
[ "${disk_pct:-0}" -lt 50 ] && disk_score=10
|
||||
|
||||
# 배포 수 패널티 (이 서버에 이미 배포된 수)
|
||||
local deploy_count
|
||||
deploy_count=$(python3 -c "
|
||||
import json
|
||||
reg=json.load(open('$REGISTRY_FILE'))
|
||||
print(sum(1 for d in reg['deployments'].values() if d['ip']=='$ip'))
|
||||
" 2>/dev/null || echo 0)
|
||||
local deploy_penalty=$((deploy_count * 8))
|
||||
|
||||
# GPU 필요 시
|
||||
local gpu_score=0
|
||||
if [[ "$app_type" =~ ai|ml|gpu|cuda ]]; then
|
||||
[ "$has_gpu" = "no" ] && gpu_score=-200 || gpu_score=40
|
||||
fi
|
||||
|
||||
local score=$((cpu_score + mem_score + disk_score - deploy_penalty + gpu_score))
|
||||
|
||||
local gpu_label="--"
|
||||
[ "$has_gpu" = "yes" ] && gpu_label="GPU"
|
||||
printf " %-16s CPU=%3d%% RAM=%5dMB 배포=%d ${YELLOW}점수=%d${NC}\n" \
|
||||
"$name" "$cpu_free" "$mem_avail" "$deploy_count" "$score" >&2
|
||||
|
||||
[ "$score" -gt "$best_score" ] && best_score=$score && best_server="$server_info"
|
||||
done
|
||||
|
||||
echo "$best_server"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 메인 배포
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
|
||||
deploy() {
|
||||
local project_dir
|
||||
if [ "${1:-.}" = "." ]; then
|
||||
project_dir=$(pwd)
|
||||
else
|
||||
cd "$1" 2>/dev/null || { err "폴더 없음: $1"; return 1; }
|
||||
project_dir=$(pwd)
|
||||
fi
|
||||
|
||||
local project_name
|
||||
project_name=$(basename "$project_dir")
|
||||
|
||||
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BOLD}${CYAN} Deploy: ${project_name}${NC}"
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||
|
||||
init_registry
|
||||
|
||||
# ── [1] 프로젝트 감지 ──
|
||||
log "[1/6] 프로젝트 분석"
|
||||
local detect_result
|
||||
detect_result=$(detect_project "$project_dir")
|
||||
IFS='|' read -r proj_type proj_cmd proj_install <<< "$detect_result"
|
||||
|
||||
if [ "$proj_type" = "unknown" ] || [ -z "$proj_cmd" ]; then
|
||||
err "프로젝트 타입을 감지할 수 없습니다"
|
||||
info "main.py, bot.py, package.json, index.html 등이 필요합니다"
|
||||
return 1
|
||||
fi
|
||||
ok "타입: ${proj_type}"
|
||||
ok "실행: ${proj_cmd}"
|
||||
|
||||
# ── [2] 기존 배포 확인 ──
|
||||
log "[2/6] 기존 배포 확인"
|
||||
local existing
|
||||
existing=$(get_deployment_info "$project_name")
|
||||
|
||||
local srv_name srv_ip srv_user srv_pass srv_cpus srv_gpu srv_tags
|
||||
local port remote_path
|
||||
|
||||
if [ -n "$existing" ]; then
|
||||
IFS='|' read -r old_server old_ip old_port old_user old_path <<< "$existing"
|
||||
warn "기존 배포 발견: ${old_server} 포트 ${old_port} → 업데이트합니다"
|
||||
# 기존 서버 정보 찾기
|
||||
for s in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r sn si su sp sc sg st <<< "$s"
|
||||
if [ "$si" = "$old_ip" ]; then
|
||||
srv_name=$sn; srv_ip=$si; srv_user=$su; srv_pass=$sp; srv_cpus=$sc; srv_gpu=$sg; srv_tags=$st
|
||||
break
|
||||
fi
|
||||
done
|
||||
port=$old_port
|
||||
remote_path=$old_path
|
||||
else
|
||||
# ── [3] 서버 선택 ──
|
||||
log "[3/6] 서버 선택"
|
||||
info "서버 스캔 중..."
|
||||
local selected
|
||||
selected=$(select_best_server "$proj_type")
|
||||
IFS='|' read -r srv_name srv_ip srv_user srv_pass srv_cpus srv_gpu srv_tags <<< "$selected"
|
||||
echo -e " ${GREEN}${BOLD}>>> ${srv_name} (${srv_ip})${NC}"
|
||||
|
||||
# 포트 할당
|
||||
port=$(find_available_port "$srv_user" "$srv_ip")
|
||||
if [ "$port" = "0" ]; then
|
||||
err "사용 가능한 포트가 없습니다"
|
||||
return 1
|
||||
fi
|
||||
ok "포트: ${port}"
|
||||
remote_path="/opt/solmeca/${project_name}"
|
||||
fi
|
||||
|
||||
# 실행 명령어에 포트 적용
|
||||
proj_cmd="${proj_cmd//__PORT__/$port}"
|
||||
|
||||
# 환경변수로 포트 전달 (PORT 환경변수)
|
||||
local env_port="PORT=${port}"
|
||||
|
||||
# ── [4] 코드 전송 ──
|
||||
log "[4/6] 코드 전송"
|
||||
|
||||
# 원격 디렉토리 생성
|
||||
run_ssh "$srv_user" "$srv_ip" "mkdir -p '${remote_path}' 2>/dev/null || (echo '${srv_pass}' | sudo -S mkdir -p '${remote_path}' && echo '${srv_pass}' | sudo -S chown \$(whoami) '${remote_path}') 2>/dev/null"
|
||||
|
||||
# tar로 묶어서 전송 (.git, node_modules 등 제외)
|
||||
tar czf /tmp/_solmeca_deploy.tar.gz \
|
||||
--exclude='.git' --exclude='node_modules' --exclude='__pycache__' \
|
||||
--exclude='.venv' --exclude='venv' --exclude='.env' \
|
||||
--exclude='target' --exclude='dist' --exclude='build' \
|
||||
-C "${project_dir}" . 2>/dev/null
|
||||
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=ERROR -i "$SSH_KEY" \
|
||||
/tmp/_solmeca_deploy.tar.gz "${srv_user}@${srv_ip}:/tmp/_solmeca_deploy.tar.gz" 2>/dev/null
|
||||
|
||||
run_ssh "$srv_user" "$srv_ip" "cd '${remote_path}' && tar xzf /tmp/_solmeca_deploy.tar.gz && rm -f /tmp/_solmeca_deploy.tar.gz"
|
||||
rm -f /tmp/_solmeca_deploy.tar.gz
|
||||
|
||||
ok "코드 전송 완료"
|
||||
|
||||
# ── [5] 설치 + systemd 서비스 생성 ──
|
||||
log "[5/6] 설치 & 서비스 등록"
|
||||
|
||||
# 서비스 이름
|
||||
local service_name="solmeca-${project_name}"
|
||||
|
||||
# ExecStart 명령어 결정
|
||||
local exec_cmd="$proj_cmd"
|
||||
if [ "$proj_type" = "python" ]; then
|
||||
exec_cmd=$(echo "$proj_cmd" | sed "s|python3|${remote_path}/venv/bin/python3|")
|
||||
fi
|
||||
|
||||
# 로컬에서 서비스 파일 생성
|
||||
cat > /tmp/_solmeca_service.tmp << EOF
|
||||
[Unit]
|
||||
Description=Solmeca - ${project_name}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=${remote_path}
|
||||
Environment=${env_port}
|
||||
ExecStart=${exec_cmd}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=${service_name}
|
||||
User=${srv_user}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 서비스 파일 전송
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=ERROR -i "$SSH_KEY" \
|
||||
/tmp/_solmeca_service.tmp "${srv_user}@${srv_ip}:/tmp/_solmeca_service.tmp" 2>/dev/null
|
||||
rm -f /tmp/_solmeca_service.tmp
|
||||
|
||||
# 원격: 의존성 설치 + 서비스 등록 + 시작
|
||||
run_ssh "$srv_user" "$srv_ip" "
|
||||
cd '${remote_path}'
|
||||
if [ '${proj_type}' = 'python' ]; then
|
||||
python3 -m venv venv 2>/dev/null || true
|
||||
. venv/bin/activate 2>/dev/null || true
|
||||
pip install -q -r requirements.txt 2>/dev/null || pip install -q . 2>/dev/null || true
|
||||
elif [ '${proj_type}' = 'node' ]; then
|
||||
npm install --omit=dev 2>/dev/null || true
|
||||
elif [ '${proj_type}' = 'go' ]; then
|
||||
go build -o server . 2>/dev/null || true
|
||||
fi
|
||||
echo '${srv_pass}' | sudo -S cp /tmp/_solmeca_service.tmp /etc/systemd/system/${service_name}.service 2>/dev/null
|
||||
rm -f /tmp/_solmeca_service.tmp
|
||||
echo '${srv_pass}' | sudo -S systemctl daemon-reload 2>/dev/null
|
||||
echo '${srv_pass}' | sudo -S systemctl enable ${service_name} 2>/dev/null
|
||||
echo '${srv_pass}' | sudo -S systemctl restart ${service_name} 2>/dev/null
|
||||
"
|
||||
|
||||
# 서비스 상태 확인
|
||||
sleep 2
|
||||
local svc_status
|
||||
svc_status=$(run_ssh "$srv_user" "$srv_ip" "systemctl is-active ${service_name}" 2>/dev/null || echo "failed")
|
||||
|
||||
if [ "$svc_status" = "active" ]; then
|
||||
ok "서비스 실행 중: ${service_name}"
|
||||
else
|
||||
warn "서비스 상태: ${svc_status}"
|
||||
info "로그 확인: deploy.sh logs ${project_name}"
|
||||
fi
|
||||
|
||||
# ── [6] 레지스트리 기록 ──
|
||||
log "[6/6] 배포 기록"
|
||||
save_deployment "$project_name" "$srv_name" "$srv_ip" "$port" "$proj_type" "$remote_path" "$srv_user"
|
||||
update_md
|
||||
ok "기록 완료"
|
||||
|
||||
# 배포 후 실시간 정보 수집
|
||||
sleep 1
|
||||
local proc_info
|
||||
proc_info=$(run_ssh "$srv_user" "$srv_ip" "
|
||||
PID=\$(systemctl show solmeca-${project_name} --property=MainPID --value 2>/dev/null)
|
||||
if [ -n \"\$PID\" ] && [ \"\$PID\" != '0' ]; then
|
||||
UPTIME=\$(ps -o etime= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
MEM=\$(ps -o rss= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
CPU=\$(ps -o %cpu= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
echo \"\$PID|\${UPTIME:-0}|\${MEM:-0}|\${CPU:-0}\"
|
||||
else
|
||||
echo '0|0|0|0'
|
||||
fi
|
||||
" 2>/dev/null || echo "0|0|0|0")
|
||||
|
||||
local p_pid p_uptime p_mem p_cpu
|
||||
IFS='|' read -r p_pid p_uptime p_mem p_cpu <<< "$proc_info"
|
||||
local mem_mb=0
|
||||
[ "${p_mem:-0}" -gt 0 ] 2>/dev/null && mem_mb=$((p_mem / 1024))
|
||||
|
||||
echo -e "\n${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
if [ "$svc_status" = "active" ]; then
|
||||
echo -e "${GREEN}${BOLD} Deploy OK${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}${BOLD} Deployed (starting...)${NC}"
|
||||
fi
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
printf " %-12s %s\n" "App" "${project_name}"
|
||||
printf " %-12s %s\n" "Type" "${proj_type}"
|
||||
printf " %-12s %s (%s)\n" "Server" "${srv_name}" "${srv_ip}"
|
||||
printf " %-12s %s\n" "Port" "${port}"
|
||||
printf " %-12s %s\n" "URL" "http://${srv_ip}:${port}"
|
||||
printf " %-12s %s\n" "Status" "${svc_status}"
|
||||
printf " %-12s %s\n" "PID" "${p_pid}"
|
||||
printf " %-12s %s\n" "Uptime" "${p_uptime}"
|
||||
printf " %-12s %s%%\n" "CPU" "${p_cpu}"
|
||||
printf " %-12s %sMB\n" "Memory" "${mem_mb}"
|
||||
printf " %-12s %s\n" "Path" "${remote_path}"
|
||||
echo ""
|
||||
echo -e " ${DIM}deploy.sh logs ${project_name} -- view logs${NC}"
|
||||
echo -e " ${DIM}deploy.sh stop ${project_name} -- stop service${NC}"
|
||||
echo -e " ${DIM}deploy.sh rm ${project_name} -- remove service${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── status 명령 ───────────────────────────────────────────────────
|
||||
|
||||
status_cmd() {
|
||||
init_registry
|
||||
|
||||
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${BOLD}${CYAN} 서버 & 배포 상태${NC}"
|
||||
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||
|
||||
log "서버 상태"
|
||||
fetch_all_server_loads
|
||||
|
||||
printf " ${BOLD}%-16s %6s %10s %6s %4s${NC}\n" "서버" "CPU" "RAM여유" "디스크" "GPU"
|
||||
echo " ─────────────────────────────────────────────"
|
||||
|
||||
for server_info in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r name ip _ cpus has_gpu _ <<< "$server_info"
|
||||
local cpu_free mem_total mem_avail _ _ disk_pct
|
||||
read -r cpu_free mem_total mem_avail _ _ disk_pct <<< "${SERVER_LOADS[$name]:-0 0 0 0 0 0}"
|
||||
|
||||
local gpu_label=" "
|
||||
[ "$has_gpu" = "yes" ] && gpu_label="GPU"
|
||||
local cpu_color=$GREEN
|
||||
[ "${cpu_free:-0}" -lt 30 ] && cpu_color=$YELLOW
|
||||
[ "${cpu_free:-0}" -lt 10 ] && cpu_color=$RED
|
||||
|
||||
printf " %-16s ${cpu_color}%5d%%${NC} %5d/%4dMB %5d%% %4s\n" \
|
||||
"$name" "$cpu_free" "$mem_avail" "$mem_total" "$disk_pct" "$gpu_label"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log "배포 목록"
|
||||
|
||||
local deployments
|
||||
deployments=$(python3 -c "
|
||||
import json
|
||||
reg=json.load(open('$REGISTRY_FILE'))
|
||||
if not reg['deployments']:
|
||||
print(' (없음)')
|
||||
else:
|
||||
print(f' {\"Name\":<20s} {\"Server\":<16s} {\"Port\":<6s} {\"Type\":<8s} {\"URL\"}')
|
||||
print(' ' + '-' * 70)
|
||||
for name, d in sorted(reg['deployments'].items()):
|
||||
print(f' {name:<20s} {d[\"server\"]:<16s} {d[\"port\"]:<6d} {d[\"type\"]:<8s} http://{d[\"ip\"]}:{d[\"port\"]}')
|
||||
" 2>/dev/null)
|
||||
echo "$deployments"
|
||||
|
||||
# 실시간 서비스 상세 정보
|
||||
echo ""
|
||||
log "Service Details"
|
||||
|
||||
local has_services=false
|
||||
for server_info in "${SERVERS[@]}"; do
|
||||
IFS='|' read -r sname sip suser _ _ _ _ <<< "$server_info"
|
||||
local services
|
||||
services=$(run_ssh "$suser" "$sip" "systemctl list-units 'solmeca-*' --no-pager --plain 2>/dev/null | grep solmeca | awk '{print \$1, \$3}'" 2>/dev/null)
|
||||
if [ -n "$services" ]; then
|
||||
has_services=true
|
||||
while IFS= read -r line; do
|
||||
local svc_name svc_status
|
||||
svc_name=$(echo "$line" | awk '{print $1}' | sed 's/solmeca-//;s/.service//')
|
||||
svc_status=$(echo "$line" | awk '{print $2}')
|
||||
|
||||
# 프로세스 상세 정보 조회
|
||||
local proc_detail
|
||||
proc_detail=$(run_ssh "$suser" "$sip" "
|
||||
PID=\$(systemctl show solmeca-${svc_name} --property=MainPID --value 2>/dev/null)
|
||||
if [ -n \"\$PID\" ] && [ \"\$PID\" != '0' ]; then
|
||||
UPTIME=\$(ps -o etime= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
MEM=\$(ps -o rss= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
CPU=\$(ps -o %cpu= -p \$PID 2>/dev/null | tr -d ' ')
|
||||
echo \"\$PID|\${UPTIME:-?}|\${MEM:-0}|\${CPU:-0}\"
|
||||
else
|
||||
echo '0|?|0|0'
|
||||
fi
|
||||
" 2>/dev/null || echo "0|?|0|0")
|
||||
|
||||
local p_pid p_up p_mem p_cpu
|
||||
IFS='|' read -r p_pid p_up p_mem p_cpu <<< "$proc_detail"
|
||||
local mem_mb=0
|
||||
[ "${p_mem:-0}" -gt 0 ] 2>/dev/null && mem_mb=$((p_mem / 1024))
|
||||
|
||||
# 레지스트리에서 포트 조회
|
||||
local reg_port
|
||||
reg_port=$(python3 -c "
|
||||
import json
|
||||
reg=json.load(open('$REGISTRY_FILE'))
|
||||
d=reg['deployments'].get('$svc_name',{})
|
||||
print(d.get('port','?'))
|
||||
" 2>/dev/null || echo "?")
|
||||
|
||||
if [ "$svc_status" = "active" ]; then
|
||||
echo -e " ${GREEN}[ON]${NC} ${BOLD}${svc_name}${NC}"
|
||||
else
|
||||
echo -e " ${RED}[--]${NC} ${BOLD}${svc_name}${NC}"
|
||||
fi
|
||||
echo -e " Server: ${sname} (${sip})"
|
||||
echo -e " URL: ${CYAN}http://${sip}:${reg_port}${NC}"
|
||||
echo -e " PID: ${p_pid} Uptime: ${p_up} CPU: ${p_cpu}% RAM: ${mem_mb}MB"
|
||||
echo ""
|
||||
done <<< "$services"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $has_services; then
|
||||
echo " (no services deployed)"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── list 명령 ─────────────────────────────────────────────────────
|
||||
|
||||
list_cmd() {
|
||||
init_registry
|
||||
python3 -c "
|
||||
import json
|
||||
reg=json.load(open('$REGISTRY_FILE'))
|
||||
if not reg['deployments']:
|
||||
print('배포된 프로젝트가 없습니다.')
|
||||
else:
|
||||
for name, d in sorted(reg['deployments'].items()):
|
||||
print(f\"{name:20s} {d['server']:16s} 포트:{d['port']:<5d} {d['type']:8s} http://{d['ip']}:{d['port']}\")
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
# ── stop 명령 ─────────────────────────────────────────────────────
|
||||
|
||||
stop_cmd() {
|
||||
local name=$1
|
||||
init_registry
|
||||
local info
|
||||
info=$(get_deployment_info "$name")
|
||||
if [ -z "$info" ]; then
|
||||
err "배포를 찾을 수 없습니다: $name"
|
||||
return 1
|
||||
fi
|
||||
IFS='|' read -r _ ip _ user _ <<< "$info"
|
||||
# 서버 비밀번호 찾기
|
||||
local pass=""
|
||||
for s in "${SERVERS[@]}"; do IFS='|' read -r _ si _ sp _ _ _ <<< "$s"; [ "$si" = "$ip" ] && pass=$sp && break; done
|
||||
run_ssh "$user" "$ip" "echo '${pass}' | sudo -S systemctl stop solmeca-${name}" 2>/dev/null
|
||||
ok "중지됨: ${name}"
|
||||
}
|
||||
|
||||
# ── rm 명령 ───────────────────────────────────────────────────────
|
||||
|
||||
rm_cmd() {
|
||||
local name=$1
|
||||
init_registry
|
||||
local info
|
||||
info=$(get_deployment_info "$name")
|
||||
if [ -z "$info" ]; then
|
||||
err "배포를 찾을 수 없습니다: $name"
|
||||
return 1
|
||||
fi
|
||||
IFS='|' read -r _ ip _ user path <<< "$info"
|
||||
local pass=""
|
||||
for s in "${SERVERS[@]}"; do IFS='|' read -r _ si _ sp _ _ _ <<< "$s"; [ "$si" = "$ip" ] && pass=$sp && break; done
|
||||
run_ssh "$user" "$ip" "
|
||||
echo '${pass}' | sudo -S systemctl stop solmeca-${name} 2>/dev/null
|
||||
echo '${pass}' | sudo -S systemctl disable solmeca-${name} 2>/dev/null
|
||||
echo '${pass}' | sudo -S rm -f /etc/systemd/system/solmeca-${name}.service
|
||||
echo '${pass}' | sudo -S systemctl daemon-reload
|
||||
echo '${pass}' | sudo -S rm -rf '${path}'
|
||||
" 2>/dev/null
|
||||
remove_deployment "$name"
|
||||
update_md
|
||||
ok "삭제됨: ${name}"
|
||||
}
|
||||
|
||||
# ── logs 명령 ─────────────────────────────────────────────────────
|
||||
|
||||
logs_cmd() {
|
||||
local name=$1
|
||||
init_registry
|
||||
local info
|
||||
info=$(get_deployment_info "$name")
|
||||
if [ -z "$info" ]; then
|
||||
err "배포를 찾을 수 없습니다: $name"
|
||||
return 1
|
||||
fi
|
||||
IFS='|' read -r _ ip _ user _ <<< "$info"
|
||||
local pass=""
|
||||
for s in "${SERVERS[@]}"; do IFS='|' read -r _ si _ sp _ _ _ <<< "$s"; [ "$si" = "$ip" ] && pass=$sp && break; done
|
||||
run_ssh "$user" "$ip" "echo '${pass}' | sudo -S journalctl -u solmeca-${name} -n 50 --no-pager"
|
||||
}
|
||||
|
||||
# ── 사용법 ────────────────────────────────────────────────────────
|
||||
|
||||
usage() {
|
||||
echo -e "${BOLD}${CYAN}솔메카 Deploy v4.0${NC}\n"
|
||||
echo "사용법:"
|
||||
echo " deploy.sh . 현재 프로젝트 배포"
|
||||
echo " deploy.sh status 서버/배포 상태"
|
||||
echo " deploy.sh list 배포 목록"
|
||||
echo " deploy.sh logs <이름> 로그 보기"
|
||||
echo " deploy.sh stop <이름> 서비스 중지"
|
||||
echo " deploy.sh rm <이름> 서비스 삭제"
|
||||
echo ""
|
||||
echo "지원 프로젝트:"
|
||||
echo " Python (main.py, bot.py, app.py + requirements.txt)"
|
||||
echo " Node.js (server.js, bot.js + package.json)"
|
||||
echo " Go (main.go + go.mod)"
|
||||
echo " Rust (src/main.rs + Cargo.toml)"
|
||||
echo " Static (index.html)"
|
||||
}
|
||||
|
||||
# ── 실행 ──────────────────────────────────────────────────────────
|
||||
case "${1:---help}" in
|
||||
.) deploy "$(pwd)" ;;
|
||||
status|st) status_cmd ;;
|
||||
list|ls) list_cmd ;;
|
||||
stop) stop_cmd "${2:?이름을 입력하세요}" ;;
|
||||
rm|remove) rm_cmd "${2:?이름을 입력하세요}" ;;
|
||||
logs|log) logs_cmd "${2:?이름을 입력하세요}" ;;
|
||||
-h|--help|help) usage ;;
|
||||
*) deploy "$1" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user