create-entity-server 0.9.14 → 0.9.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-entity-server",
3
- "version": "0.9.14",
3
+ "version": "0.9.15",
4
4
  "description": "Create a new entity-server project in one command — like create-react-app or create-vite.",
5
5
  "keywords": [
6
6
  "entity-server",
@@ -1,7 +1,9 @@
1
1
  #!/bin/bash
2
2
  # Entity Server - Run Script
3
3
 
4
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
+ # 심볼릭 링크 경로(/home/codeshop → /data/codeshop) 실행돼도 systemd ExecStart 의 실경로와
5
+ # 매칭되도록 pwd -P 로 실경로로 정규화한다. (find_systemd_service 의 ExecStart 매칭에 필요)
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
5
7
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
6
8
 
7
9
  cd "$PROJECT_ROOT"
@@ -45,6 +47,15 @@ find_systemd_service() {
45
47
  [ -z "$namespace" ] && namespace="default"
46
48
 
47
49
  local svc_name="${namespace}-entity-server.service"
50
+
51
+ # 유닛이 실제로 로드되어 있는지 먼저 확인한다.
52
+ # (등록되지 않은 유닛은 LoadState=not-found 이며, 이 경우 systemd 서비스로 취급하지 않는다.)
53
+ local load_state=""
54
+ load_state=$(systemctl show -p LoadState --value "$svc_name" 2>/dev/null || true)
55
+ if [ "$load_state" != "loaded" ]; then
56
+ return 1
57
+ fi
58
+
48
59
  local exec_start=""
49
60
  exec_start=$(systemctl show -p ExecStart --value "$svc_name" 2>/dev/null || true)
50
61
 
@@ -135,6 +146,12 @@ is_pid_running() {
135
146
  return 1
136
147
  fi
137
148
 
149
+ # 리눅스: /proc 로 먼저 확인한다. kill -0 은 다른 사용자(root) 프로세스에 대해
150
+ # EPERM 으로 실패해 "없음"으로 오판하므로, 존재 확인에 의존하면 안 된다.
151
+ if [ -d "/proc/$pid" ]; then
152
+ return 0
153
+ fi
154
+
138
155
  if kill -0 "$pid" 2>/dev/null; then
139
156
  return 0
140
157
  fi
@@ -148,6 +165,18 @@ is_pid_running() {
148
165
  return 1
149
166
  }
150
167
 
168
+ # PID를 종료합니다. 권한이 부족하면(다른 사용자/root 소유) sudo 로 재시도합니다.
169
+ kill_pid_signal() {
170
+ local sig="$1"
171
+ local pid="$2"
172
+
173
+ if kill "$sig" "$pid" 2>/dev/null; then
174
+ return 0
175
+ fi
176
+ # 권한 부족 등으로 실패하면 sudo 로 한 번 더 시도한다.
177
+ sudo -n kill "$sig" "$pid" 2>/dev/null || true
178
+ }
179
+
151
180
  # PID를 종료하고 남아 있으면 강제 종료까지 진행합니다.
152
181
  force_stop_pid() {
153
182
  local pid="$1"
@@ -156,7 +185,7 @@ force_stop_pid() {
156
185
  return 1
157
186
  fi
158
187
 
159
- kill "$pid" 2>/dev/null || true
188
+ kill_pid_signal -TERM "$pid"
160
189
  if is_pid_running "$pid" && has_command powershell.exe; then
161
190
  powershell.exe -NoProfile -Command "Stop-Process -Id $pid -ErrorAction SilentlyContinue" >/dev/null 2>&1 || true
162
191
  fi
@@ -168,7 +197,7 @@ force_stop_pid() {
168
197
  sleep 0.1
169
198
  done
170
199
 
171
- kill -9 "$pid" 2>/dev/null || true
200
+ kill_pid_signal -KILL "$pid"
172
201
  if is_pid_running "$pid" && has_command powershell.exe; then
173
202
  powershell.exe -NoProfile -Command "Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue" >/dev/null 2>&1 || true
174
203
  fi
@@ -318,7 +347,37 @@ find_server_pids() {
318
347
  port=$(get_server_value "port" "3400")
319
348
 
320
349
  if has_command ss; then
321
- ss -ltnp 2>/dev/null | sed -n "s/.*:$port .*pid=\([0-9]\+\).*/\1/p" | sort -u
350
+ local ss_pids
351
+ ss_pids="$(ss -ltnp 2>/dev/null | sed -n "s/.*:$port .*pid=\([0-9]\+\).*/\1/p" | sort -u)"
352
+ if [ -n "$ss_pids" ]; then
353
+ echo "$ss_pids"
354
+ return
355
+ fi
356
+
357
+ # ss 가 LISTEN 은 보이는데 PID 가 비어 있으면, 다른 사용자(root 등) 소유 소켓이라
358
+ # 일반 권한으로 PID 가 안 보이는 경우다. sudo 로 한 번 더, 그 다음 lsof/fuser 로 보강한다.
359
+ if ss -ltn 2>/dev/null | grep -q ":$port "; then
360
+ local sudo_pids
361
+ sudo_pids="$(sudo -n ss -ltnp 2>/dev/null | sed -n "s/.*:$port .*pid=\([0-9]\+\).*/\1/p" | sort -u)"
362
+ if [ -n "$sudo_pids" ]; then
363
+ echo "$sudo_pids"
364
+ return
365
+ fi
366
+
367
+ if has_command lsof; then
368
+ local lsof_pids
369
+ lsof_pids="$(sudo -n lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | awk '/^[0-9]+$/' | sort -u)"
370
+ if [ -n "$lsof_pids" ]; then
371
+ echo "$lsof_pids"
372
+ return
373
+ fi
374
+ fi
375
+
376
+ if has_command fuser; then
377
+ sudo -n fuser "$port"/tcp 2>/dev/null | tr ' ' '\n' | awk '/^[0-9]+$/' | sort -u
378
+ return
379
+ fi
380
+ fi
322
381
  return
323
382
  fi
324
383
 
@@ -423,9 +482,11 @@ show_unmanaged_server_message() {
423
482
  if [ "$LANGUAGE" = "en" ]; then
424
483
  echo "ℹ️ A process is using port $port, but it was not started by this project's pid file."
425
484
  echo " For safety, run.sh stop does not kill processes based on port match alone."
485
+ echo " To kill it anyway (by port, ignoring process name): ./run.sh stop --force"
426
486
  else
427
487
  echo "ℹ️ 포트 $port 를 사용하는 프로세스가 있지만, 현재 프로젝트의 pid 파일로 시작한 서버가 아닙니다."
428
488
  echo " 안전을 위해 run.sh stop 은 포트 일치만으로 프로세스를 종료하지 않습니다."
489
+ echo " 그래도 종료하려면(포트 기준, 프로세스명 무시): ./run.sh stop --force"
429
490
  fi
430
491
 
431
492
  print_port_process_details
@@ -467,7 +528,94 @@ stop_pid_with_confirm() {
467
528
  return 1
468
529
  }
469
530
 
531
+ # 설정 포트를 점유한 모든 PID 를 프로세스명과 무관하게 강제 종료한다 (--force 전용).
532
+ # systemd 서비스가 등록돼 있으면 먼저 'systemctl stop' 으로 완전히 중지한다.
533
+ # - systemctl stop 은 명시적 중지라 Restart=always 정책을 트리거하지 않는다.
534
+ # - 반대로, 서비스를 stop 하지 않고 PID 만 kill 하면 systemd 가 비정상 종료로 보고 즉시 재기동한다.
535
+ # stop 이후에도 포트를 쥔 잔여(고아) 프로세스가 있으면 그때 kill 한다(이미 unit 이 멈춰 재기동 안 됨).
536
+ force_stop_by_port() {
537
+ local port
538
+ port=$(get_server_value "port" "3400")
539
+
540
+ local svc=""
541
+ svc=$(find_systemd_service || true)
542
+ if [ -n "$svc" ]; then
543
+ local svc_state=""
544
+ svc_state=$(systemctl is-active "$svc" 2>/dev/null || true)
545
+ if [ "$svc_state" = "active" ] || [ "$svc_state" = "activating" ] || [ "$svc_state" = "failed" ]; then
546
+ echo "ℹ️ systemd 서비스 완전 중지: $svc"
547
+ sudo systemctl stop "$svc"
548
+ # 포트가 풀릴 때까지 잠시 대기 (systemctl stop 은 재기동을 유발하지 않는다)
549
+ local i=0
550
+ while [ "$i" -lt 30 ]; do
551
+ if [ -z "$(find_server_pids | head -n 1)" ]; then
552
+ break
553
+ fi
554
+ sleep 0.1
555
+ i=$((i + 1))
556
+ done
557
+ fi
558
+ fi
559
+
560
+ rm -f "$PID_FILE"
561
+
562
+ local killed_any=0
563
+ local pid=""
564
+ while read -r pid; do
565
+ pid=$(echo "$pid" | tr -d '[:space:]')
566
+ [ -z "$pid" ] && continue
567
+ local name=""
568
+ name=$(get_pid_name "$pid")
569
+ if [ "$LANGUAGE" = "en" ]; then
570
+ echo "⚠️ --force: killing PID $pid (${name:-unknown}) on port $port"
571
+ else
572
+ echo "⚠️ --force: 포트 $port 점유 PID $pid (${name:-unknown}) 강제 종료"
573
+ fi
574
+ if force_stop_pid "$pid"; then
575
+ killed_any=1
576
+ else
577
+ if [ "$LANGUAGE" = "en" ]; then
578
+ echo "❌ failed to kill PID $pid"
579
+ else
580
+ echo "❌ PID $pid 종료 실패"
581
+ fi
582
+ return 1
583
+ fi
584
+ done < <(find_server_pids)
585
+
586
+ if [ "$killed_any" -eq 1 ]; then
587
+ if [ "$LANGUAGE" = "en" ]; then
588
+ echo "✅ ${SERVER_NAME} force-stopped (port $port)"
589
+ else
590
+ echo "✅ ${SERVER_NAME} 강제 종료 완료 (포트 $port)"
591
+ fi
592
+ return 0
593
+ fi
594
+
595
+ if [ -n "$svc" ]; then
596
+ if [ "$LANGUAGE" = "en" ]; then
597
+ echo "✅ ${SERVER_NAME} stopped via systemd (port $port released)"
598
+ else
599
+ echo "✅ ${SERVER_NAME} systemd 로 중지됨 (포트 $port 해제)"
600
+ fi
601
+ return 0
602
+ fi
603
+
604
+ if [ "$LANGUAGE" = "en" ]; then
605
+ echo "ℹ️ no process is occupying port $port."
606
+ else
607
+ echo "ℹ️ 포트 $port 를 점유한 프로세스가 없습니다."
608
+ fi
609
+ return 0
610
+ }
611
+
470
612
  stop_server() {
613
+ # --force: 프로세스명을 따지지 않고 포트 점유 PID 를 무조건 종료한다.
614
+ if [ "${FORCE_STOP:-0}" -eq 1 ]; then
615
+ force_stop_by_port
616
+ return $?
617
+ fi
618
+
471
619
  local svc=""
472
620
  svc=$(find_systemd_service || true)
473
621
  if [ -n "$svc" ]; then
@@ -584,14 +732,17 @@ if [ $# -eq 0 ]; then
584
732
  echo "Modes:"
585
733
  echo " dev environment=development, keep database.default, then run binary"
586
734
  echo " start run current config in background without modifying configs"
587
- echo " stop stop background server started by this script"
735
+ echo " stop stop background server started by this script (--force: kill by port)"
736
+ echo " restart restart server (--force: force-kill by port, then start)"
588
737
  echo " status show server status"
589
738
  echo ""
590
739
  echo "Examples:"
591
- echo " $0 dev # Start in development mode"
592
- echo " $0 start # Start current config in background"
593
- echo " $0 stop # Stop server"
594
- echo " $0 status # Show status"
740
+ echo " $0 dev # Start in development mode"
741
+ echo " $0 start # Start current config in background"
742
+ echo " $0 stop # Stop server"
743
+ echo " $0 stop --force # Kill whatever occupies the port (ignore process name)"
744
+ echo " $0 restart --force # Force-kill by port, then start"
745
+ echo " $0 status # Show status"
595
746
  else
596
747
  echo "Entity Server - 실행 스크립트"
597
748
  echo "==========================="
@@ -603,38 +754,33 @@ if [ $# -eq 0 ]; then
603
754
  echo "모드:"
604
755
  echo " dev environment=development로 설정하고 database.default는 유지한 채 바이너리 실행"
605
756
  echo " start 설정파일을 수정하지 않고 현재 설정 그대로 백그라운드 실행"
606
- echo " stop run.sh로 백그라운드 실행한 서버 중지"
607
- echo " restart 서버 재시작"
757
+ echo " stop run.sh로 백그라운드 실행한 서버 중지 (--force: 포트 기준 강제 종료)"
758
+ echo " restart 서버 재시작 (--force: 포트 기준 강제 종료 후 시작)"
608
759
  echo " status 서버 상태 조회"
609
760
  echo ""
610
761
  echo "예제:"
611
- echo " $0 dev # 개발 모드로 시작"
612
- echo " $0 start # 현재 설정 그대로 시작(백그라운드)"
613
- echo " $0 stop # 서버 중지"
614
- echo " $0 restart # 서버 재시작"
615
- echo " $0 status # 상태 조회"
616
- fi
617
-
618
- echo ""
619
- if [ -f "$SERVER_CONFIG" ] && [ -f "$DATABASE_CONFIG" ] && [ -f "$SERVER_BIN" ]; then
620
- if [ "$LANGUAGE" = "en" ]; then
621
- echo "Current status:"
622
- else
623
- echo "현재 상태:"
624
- fi
625
- show_status
626
- else
627
- if [ "$LANGUAGE" = "en" ]; then
628
- echo "Current status: unavailable (missing config or server binary)"
629
- else
630
- echo "현재 상태: 확인 불가 (설정 파일 또는 서버 바이너리 없음)"
631
- fi
762
+ echo " $0 dev # 개발 모드로 시작"
763
+ echo " $0 start # 현재 설정 그대로 시작(백그라운드)"
764
+ echo " $0 stop # 서버 중지"
765
+ echo " $0 stop --force # 포트 점유 프로세스를 이름 무관 강제 종료"
766
+ echo " $0 restart --force # 포트 기준 강제 종료 후 재시작"
767
+ echo " $0 status # 상태 조회"
632
768
  fi
633
769
  exit 0
634
770
  fi
635
771
 
636
772
  MODE="$1"
637
773
 
774
+ # --force: 프로세스명을 따지지 않고, 설정 포트를 점유한 PID 면 무조건 종료한다.
775
+ FORCE_STOP=0
776
+ for arg in "$@"; do
777
+ case "$arg" in
778
+ --force | -f)
779
+ FORCE_STOP=1
780
+ ;;
781
+ esac
782
+ done
783
+
638
784
  if [ ! -f "$SERVER_CONFIG" ]; then
639
785
  if [ "$LANGUAGE" = "en" ]; then
640
786
  echo "❌ configs/server.json not found"
@@ -702,6 +848,11 @@ case "$MODE" in
702
848
  echo " 재시작: sudo systemctl restart $svc"
703
849
  exit 0
704
850
  fi
851
+ # 부팅 시 자동 시작되도록 enable 까지 함께 켠다 (이미 enabled 면 멱등).
852
+ if [ "$(systemctl is-enabled "$svc" 2>/dev/null || true)" != "enabled" ]; then
853
+ echo "ℹ️ systemd 서비스 자동시작 등록(enable): $svc"
854
+ sudo systemctl enable "$svc" 2>/dev/null || true
855
+ fi
705
856
  echo "ℹ️ systemd 서비스 시작: $svc"
706
857
  sudo systemctl start "$svc"
707
858
  echo "✅ ${SERVER_NAME} 시작 완료 (systemd)"
@@ -771,6 +922,12 @@ case "$MODE" in
771
922
  ;;
772
923
 
773
924
  restart)
925
+ # --force restart: 포트 점유 프로세스를 이름 무관 강제 종료한 뒤 재기동한다.
926
+ if [ "$FORCE_STOP" -eq 1 ]; then
927
+ stop_server || exit $?
928
+ exec "$SCRIPT_DIR/run.sh" start
929
+ fi
930
+
774
931
  svc=$(find_systemd_service || true)
775
932
  if [ -n "$svc" ]; then
776
933
  echo "ℹ️ systemd 서비스 재시작: $svc"
@@ -7,9 +7,17 @@ set -euo pipefail
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
8
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
9
9
 
10
- # Load language from .env
10
+ # Load language and namespace from .env
11
+ # run.sh 와 동일한 서비스명을 산출하기 위해 SERVER_NAMESPACE/NAMESPACE 를 .env 에서 읽는다.
12
+ # (run.sh 는 .env 를 source 하므로, 여기서도 같은 값을 반영하지 않으면 서비스명이 어긋난다.)
11
13
  if [ -f "$PROJECT_ROOT/.env" ]; then
12
14
  LANGUAGE=$(sed -n 's/^LANGUAGE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
15
+ if [ -z "${SERVER_NAMESPACE:-}" ]; then
16
+ SERVER_NAMESPACE=$(sed -n 's/^SERVER_NAMESPACE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
17
+ fi
18
+ if [ -z "${NAMESPACE:-}" ]; then
19
+ NAMESPACE=$(sed -n 's/^NAMESPACE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
20
+ fi
13
21
  fi
14
22
  LANGUAGE=${LANGUAGE:-ko}
15
23
 
@@ -7,9 +7,17 @@ set -euo pipefail
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
8
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
9
9
 
10
- # Load language from .env
10
+ # Load language and namespace from .env
11
+ # run.sh 와 동일한 서비스명을 산출하기 위해 SERVER_NAMESPACE/NAMESPACE 를 .env 에서 읽는다.
12
+ # (run.sh 는 .env 를 source 하므로, 여기서도 같은 값을 반영하지 않으면 서비스명이 어긋난다.)
11
13
  if [ -f "$PROJECT_ROOT/.env" ]; then
12
14
  LANGUAGE=$(sed -n 's/^LANGUAGE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
15
+ if [ -z "${SERVER_NAMESPACE:-}" ]; then
16
+ SERVER_NAMESPACE=$(sed -n 's/^SERVER_NAMESPACE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
17
+ fi
18
+ if [ -z "${NAMESPACE:-}" ]; then
19
+ NAMESPACE=$(sed -n 's/^NAMESPACE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
20
+ fi
13
21
  fi
14
22
  LANGUAGE=${LANGUAGE:-ko}
15
23
 
@@ -54,6 +62,31 @@ for arg in "$@"; do
54
62
  esac
55
63
  done
56
64
 
65
+ UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
66
+
67
+ # 제거를 묻기 전에 서비스가 실제로 등록되어 있는지 먼저 확인한다.
68
+ # (등록되어 있지 않으면 묻지 않고 안내만 출력하고 종료한다.)
69
+ SERVICE_EXISTS=false
70
+ if systemctl list-unit-files 2>/dev/null | grep -q "^${SERVICE_NAME}\.service"; then
71
+ SERVICE_EXISTS=true
72
+ fi
73
+ if [ ! -f "$UNIT_PATH" ] && [ "$SERVICE_EXISTS" = false ]; then
74
+ if [ "$LANGUAGE" = "en" ]; then
75
+ echo "ℹ️ Service '$SERVICE_NAME' is not registered."
76
+ echo " Nothing to remove."
77
+ echo ""
78
+ echo " To register the service, run:"
79
+ echo " sudo $(dirname "$0")/service-install.sh"
80
+ else
81
+ echo "ℹ️ '$SERVICE_NAME' 서비스가 등록되어 있지 않습니다."
82
+ echo " 제거할 항목이 없습니다."
83
+ echo ""
84
+ echo " 서비스를 등록하려면:"
85
+ echo " sudo $(dirname "$0")/service-install.sh"
86
+ fi
87
+ exit 0
88
+ fi
89
+
57
90
  if [ "$INTERACTIVE" = true ] && [ "$CONFIRMED" = false ]; then
58
91
  if [ "$LANGUAGE" = "en" ]; then
59
92
  echo "[interactive] systemd service removal"
@@ -79,8 +112,6 @@ if [ "$INTERACTIVE" = true ] && [ "$CONFIRMED" = false ]; then
79
112
  fi
80
113
  fi
81
114
 
82
- UNIT_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
83
-
84
115
  if [ "$EUID" -ne 0 ]; then
85
116
  if command -v sudo >/dev/null 2>&1; then
86
117
  exec sudo "$0" --yes
@@ -93,27 +124,6 @@ if [ "$EUID" -ne 0 ]; then
93
124
  exit 1
94
125
  fi
95
126
 
96
- SERVICE_EXISTS=false
97
- if systemctl list-unit-files | grep -q "^${SERVICE_NAME}\.service"; then
98
- SERVICE_EXISTS=true
99
- fi
100
- if [ ! -f "$UNIT_PATH" ] && [ "$SERVICE_EXISTS" = false ]; then
101
- if [ "$LANGUAGE" = "en" ]; then
102
- echo "ℹ️ Service '$SERVICE_NAME' is not registered."
103
- echo " Nothing to remove."
104
- echo ""
105
- echo " To register the service, run:"
106
- echo " sudo $(dirname "$0")/service-install.sh"
107
- else
108
- echo "ℹ️ '$SERVICE_NAME' 서비스가 등록되어 있지 않습니다."
109
- echo " 제거할 항목이 없습니다."
110
- echo ""
111
- echo " 서비스를 등록하려면:"
112
- echo " sudo $(dirname "$0")/service-install.sh"
113
- fi
114
- exit 0
115
- fi
116
-
117
127
  if [ "$SERVICE_EXISTS" = true ]; then
118
128
  systemctl stop "$SERVICE_NAME" 2>/dev/null || true
119
129
  systemctl disable "$SERVICE_NAME" 2>/dev/null || true