172 lines
5.3 KiB
Bash
Executable File
172 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# PenguinHarness service script (portable)
|
|
# -----------------------------------------------------------------------------
|
|
# Starts / stops / restarts the penguin-harness web server as a background
|
|
# daemon. Works anywhere: the repo location, node binary and .env are
|
|
# auto-detected (no hardcoded /workspace paths).
|
|
#
|
|
# scripts/penguin-service.sh start|stop|status|restart|run
|
|
#
|
|
# run = foreground mode (for systemd Type=simple, ExecStart=.../run)
|
|
#
|
|
# Console output (including the one-time seeded admin password) goes to:
|
|
# ~/.penguin/penguin-service.log
|
|
#
|
|
# Configuration comes from .env at the repo root (HOST / PORT / PENGUIN_WEB_DIST).
|
|
# =============================================================================
|
|
set -u
|
|
|
|
# --- Self-locating paths: BASE = repo root (parent of scripts/) ---------------
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BASE="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# --- Node discovery: $PENGUIN_NODE > known nvm paths > PATH -------------------
|
|
if [ -n "${PENGUIN_NODE:-}" ]; then
|
|
NODE="$PENGUIN_NODE"
|
|
elif [ -x /workspace/.nvm/versions/node/v24.18.0/bin/node ]; then
|
|
NODE=/workspace/.nvm/versions/node/v24.18.0/bin/node
|
|
elif [ -x "$HOME/.nvm/versions/node/v24.18.0/bin/node" ]; then
|
|
NODE="$HOME/.nvm/versions/node/v24.18.0/bin/node"
|
|
elif command -v node >/dev/null 2>&1; then
|
|
NODE="$(command -v node)"
|
|
else
|
|
echo "Không tìm thấy node. Cài Node >= 24 hoặc đặt biến PENGUIN_NODE." >&2
|
|
exit 1
|
|
fi
|
|
|
|
ENTRY="$BASE/packages/server/dist/index.js"
|
|
ENVFILE="$BASE/.env"
|
|
|
|
PENGUIN_HOME="${PENGUIN_HOME:-$HOME/.penguin/data}"
|
|
LOG="${PENGUIN_SERVICE_LOG:-$HOME/.penguin/penguin-service.log}"
|
|
PIDFILE="${PENGUIN_SERVICE_PID:-$HOME/.penguin/penguin-service.pid}"
|
|
|
|
# --- Sanity checks ------------------------------------------------------------
|
|
if [ ! -f "$ENTRY" ]; then
|
|
echo "Không thấy bản build: $ENTRY" >&2
|
|
echo "Chạy trước: cd \"$BASE\" && pnpm install && pnpm build" >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$ENVFILE" ]; then
|
|
echo "Lưu ý: không có $ENVFILE — server sẽ nghe trên HOST=127.0.0.1." >&2
|
|
echo "Tạo .env với nội dung 'HOST=0.0.0.0' để mở mọi interface." >&2
|
|
fi
|
|
|
|
# --- Load .env (HOST / PORT / PENGUIN_WEB_DIST ...) and export -----------------
|
|
if [ -f "$ENVFILE" ]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$ENVFILE"
|
|
set +a
|
|
fi
|
|
export PENGUIN_HOME
|
|
|
|
PORT="${PORT:-7364}"
|
|
|
|
# True only while the process is really alive: `kill -0` alone would also
|
|
# succeed on a zombie (exited but not yet reaped). A zombie (state Z in
|
|
# /proc/<pid>/stat) counts as dead.
|
|
proc_alive() {
|
|
local pid="$1"
|
|
kill -0 "$pid" 2>/dev/null || return 1
|
|
[ "$(awk '{print $3}' "/proc/$pid/stat" 2>/dev/null)" != "Z" ]
|
|
}
|
|
|
|
is_running() {
|
|
[ -f "$PIDFILE" ] || return 1
|
|
local pid
|
|
pid="$(cat "$PIDFILE" 2>/dev/null)" || return 1
|
|
[ -n "$pid" ] && proc_alive "$pid"
|
|
}
|
|
|
|
start() {
|
|
if is_running; then
|
|
echo "PenguinHarness đang chạy (pid $(cat "$PIDFILE"))."
|
|
return 0
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$LOG")" "$(dirname "$PIDFILE")"
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] starting penguin-server (HOST=${HOST:-127.0.0.1} PORT=$PORT PENGUIN_HOME=$PENGUIN_HOME)" >>"$LOG"
|
|
|
|
# setsid detaches from the controlling terminal; all fds go to the log file.
|
|
setsid "$NODE" "$ENTRY" >>"$LOG" 2>&1 </dev/null &
|
|
echo $! >"$PIDFILE"
|
|
|
|
# Wait for readiness (up to 30s) — any HTTP answer counts.
|
|
local i
|
|
for i in $(seq 1 30); do
|
|
if curl -s -o /dev/null --max-time 2 "http://127.0.0.1:$PORT/"; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if is_running; then
|
|
echo "PenguinHarness đã khởi động: http://0.0.0.0:$PORT (pid $(cat "$PIDFILE"))"
|
|
echo "Log (console): $LOG"
|
|
# In mật khẩu admin khởi tạo ra console nếu có (chỉ in 1 lần khi seed).
|
|
grep -h "initial password" "$LOG" 2>/dev/null | tail -1
|
|
else
|
|
echo "Khởi động THẤT BẠI — xem log: $LOG" >&2
|
|
tail -20 "$LOG" >&2
|
|
rm -f "$PIDFILE"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
stop() {
|
|
if ! is_running; then
|
|
echo "PenguinHarness không đang chạy."
|
|
rm -f "$PIDFILE"
|
|
return 0
|
|
fi
|
|
local pid
|
|
pid="$(cat "$PIDFILE")"
|
|
echo "Đang dừng penguin-server (pid $pid)..."
|
|
kill -TERM "$pid" 2>/dev/null
|
|
local i
|
|
for i in $(seq 1 10); do
|
|
proc_alive "$pid" || break
|
|
sleep 1
|
|
done
|
|
if proc_alive "$pid"; then
|
|
echo "Không dừng được trong 10s, buộc dừng (SIGKILL)." >&2
|
|
kill -KILL "$pid" 2>/dev/null
|
|
fi
|
|
# Reap any zombie so it cannot linger in the process table.
|
|
wait "$pid" 2>/dev/null
|
|
rm -f "$PIDFILE"
|
|
echo "Đã dừng."
|
|
}
|
|
|
|
status() {
|
|
if is_running; then
|
|
echo "PenguinHarness: ĐANG CHẠY (pid $(cat "$PIDFILE"))"
|
|
echo " URL: http://0.0.0.0:$PORT"
|
|
echo " Log: $LOG"
|
|
if grep -h "initial password" "$LOG" 2>/dev/null | tail -1 | grep -q .; then
|
|
echo " Admin khởi tạo: $(grep -h 'initial password' "$LOG" | tail -1)"
|
|
fi
|
|
return 0
|
|
fi
|
|
echo "PenguinHarness: KHÔNG chạy."
|
|
return 1
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start) start ;;
|
|
stop) stop ;;
|
|
restart) stop; start ;;
|
|
status) status ;;
|
|
run)
|
|
# Foreground mode for systemd (Type=simple): exec replaces the shell so
|
|
# systemd tracks the real server process and Restart= works.
|
|
exec "$NODE" "$ENTRY"
|
|
;;
|
|
*)
|
|
echo "Sử dụng: $0 {start|stop|status|restart|run}"
|
|
exit 2
|
|
;;
|
|
esac
|