Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,17 @@ NPM_CMD="npm install --omit=dev"
NPM_OUT=""
if NPM_OUT=$(${NPM_CMD} 2>&1); then
say "Dependencies installed."
mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/logs"
mkdir -p "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs"
touch "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log"

if id -u "$APP_NAME" >/dev/null 2>&1; then
chown -R "$APP_NAME:$APP_NAME" "$PROJECT_DIR/data" "$PROJECT_DIR/logs" || true
[[ -f "$PROJECT_DIR/.env" ]] && chown "$APP_NAME:$APP_NAME" "$PROJECT_DIR/.env" || true
fi

chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$PROJECT_DIR/logs" || true
chmod 0640 "$PROJECT_DIR/data/sshv-sessions.json" "$PROJECT_DIR/data/admin-events.log" || true
[[ -f "$PROJECT_DIR/.env" ]] && chmod 0600 "$PROJECT_DIR/.env" || true
else
warn "npm install failed — restarting bot on existing node_modules"
warn "npm output: $NPM_OUT"
Expand Down
116 changes: 98 additions & 18 deletions prod-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,39 @@ ADMIN_EVENTS_LOG="$PROJECT_DIR/data/admin-events.log"
SSHV_SESSIONS_FILE="$PROJECT_DIR/data/sshv-sessions.json"
SERVICE_FILE="/etc/systemd/system/${APP_NAME}.service"
LOGROTATE_FILE="/etc/logrotate.d/${APP_NAME}"
HEALTH_TLS_INSECURE="${HEALTH_TLS_INSECURE:-0}"

say() { printf '[%s] %s\n' "$APP_NAME" "$*"; }
warn() { printf '[%s][warn] %s\n' "$APP_NAME" "$*" >&2; }
err() { printf '[%s][error] %s\n' "$APP_NAME" "$*" >&2; }


SERVICE_USER="$APP_NAME"
SERVICE_GROUP="$APP_NAME"

ensure_service_user() {
if id -u "$APP_NAME" >/dev/null 2>&1; then
return 0
fi

if [[ "$(id -u)" -ne 0 ]]; then
SERVICE_USER="$(id -un)"
SERVICE_GROUP="$(id -gn)"
warn "System user '$APP_NAME' not found and current user is not root — using ${SERVICE_USER}:${SERVICE_GROUP}"
return 1
fi

if ! getent group "$APP_NAME" >/dev/null 2>&1; then
groupadd --system "$APP_NAME"
fi

if ! id -u "$APP_NAME" >/dev/null 2>&1; then
useradd --system --gid "$APP_NAME" --home-dir "$PROJECT_DIR" --shell /usr/sbin/nologin "$APP_NAME"
fi

say "Created system user/group '$APP_NAME' for service runtime"
}

read_env_value() {
local key="$1"
[[ -f "$PROJECT_DIR/.env" ]] || return 1
Expand All @@ -56,7 +84,12 @@ check_health_endpoint() {
local url="$1"
local code
if [[ "$url" == https://* ]]; then
code="$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)"
if [[ "$HEALTH_TLS_INSECURE" == "1" ]]; then
warn "HEALTH_TLS_INSECURE=1 set; skipping TLS cert verification for health probe"
code="$(curl -k -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)"
else
code="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)"
fi
else
code="$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo 000)"
fi
Expand All @@ -79,17 +112,32 @@ wait_for_health() {

is_port_listening() {
local port="$1"
local port_hex
if command -v ss >/dev/null 2>&1; then
ss -ltn "sport = :${port}" 2>/dev/null | awk 'NR>1 {found=1} END {exit(found ? 0 : 1)}'
else
return 1
ss -ltn "sport = :${port}" 2>/dev/null | awk 'NR>1 {found=1} END {exit(found ? 0 : 1)}' && return 0
fi

if command -v netstat >/dev/null 2>&1; then
netstat -ltn 2>/dev/null | awk -v p=":${port}" 'index($4,p){found=1} END{exit(found?0:1)}' && return 0
fi

if [[ -r /proc/net/tcp ]]; then
port_hex="$(printf '%04X' "$port" | tr '[:lower:]' '[:upper:]')"
awk -v p=":${port_hex}" 'NR>1 && index($2,p)==(length($2)-4) {found=1} END{exit(found?0:1)}' /proc/net/tcp && return 0
fi

return 1
}

say "Starting production runner"
say "Project directory: $PROJECT_DIR"
say "Running as: $(id -un) (uid=$(id -u) gid=$(id -g))"

ensure_service_user || true
if [[ "$SERVICE_USER" != "$APP_NAME" ]]; then
warn "Using fallback service identity ${SERVICE_USER}:${SERVICE_GROUP}; ownership/service behavior may differ from root-managed installs"
fi

# ---------------------------------------------------------
# 1) Git fetch + reset
say "Fetching latest code from origin main..."
Expand All @@ -110,13 +158,11 @@ mkdir -p "$PROJECT_DIR/data"
mkdir -p "$PROJECT_DIR/data/backups"
touch "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE" # create if missing; safe on existing files

if id -u "$APP_NAME" >/dev/null 2>&1; then
chown -R "$APP_NAME:$APP_NAME" "$LOG_DIR" "$PROJECT_DIR/data"
chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$LOG_DIR"
chmod 0640 "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE"
else
warn "System user '$APP_NAME' not found — using $(id -un):$(id -gn)"
if id -u "$SERVICE_USER" >/dev/null 2>&1; then
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$LOG_DIR" "$PROJECT_DIR/data"
fi
chmod 0750 "$PROJECT_DIR/data" "$PROJECT_DIR/data/backups" "$LOG_DIR"
chmod 0640 "$MAIN_LOG" "$ERROR_LOG" "$ADMIN_EVENTS_LOG" "$SSHV_SESSIONS_FILE"

say "Ensured runtime directories and log files exist"

Expand Down Expand Up @@ -157,6 +203,14 @@ if [[ -f "$PROJECT_DIR/.env" ]]; then
fi
fi

# Lock down .env permissions (contains secrets)
if [[ -f "$PROJECT_DIR/.env" ]]; then
chmod 0600 "$PROJECT_DIR/.env" || true
if id -u "$SERVICE_USER" >/dev/null 2>&1; then
chown "$SERVICE_USER:$SERVICE_GROUP" "$PROJECT_DIR/.env" || true
fi
fi

# ---------------------------------------------------------
# 6) Production dependency install
say "Installing/updating production dependencies..."
Expand All @@ -176,12 +230,10 @@ PID="$(get_bot_pid)"
ensure_systemd_service() {
local node_bin svc_user svc_group conf
node_bin="$(command -v node || echo /usr/bin/node)"
if id -u "$APP_NAME" >/dev/null 2>&1; then
svc_user="$APP_NAME"; svc_group="$APP_NAME"
else
svc_user="$(id -un)"; svc_group="$(id -gn)"
warn "System user '$APP_NAME' not found — using $svc_user:$svc_group"
fi

local svc_user svc_group
svc_user="$SERVICE_USER"
svc_group="$SERVICE_GROUP"

conf=$(cat <<UNIT
[Unit]
Expand All @@ -199,10 +251,18 @@ EnvironmentFile=${PROJECT_DIR}/.env
Environment=NODE_ENV=production
Restart=always
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5
KillSignal=SIGTERM
TimeoutStopSec=20
StandardOutput=append:${MAIN_LOG}
StandardError=append:${ERROR_LOG}
ReadWritePaths=${PROJECT_DIR}/logs ${PROJECT_DIR}/data
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
UMask=0077

[Install]
WantedBy=multi-user.target
Expand All @@ -217,8 +277,11 @@ ensure_systemd_service
# ---------------------------------------------------------
# 9) Logrotate
ensure_logrotate() {
local uid_num gid_num conf lr_out cron_line current_cron
uid_num="$(id -u)"; gid_num="$(id -g)"
local uid_num gid_num
uid_num="$(id -u "$SERVICE_USER" 2>/dev/null || id -u)"
gid_num="$(id -g "$SERVICE_GROUP" 2>/dev/null || id -g)"

local conf
conf=$(cat <<LRCONF
${MAIN_LOG} ${ERROR_LOG} ${ADMIN_EVENTS_LOG} {
daily
Expand Down Expand Up @@ -366,6 +429,23 @@ else
PORT_STATUS="not-listening"
fi

SYSTEMD_ACTIVE="$(systemctl is-active "${APP_NAME}.service" 2>/dev/null || echo inactive)"
HEALTH_URL="$(resolve_health_url)"
HEALTH_PORT="${HEALTH_URL#*://127.0.0.1:}"
HEALTH_PORT="${HEALTH_PORT%%/*}"

if wait_for_health "$HEALTH_URL" 20 2; then
HEALTH_STATUS="healthy"
else
HEALTH_STATUS="unhealthy"
fi

if is_port_listening "$HEALTH_PORT"; then
PORT_STATUS="listening"
else
PORT_STATUS="not-listening"
fi

say "✔ Systemd service: ${SYSTEMD_ACTIVE}"
say "✔ Health URL: ${HEALTH_URL}"
say "✔ Health endpoint: ${HEALTH_STATUS}"
Expand Down
1 change: 1 addition & 0 deletions runewager.service
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
NoNewPrivileges=true
UMask=0077

# File descriptor limit (for concurrent connections)
LimitNOFILE=65536
Expand Down
Loading
Loading