create-entity-app-server 0.1.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create.js +2 -2
- package/dist-create/template/.env +2 -2
- package/dist-create/template/.env.example +2 -2
- package/dist-create/template/.gateway-version +1 -1
- package/dist-create/template/app/plugins/ocr/cache.ts +1 -1
- package/dist-create/template/app/plugins/ocr/config.ts +1 -1
- package/dist-create/template/app/plugins/ocr/direction.ts +1 -1
- package/dist-create/template/app/plugins/ocr/dispatch.ts +1 -1
- package/dist-create/template/app/plugins/ocr/entity-adapter.ts +1 -1
- package/dist-create/template/app/plugins/ocr/errors.ts +1 -1
- package/dist-create/template/app/plugins/ocr/handlers.ts +1 -1
- package/dist-create/template/app/plugins/ocr/index.ts +1 -1
- package/dist-create/template/app/plugins/ocr/llm-parser.ts +1 -1
- package/dist-create/template/app/plugins/ocr/parsing-pipeline.ts +1 -1
- package/dist-create/template/app/plugins/ocr/pdf-converter.ts +1 -1
- package/dist-create/template/app/plugins/ocr/preprocessor.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/aws.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/azure.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/google.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/index.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/naver.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/tesseract.ts +1 -1
- package/dist-create/template/app/plugins/ocr/providers/upstage.ts +1 -1
- package/dist-create/template/app/plugins/ocr/quota.ts +1 -1
- package/dist-create/template/app/plugins/ocr/refiner.ts +1 -1
- package/dist-create/template/app/plugins/ocr/routes.ts +1 -1
- package/dist-create/template/app/plugins/ocr/service.ts +1 -1
- package/dist-create/template/app/plugins/ocr/template-loader.ts +1 -1
- package/dist-create/template/app/plugins/ocr/template-matcher.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/config.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/driver.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/index.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/parsed.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/store.ts +1 -1
- package/dist-create/template/app/plugins/ocr/types/template.ts +1 -1
- package/dist-create/template/app/plugins/ocr/utils.ts +1 -1
- package/dist-create/template/app/plugins/smtp/config.ts +41 -1
- package/dist-create/template/app/plugins/smtp/handlers.ts +52 -1
- package/dist-create/template/app/plugins/smtp/index.ts +33 -1
- package/dist-create/template/app/plugins/smtp/routes.ts +19 -1
- package/dist-create/template/app/plugins/smtp/types/config.ts +8 -1
- package/dist-create/template/app/plugins/smtp/types/index.ts +1 -1
- package/dist-create/template/docs/configs.md +12 -14
- package/dist-create/template/docs/getting-started.md +5 -3
- package/dist-create/template/docs/scripts-guide.md +51 -22
- package/dist-create/template/scripts/run.sh +229 -6
- package/dist-create/template/scripts/service-install.sh +174 -0
- package/dist-create/template/scripts/service-remove.sh +102 -0
- package/dist-create/template/scripts/update-server.sh +25 -20
- package/dist-create/template/system-api.js +1 -1
- package/dist-create/template/system.js +1 -1
- package/package.json +1 -1
|
@@ -19,6 +19,187 @@ cd "$PROJECT_ROOT"
|
|
|
19
19
|
PID_FILE="$PROJECT_ROOT/.gateway.pid"
|
|
20
20
|
LOG_FILE="$PROJECT_ROOT/logs/gateway.log"
|
|
21
21
|
|
|
22
|
+
is_placeholder_value() {
|
|
23
|
+
local value="$1"
|
|
24
|
+
|
|
25
|
+
[[ "$value" =~ ^(your-|replace-with|change-this) ]] && return 0
|
|
26
|
+
[[ "$value" = "shared-jwt-secret" ]] && return 0
|
|
27
|
+
[[ "$value" = "your-jwt-secret" ]] && return 0
|
|
28
|
+
[[ "$value" = "your-api-key" ]] && return 0
|
|
29
|
+
[[ "$value" = "your-hmac-secret" ]] && return 0
|
|
30
|
+
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
show_env_setup_error() {
|
|
35
|
+
local reason="$1"
|
|
36
|
+
|
|
37
|
+
echo "❌ .env 설정이 아직 완료되지 않았습니다."
|
|
38
|
+
echo " $reason"
|
|
39
|
+
echo ""
|
|
40
|
+
echo " 확인할 항목:"
|
|
41
|
+
echo " 1. .env 의 ENTITY_SERVER_URL"
|
|
42
|
+
echo " 2. .env 의 ENTITY_API_KEY"
|
|
43
|
+
echo " 3. .env 의 ENTITY_HMAC_SECRET (32자 이상)"
|
|
44
|
+
echo " 4. .env 의 JWT_SECRET (32자 이상, Entity Server와 동일)"
|
|
45
|
+
echo ""
|
|
46
|
+
echo " 예시 파일: .env.example"
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ensure_env_ready() {
|
|
51
|
+
if [ ! -f "$PROJECT_ROOT/.env" ]; then
|
|
52
|
+
show_env_setup_error ".env 파일이 없습니다. .env.example 을 참고해 .env 를 생성하세요."
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
local entity_server_url="${ENTITY_SERVER_URL:-}"
|
|
56
|
+
local entity_api_key="${ENTITY_API_KEY:-}"
|
|
57
|
+
local entity_hmac_secret="${ENTITY_HMAC_SECRET:-}"
|
|
58
|
+
local jwt_secret="${JWT_SECRET:-}"
|
|
59
|
+
|
|
60
|
+
if [ -z "$entity_server_url" ]; then
|
|
61
|
+
show_env_setup_error "ENTITY_SERVER_URL 이 비어 있습니다."
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [ -z "$entity_api_key" ]; then
|
|
65
|
+
show_env_setup_error "ENTITY_API_KEY 가 비어 있습니다."
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [ -z "$entity_hmac_secret" ]; then
|
|
69
|
+
show_env_setup_error "ENTITY_HMAC_SECRET 이 비어 있습니다."
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [ -z "$jwt_secret" ]; then
|
|
73
|
+
show_env_setup_error "JWT_SECRET 이 비어 있습니다."
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if is_placeholder_value "$entity_api_key"; then
|
|
77
|
+
show_env_setup_error "ENTITY_API_KEY 가 예시 값입니다. Entity Server에서 발급한 실제 키로 바꾸세요."
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if is_placeholder_value "$entity_hmac_secret"; then
|
|
81
|
+
show_env_setup_error "ENTITY_HMAC_SECRET 이 예시 값입니다. Entity Server에서 발급한 실제 시크릿으로 바꾸세요."
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if is_placeholder_value "$jwt_secret"; then
|
|
85
|
+
show_env_setup_error "JWT_SECRET 이 예시 값입니다. Entity Server와 동일한 실제 시크릿으로 바꾸세요."
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "${#entity_hmac_secret}" -lt 32 ]; then
|
|
89
|
+
show_env_setup_error "ENTITY_HMAC_SECRET 은 32자 이상이어야 합니다."
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [ "${#jwt_secret}" -lt 32 ]; then
|
|
93
|
+
show_env_setup_error "JWT_SECRET 은 32자 이상이어야 합니다."
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
verify_entity_server_credentials() {
|
|
98
|
+
local result
|
|
99
|
+
|
|
100
|
+
result=$(node <<'NODE'
|
|
101
|
+
const { createHmac, randomUUID } = require("node:crypto");
|
|
102
|
+
|
|
103
|
+
const baseUrl = String(process.env.ENTITY_SERVER_URL || "").replace(/\/$/, "");
|
|
104
|
+
const apiKey = String(process.env.ENTITY_API_KEY || "");
|
|
105
|
+
const hmacSecret = String(process.env.ENTITY_HMAC_SECRET || "");
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const healthUrl = `${baseUrl}/v1/health`;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const health = await fetch(healthUrl, { signal: AbortSignal.timeout(5000) });
|
|
112
|
+
if (!health.ok) {
|
|
113
|
+
console.log(`HEALTH_HTTP:${health.status}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.log(`HEALTH_ERROR:${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const method = "GET";
|
|
122
|
+
const path = "/v1/admin/configs";
|
|
123
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
124
|
+
const nonce = randomUUID();
|
|
125
|
+
const payload = Buffer.from(`${method}|${path}|${timestamp}|${nonce}|`, "utf8");
|
|
126
|
+
const signature = createHmac("sha256", hmacSecret).update(payload).digest("hex");
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
130
|
+
method,
|
|
131
|
+
headers: {
|
|
132
|
+
"X-API-Key": apiKey,
|
|
133
|
+
"X-Timestamp": timestamp,
|
|
134
|
+
"X-Nonce": nonce,
|
|
135
|
+
"X-Signature": signature,
|
|
136
|
+
},
|
|
137
|
+
signal: AbortSignal.timeout(5000),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (response.ok) {
|
|
141
|
+
console.log("OK");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const text = (await response.text().catch(() => "")).trim();
|
|
146
|
+
console.log(`AUTH_HTTP:${response.status}:${text}`);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.log(`AUTH_ERROR:${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main();
|
|
153
|
+
NODE
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
case "$result" in
|
|
157
|
+
OK)
|
|
158
|
+
return 0
|
|
159
|
+
;;
|
|
160
|
+
HEALTH_HTTP:*)
|
|
161
|
+
echo "❌ Entity Server에 연결했지만 헬스체크가 실패했습니다."
|
|
162
|
+
echo " 주소: ${ENTITY_SERVER_URL}"
|
|
163
|
+
echo " 응답: ${result#HEALTH_HTTP:}"
|
|
164
|
+
exit 1
|
|
165
|
+
;;
|
|
166
|
+
HEALTH_ERROR:*)
|
|
167
|
+
echo "❌ Entity Server에 연결할 수 없습니다."
|
|
168
|
+
echo " 주소: ${ENTITY_SERVER_URL}"
|
|
169
|
+
echo " 원인: ${result#HEALTH_ERROR:}"
|
|
170
|
+
echo " Entity Server가 실행 중인지 먼저 확인하세요."
|
|
171
|
+
exit 1
|
|
172
|
+
;;
|
|
173
|
+
AUTH_HTTP:401:*|AUTH_HTTP:403:*)
|
|
174
|
+
echo "❌ .env 의 Entity Server 인증 정보가 맞지 않습니다."
|
|
175
|
+
echo " ENTITY_API_KEY 또는 ENTITY_HMAC_SECRET 이 Entity Server 값과 다릅니다."
|
|
176
|
+
echo " Entity Server 관리자에서 발급된 API Key / HMAC Secret 을 다시 복사하세요."
|
|
177
|
+
exit 1
|
|
178
|
+
;;
|
|
179
|
+
AUTH_HTTP:404:*)
|
|
180
|
+
echo "❌ Entity Server 관리자 API 점검 경로를 찾지 못했습니다."
|
|
181
|
+
echo " 연결 대상: ${ENTITY_SERVER_URL}"
|
|
182
|
+
echo " /v1/admin/configs 가 없는 구버전 서버이거나 잘못된 주소일 수 있습니다."
|
|
183
|
+
exit 1
|
|
184
|
+
;;
|
|
185
|
+
AUTH_HTTP:*)
|
|
186
|
+
echo "❌ Entity Server 인증 점검 중 오류가 발생했습니다."
|
|
187
|
+
echo " 응답: ${result#AUTH_HTTP:}"
|
|
188
|
+
exit 1
|
|
189
|
+
;;
|
|
190
|
+
AUTH_ERROR:*)
|
|
191
|
+
echo "❌ Entity Server 인증 점검 요청을 보내지 못했습니다."
|
|
192
|
+
echo " 원인: ${result#AUTH_ERROR:}"
|
|
193
|
+
exit 1
|
|
194
|
+
;;
|
|
195
|
+
*)
|
|
196
|
+
echo "❌ Entity Server 사전 점검 결과를 해석하지 못했습니다."
|
|
197
|
+
echo " 응답: $result"
|
|
198
|
+
exit 1
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
}
|
|
202
|
+
|
|
22
203
|
ensure_runtime_dependencies_installed() {
|
|
23
204
|
if [ -d "$PROJECT_ROOT/node_modules/tsx" ]; then
|
|
24
205
|
return 0
|
|
@@ -58,6 +239,30 @@ find_gateway_pids() {
|
|
|
58
239
|
ss -ltnp 2>/dev/null | sed -n "s/.*:$port .*pid=\([0-9]\+\).*/\1/p" | sort -u
|
|
59
240
|
}
|
|
60
241
|
|
|
242
|
+
show_status() {
|
|
243
|
+
local pid=""
|
|
244
|
+
|
|
245
|
+
if [ -f "$PID_FILE" ]; then
|
|
246
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || true)
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
250
|
+
echo "✅ Entity App Server가 실행 중입니다 (pid: $pid)"
|
|
251
|
+
echo "로그: $LOG_FILE"
|
|
252
|
+
return 0
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
mapfile -t PORT_PIDS < <(find_gateway_pids)
|
|
256
|
+
if [ "${#PORT_PIDS[@]}" -gt 0 ]; then
|
|
257
|
+
echo "✅ Entity App Server가 실행 중입니다 (pid: ${PORT_PIDS[0]})"
|
|
258
|
+
echo "로그: $LOG_FILE"
|
|
259
|
+
return 0
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
echo "ℹ️ Entity App Server가 실행 중이지 않습니다."
|
|
263
|
+
return 1
|
|
264
|
+
}
|
|
265
|
+
|
|
61
266
|
show_port_in_use_error() {
|
|
62
267
|
local port
|
|
63
268
|
port=$(get_gateway_port)
|
|
@@ -109,11 +314,13 @@ _print_help() {
|
|
|
109
314
|
echo " 명령:"
|
|
110
315
|
echo " start 프로덕션 모드로 백그라운드 실행 (dist/system.js)"
|
|
111
316
|
echo " stop 실행 중인 서버 종료"
|
|
317
|
+
echo " status 실행 상태 확인"
|
|
112
318
|
echo " dev 개발 모드 실행 (tsx watch)"
|
|
113
319
|
echo ""
|
|
114
320
|
echo " 예제:"
|
|
115
321
|
echo " $0 start # 서버 시작"
|
|
116
322
|
echo " $0 stop # 서버 종료"
|
|
323
|
+
echo " $0 status # 실행 상태 확인"
|
|
117
324
|
echo " $0 dev # 개발 모드"
|
|
118
325
|
echo ""
|
|
119
326
|
if [ -f "$PID_FILE" ]; then
|
|
@@ -130,13 +337,18 @@ _print_help() {
|
|
|
130
337
|
|
|
131
338
|
if [ $# -eq 0 ]; then
|
|
132
339
|
_print_help
|
|
340
|
+
echo ""
|
|
341
|
+
echo "현재 상태:"
|
|
342
|
+
show_status
|
|
133
343
|
exit 0
|
|
134
344
|
fi
|
|
135
345
|
|
|
136
346
|
case "$1" in
|
|
137
347
|
start)
|
|
138
348
|
ensure_port_available
|
|
349
|
+
ensure_env_ready
|
|
139
350
|
ensure_runtime_dependencies_installed
|
|
351
|
+
verify_entity_server_credentials
|
|
140
352
|
|
|
141
353
|
if [ ! -f "dist/system.js" ]; then
|
|
142
354
|
echo "❌ dist/system.js가 없습니다. 먼저 빌드하세요:"
|
|
@@ -160,12 +372,17 @@ case "$1" in
|
|
|
160
372
|
echo $! > "$PID_FILE"
|
|
161
373
|
PID=$(cat "$PID_FILE")
|
|
162
374
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
375
|
+
sleep 0.3
|
|
376
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
377
|
+
echo "✅ Entity App Server가 백그라운드에서 시작되었습니다 (pid: $PID)"
|
|
378
|
+
echo "상태: ./run.sh status"
|
|
379
|
+
echo "중지: ./run.sh stop"
|
|
380
|
+
else
|
|
381
|
+
rm -f "$PID_FILE"
|
|
382
|
+
echo "❌ Entity App Server 백그라운드 시작에 실패했습니다"
|
|
383
|
+
echo "로그 확인: $LOG_FILE"
|
|
384
|
+
exit 1
|
|
385
|
+
fi
|
|
169
386
|
;;
|
|
170
387
|
|
|
171
388
|
stop)
|
|
@@ -195,13 +412,19 @@ case "$1" in
|
|
|
195
412
|
fi
|
|
196
413
|
;;
|
|
197
414
|
|
|
415
|
+
status)
|
|
416
|
+
show_status
|
|
417
|
+
;;
|
|
418
|
+
|
|
198
419
|
dev)
|
|
199
420
|
if [ -f "$PID_FILE" ] || [ -n "$(find_gateway_pids | head -n 1)" ]; then
|
|
200
421
|
echo " 기존 Entity App Server 정리 중..."
|
|
201
422
|
"$SCRIPT_DIR/run.sh" stop
|
|
202
423
|
fi
|
|
203
424
|
|
|
425
|
+
ensure_env_ready
|
|
204
426
|
ensure_runtime_dependencies_installed
|
|
427
|
+
verify_entity_server_credentials
|
|
205
428
|
|
|
206
429
|
echo " Starting Entity App Server (dev mode)..."
|
|
207
430
|
exec npm run dev
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
7
|
+
SERVER_CONFIG="$PROJECT_ROOT/configs/server.json"
|
|
8
|
+
|
|
9
|
+
if [ -f "$PROJECT_ROOT/.env" ]; then
|
|
10
|
+
LANGUAGE=$(grep '^LANGUAGE=' "$PROJECT_ROOT/.env" | cut -d '=' -f2)
|
|
11
|
+
fi
|
|
12
|
+
LANGUAGE=${LANGUAGE:-ko}
|
|
13
|
+
|
|
14
|
+
SERVICE_NAME="entity-app-server"
|
|
15
|
+
RUN_USER="${SUDO_USER:-$(stat -c '%U' "$PROJECT_ROOT")}"
|
|
16
|
+
RUN_GROUP="$(id -gn "$RUN_USER" 2>/dev/null || true)"
|
|
17
|
+
START_NOW=true
|
|
18
|
+
INTERACTIVE=false
|
|
19
|
+
|
|
20
|
+
load_service_name() {
|
|
21
|
+
local namespace=""
|
|
22
|
+
if [ -f "$SERVER_CONFIG" ]; then
|
|
23
|
+
namespace=$(sed -n 's/.*"namespace"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SERVER_CONFIG" | head -n 1)
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
namespace=$(echo "$namespace" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g')
|
|
27
|
+
|
|
28
|
+
if [ -z "$namespace" ]; then
|
|
29
|
+
SERVICE_NAME="entity-app-server"
|
|
30
|
+
elif [[ "$namespace" == "entity-app-server" ]] || [[ "$namespace" == *"-entity-app-server" ]]; then
|
|
31
|
+
SERVICE_NAME="$namespace"
|
|
32
|
+
else
|
|
33
|
+
SERVICE_NAME="${namespace}-entity-app-server"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
load_service_name
|
|
38
|
+
|
|
39
|
+
if [ $# -eq 0 ]; then
|
|
40
|
+
INTERACTIVE=true
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
for arg in "$@"; do
|
|
44
|
+
case "$arg" in
|
|
45
|
+
--user=*)
|
|
46
|
+
RUN_USER="${arg#*=}"
|
|
47
|
+
;;
|
|
48
|
+
--group=*)
|
|
49
|
+
RUN_GROUP="${arg#*=}"
|
|
50
|
+
;;
|
|
51
|
+
--no-start)
|
|
52
|
+
START_NOW=false
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
if [ "$LANGUAGE" = "en" ]; then
|
|
56
|
+
echo "❌ Unknown option: $arg"
|
|
57
|
+
else
|
|
58
|
+
echo "❌ 알 수 없는 옵션: $arg"
|
|
59
|
+
fi
|
|
60
|
+
exit 1
|
|
61
|
+
;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
if [ "$INTERACTIVE" = true ]; then
|
|
66
|
+
if [ "$LANGUAGE" = "en" ]; then
|
|
67
|
+
echo "[interactive] systemd service setup"
|
|
68
|
+
echo "service name: $SERVICE_NAME"
|
|
69
|
+
read -r -p "Run user [$RUN_USER]: " input
|
|
70
|
+
else
|
|
71
|
+
echo "[interactive] systemd 서비스 설정"
|
|
72
|
+
echo "서비스명: $SERVICE_NAME"
|
|
73
|
+
read -r -p "실행 사용자 [$RUN_USER]: " input
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
if [ -n "$input" ]; then
|
|
77
|
+
RUN_USER="$input"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [ -z "$RUN_GROUP" ]; then
|
|
81
|
+
RUN_GROUP="$(id -gn "$RUN_USER" 2>/dev/null || true)"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [ "$LANGUAGE" = "en" ]; then
|
|
85
|
+
read -r -p "Run group [$RUN_GROUP]: " input
|
|
86
|
+
else
|
|
87
|
+
read -r -p "실행 그룹 [$RUN_GROUP]: " input
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
if [ -n "$input" ]; then
|
|
91
|
+
RUN_GROUP="$input"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [ "$LANGUAGE" = "en" ]; then
|
|
95
|
+
read -r -p "Start service now? [Y/n]: " input
|
|
96
|
+
else
|
|
97
|
+
read -r -p "서비스를 즉시 시작할까요? [Y/n]: " input
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
input=$(echo "$input" | tr '[:upper:]' '[:lower:]')
|
|
101
|
+
if [ "$input" = "n" ] || [ "$input" = "no" ]; then
|
|
102
|
+
START_NOW=false
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ -z "$RUN_GROUP" ]; then
|
|
107
|
+
RUN_GROUP="$(id -gn "$RUN_USER")"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if ! id -u "$RUN_USER" >/dev/null 2>&1; then
|
|
111
|
+
echo "❌ 사용자를 찾을 수 없습니다: $RUN_USER"
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if ! getent group "$RUN_GROUP" >/dev/null 2>&1; then
|
|
116
|
+
echo "❌ 그룹을 찾을 수 없습니다: $RUN_GROUP"
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [ ! -x "$PROJECT_ROOT/scripts/run.sh" ]; then
|
|
121
|
+
chmod +x "$PROJECT_ROOT/scripts/run.sh"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ ! -f "$PROJECT_ROOT/system.js" ]; then
|
|
125
|
+
echo "❌ system.js 파일이 없습니다. create-entity-app-server 로 생성된 프로젝트 루트인지 확인하세요."
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
130
|
+
|
|
131
|
+
if [ "$EUID" -ne 0 ]; then
|
|
132
|
+
if command -v sudo >/dev/null 2>&1; then
|
|
133
|
+
SUDO_ARGS=("--user=$RUN_USER" "--group=$RUN_GROUP")
|
|
134
|
+
if [ "$START_NOW" = false ]; then
|
|
135
|
+
SUDO_ARGS+=("--no-start")
|
|
136
|
+
fi
|
|
137
|
+
exec sudo "$0" "${SUDO_ARGS[@]}"
|
|
138
|
+
fi
|
|
139
|
+
echo "❌ 이 스크립트는 root 권한이 필요합니다"
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
cat > "$UNIT_PATH" <<EOF
|
|
144
|
+
[Unit]
|
|
145
|
+
Description=Entity App Server
|
|
146
|
+
After=network.target
|
|
147
|
+
|
|
148
|
+
[Service]
|
|
149
|
+
Type=simple
|
|
150
|
+
User=$RUN_USER
|
|
151
|
+
Group=$RUN_GROUP
|
|
152
|
+
WorkingDirectory=$PROJECT_ROOT
|
|
153
|
+
EnvironmentFile=-$PROJECT_ROOT/.env
|
|
154
|
+
ExecStart=$PROJECT_ROOT/scripts/run.sh start
|
|
155
|
+
Restart=always
|
|
156
|
+
RestartSec=3
|
|
157
|
+
LimitNOFILE=65535
|
|
158
|
+
|
|
159
|
+
[Install]
|
|
160
|
+
WantedBy=multi-user.target
|
|
161
|
+
EOF
|
|
162
|
+
|
|
163
|
+
systemctl daemon-reload
|
|
164
|
+
systemctl enable "$SERVICE_NAME"
|
|
165
|
+
|
|
166
|
+
if [ "$START_NOW" = true ]; then
|
|
167
|
+
systemctl restart "$SERVICE_NAME"
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
echo "✅ 서비스 등록 완료: $SERVICE_NAME"
|
|
171
|
+
echo " Unit: $UNIT_PATH"
|
|
172
|
+
echo " 시작: sudo systemctl start $SERVICE_NAME"
|
|
173
|
+
echo " 중지: sudo systemctl stop $SERVICE_NAME"
|
|
174
|
+
echo " 상태: sudo systemctl status $SERVICE_NAME"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
7
|
+
SERVER_CONFIG="$PROJECT_ROOT/configs/server.json"
|
|
8
|
+
|
|
9
|
+
if [ -f "$PROJECT_ROOT/.env" ]; then
|
|
10
|
+
LANGUAGE=$(grep '^LANGUAGE=' "$PROJECT_ROOT/.env" | cut -d '=' -f2)
|
|
11
|
+
fi
|
|
12
|
+
LANGUAGE=${LANGUAGE:-ko}
|
|
13
|
+
|
|
14
|
+
SERVICE_NAME="entity-app-server"
|
|
15
|
+
INTERACTIVE=false
|
|
16
|
+
CONFIRMED=false
|
|
17
|
+
|
|
18
|
+
load_service_name() {
|
|
19
|
+
local namespace=""
|
|
20
|
+
if [ -f "$SERVER_CONFIG" ]; then
|
|
21
|
+
namespace=$(sed -n 's/.*"namespace"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SERVER_CONFIG" | head -n 1)
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
namespace=$(echo "$namespace" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g')
|
|
25
|
+
|
|
26
|
+
if [ -z "$namespace" ]; then
|
|
27
|
+
SERVICE_NAME="entity-app-server"
|
|
28
|
+
elif [[ "$namespace" == "entity-app-server" ]] || [[ "$namespace" == *"-entity-app-server" ]]; then
|
|
29
|
+
SERVICE_NAME="$namespace"
|
|
30
|
+
else
|
|
31
|
+
SERVICE_NAME="${namespace}-entity-app-server"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
load_service_name
|
|
36
|
+
|
|
37
|
+
if [ $# -eq 0 ]; then
|
|
38
|
+
INTERACTIVE=true
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case "$arg" in
|
|
43
|
+
--yes|-y)
|
|
44
|
+
CONFIRMED=true
|
|
45
|
+
;;
|
|
46
|
+
*)
|
|
47
|
+
echo "❌ 알 수 없는 옵션: $arg"
|
|
48
|
+
exit 1
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
if [ "$INTERACTIVE" = true ] && [ "$CONFIRMED" = false ]; then
|
|
54
|
+
echo "[interactive] systemd 서비스 제거"
|
|
55
|
+
echo "서비스명: $SERVICE_NAME"
|
|
56
|
+
read -r -p "'$SERVICE_NAME' 서비스를 제거할까요? [y/N]: " input
|
|
57
|
+
input=$(echo "$input" | tr '[:upper:]' '[:lower:]')
|
|
58
|
+
if [ "$input" != "y" ] && [ "$input" != "yes" ]; then
|
|
59
|
+
echo "취소되었습니다."
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
65
|
+
|
|
66
|
+
if [ "$EUID" -ne 0 ]; then
|
|
67
|
+
if command -v sudo >/dev/null 2>&1; then
|
|
68
|
+
exec sudo "$0" --yes
|
|
69
|
+
fi
|
|
70
|
+
echo "❌ 이 스크립트는 root 권한이 필요합니다"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
SERVICE_EXISTS=false
|
|
75
|
+
if systemctl list-unit-files | grep -q "^${SERVICE_NAME}\.service"; then
|
|
76
|
+
SERVICE_EXISTS=true
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ ! -f "$UNIT_PATH" ] && [ "$SERVICE_EXISTS" = false ]; then
|
|
80
|
+
echo "ℹ️ '$SERVICE_NAME' 서비스가 등록되어 있지 않습니다."
|
|
81
|
+
echo " 제거할 항목이 없습니다."
|
|
82
|
+
echo ""
|
|
83
|
+
echo " 서비스를 등록하려면:"
|
|
84
|
+
echo " sudo $(dirname "$0")/service-install.sh"
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "$SERVICE_EXISTS" = true ]; then
|
|
89
|
+
systemctl stop "$SERVICE_NAME" 2>/dev/null || true
|
|
90
|
+
systemctl disable "$SERVICE_NAME" 2>/dev/null || true
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
if [ -f "$UNIT_PATH" ]; then
|
|
94
|
+
rm -f "$UNIT_PATH"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
systemctl daemon-reload
|
|
98
|
+
|
|
99
|
+
echo "✅ 서비스 제거 완료: $SERVICE_NAME"
|
|
100
|
+
echo ""
|
|
101
|
+
echo " 서비스를 다시 등록하려면:"
|
|
102
|
+
echo " sudo $(dirname "$0")/service-install.sh"
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
# update-server.sh — Entity App Server 코어 업데이트
|
|
3
3
|
#
|
|
4
4
|
# 사용법:
|
|
5
|
-
# ./scripts/update-server.sh # 도움말
|
|
6
|
-
# ./scripts/update-server.sh version # 현재 코어 버전 + 최신 태그 확인
|
|
5
|
+
# ./scripts/update-server.sh # 도움말 + 현재/최신 버전 확인
|
|
7
6
|
# ./scripts/update-server.sh latest # 최신 태그로 업데이트
|
|
8
7
|
# ./scripts/update-server.sh 0.0.8 # 특정 버전으로 업데이트
|
|
9
8
|
# ./scripts/update-server.sh v0.0.8
|
|
@@ -15,7 +14,7 @@ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
|
15
14
|
PACKAGE_NAME="create-entity-app-server"
|
|
16
15
|
VERSION_FILE="$PROJECT_ROOT/.gateway-version"
|
|
17
16
|
PID_FILE="$PROJECT_ROOT/.gateway.pid"
|
|
18
|
-
MANAGED_SCRIPTS=("run.sh" "entity.sh" "reset-all.sh" "update-server.sh")
|
|
17
|
+
MANAGED_SCRIPTS=("run.sh" "entity.sh" "reset-all.sh" "update-server.sh" "service-install.sh" "service-remove.sh")
|
|
19
18
|
MANAGED_FILES=("system.js" "system-api.js" "tsconfig.json" ".env.example" ".gateway-version")
|
|
20
19
|
|
|
21
20
|
cd "$PROJECT_ROOT"
|
|
@@ -113,6 +112,28 @@ latest_gateway_version() {
|
|
|
113
112
|
echo "$latest"
|
|
114
113
|
}
|
|
115
114
|
|
|
115
|
+
print_version_status() {
|
|
116
|
+
local current
|
|
117
|
+
local latest
|
|
118
|
+
|
|
119
|
+
echo "🔍 버전 확인 중..."
|
|
120
|
+
current="$(current_gateway_version)"
|
|
121
|
+
latest="$(latest_gateway_version)"
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo " 현재 코어 버전: v${current}"
|
|
125
|
+
echo " 최신 패키지 버전: v${latest}"
|
|
126
|
+
echo ""
|
|
127
|
+
|
|
128
|
+
if [ "$current" = "$latest" ]; then
|
|
129
|
+
echo "✅ 최신 버전입니다."
|
|
130
|
+
elif is_semver "$current" && [ "$(compare_versions "$current" "$latest")" = "1" ]; then
|
|
131
|
+
echo "ℹ️ 현재 코어가 npm 최신 버전보다 더 새롭습니다."
|
|
132
|
+
else
|
|
133
|
+
echo "💡 업데이트 가능: ./scripts/update-server.sh latest"
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
package_tarball_url() {
|
|
117
138
|
local version="$1"
|
|
118
139
|
local tarball
|
|
@@ -370,29 +391,13 @@ case "$ARG" in
|
|
|
370
391
|
echo "update-server.sh — Entity App Server 코어 업데이트"
|
|
371
392
|
echo ""
|
|
372
393
|
echo "사용법:"
|
|
373
|
-
echo " ./scripts/update-server.sh version"
|
|
374
394
|
echo " ./scripts/update-server.sh latest"
|
|
375
395
|
echo " ./scripts/update-server.sh <버전>"
|
|
376
396
|
echo ""
|
|
377
397
|
echo "업데이트 대상: system.js / system-api.js / scripts / docs / tsconfig.json / .env.example / package.json(deps)"
|
|
378
398
|
echo "보존 대상: app/ / configs/ / .env / package.json의 name/version/private"
|
|
379
|
-
;;
|
|
380
|
-
|
|
381
|
-
"version")
|
|
382
|
-
echo "🔍 버전 확인 중..."
|
|
383
|
-
CURRENT="$(current_gateway_version)"
|
|
384
|
-
LATEST="$(latest_gateway_version)"
|
|
385
399
|
echo ""
|
|
386
|
-
|
|
387
|
-
echo " 최신 패키지 버전: v${LATEST}"
|
|
388
|
-
echo ""
|
|
389
|
-
if [ "$CURRENT" = "$LATEST" ]; then
|
|
390
|
-
echo "✅ 최신 버전입니다."
|
|
391
|
-
elif is_semver "$CURRENT" && [ "$(compare_versions "$CURRENT" "$LATEST")" = "1" ]; then
|
|
392
|
-
echo "ℹ️ 현재 코어가 npm 최신 버전보다 더 새롭습니다."
|
|
393
|
-
else
|
|
394
|
-
echo "💡 업데이트 가능: ./scripts/update-server.sh latest"
|
|
395
|
-
fi
|
|
400
|
+
print_version_status
|
|
396
401
|
;;
|
|
397
402
|
|
|
398
403
|
"latest")
|