Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

207 строки
5.1 KiB

  1. #!/usr/bin/env bash
  2. if [ -z "${BASH_VERSION:-}" ]; then
  3. exec bash "$0" "$@"
  4. fi
  5. set -Eeuo pipefail
  6. APP_NAME="${APP_NAME:-pc_nuxt}"
  7. BRANCH="${BRANCH:-}"
  8. NUXT_PUBLIC_API_BASE="${NUXT_PUBLIC_API_BASE:-https://api.aionline.cc}"
  9. NUXT_API_SERVER_BASE="${NUXT_API_SERVER_BASE:-http://127.0.0.1:16888}"
  10. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  11. export NUXT_PUBLIC_API_BASE
  12. export NUXT_API_SERVER_BASE
  13. cd "$SCRIPT_DIR"
  14. log() {
  15. printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
  16. }
  17. require_command() {
  18. if ! command -v "$1" >/dev/null 2>&1; then
  19. log "Missing command: $1"
  20. exit 1
  21. fi
  22. }
  23. validate_api_base() {
  24. node - "$NUXT_PUBLIC_API_BASE" <<'NODE'
  25. const value = process.argv[2]
  26. let url
  27. try {
  28. url = new URL(value)
  29. } catch {
  30. console.error(`[pc_nuxt] Invalid NUXT_PUBLIC_API_BASE: ${value || '(empty)'}`)
  31. process.exit(1)
  32. }
  33. const hostname = url.hostname.toLowerCase()
  34. const isPrivate = hostname === 'localhost'
  35. || hostname === '0.0.0.0'
  36. || /^127\./.test(hostname)
  37. || hostname === '[::1]'
  38. || hostname.endsWith('.local')
  39. || /^10\./.test(hostname)
  40. || /^192\.168\./.test(hostname)
  41. || /^169\.254\./.test(hostname)
  42. || /^172\.(1[6-9]|2\d|3[01])\./.test(hostname)
  43. if (!['http:', 'https:'].includes(url.protocol) || isPrivate) {
  44. console.error(`[pc_nuxt] Production API must be a public http(s) URL: ${value}`)
  45. process.exit(1)
  46. }
  47. NODE
  48. }
  49. validate_server_api_base() {
  50. node - "$NUXT_API_SERVER_BASE" <<'NODE'
  51. const value = process.argv[2]
  52. let url
  53. try {
  54. url = new URL(value)
  55. } catch {
  56. console.error(`[pc_nuxt] Invalid NUXT_API_SERVER_BASE: ${value || '(empty)'}`)
  57. process.exit(1)
  58. }
  59. if (!['http:', 'https:'].includes(url.protocol)) {
  60. console.error(`[pc_nuxt] NUXT_API_SERVER_BASE must be http(s): ${value}`)
  61. process.exit(1)
  62. }
  63. NODE
  64. }
  65. verify_server_api() {
  66. log "Verify SSR API connectivity"
  67. node - "$NUXT_API_SERVER_BASE" <<'NODE'
  68. const http = require('node:http')
  69. const https = require('node:https')
  70. const base = process.argv[2]
  71. const target = new URL('/article/getlist', `${base.replace(/\/+$/, '')}/`)
  72. const body = new URLSearchParams({
  73. mac: 'pc-nuxt-deploy-check',
  74. base_timestamp: String(Math.floor(Date.now() / 1000)),
  75. client: '1',
  76. client_ios: '0',
  77. version: '1.0.0',
  78. version_code: '1',
  79. page_no: '1',
  80. page_size: '1'
  81. }).toString()
  82. const transport = target.protocol === 'https:' ? https : http
  83. const request = transport.request(target, {
  84. method: 'POST',
  85. headers: {
  86. 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
  87. 'Content-Length': Buffer.byteLength(body)
  88. },
  89. timeout: 10000
  90. }, (response) => {
  91. const chunks = []
  92. response.on('data', chunk => chunks.push(chunk))
  93. response.on('end', () => {
  94. const text = Buffer.concat(chunks).toString('utf8')
  95. let result
  96. try {
  97. result = JSON.parse(text)
  98. } catch {
  99. console.error(`[pc_nuxt] SSR API returned invalid JSON (${response.statusCode}): ${text.slice(0, 200)}`)
  100. process.exit(1)
  101. }
  102. if (response.statusCode < 200 || response.statusCode >= 300 || result.code !== 0) {
  103. console.error(`[pc_nuxt] SSR API check failed (${response.statusCode}): ${result.msg || text.slice(0, 200)}`)
  104. process.exit(1)
  105. }
  106. console.log(`[pc_nuxt] SSR API OK: ${base}`)
  107. })
  108. })
  109. request.on('timeout', () => request.destroy(new Error('timeout')))
  110. request.on('error', (error) => {
  111. console.error(`[pc_nuxt] SSR API unavailable: ${base} (${error.message})`)
  112. process.exit(1)
  113. })
  114. request.write(body)
  115. request.end()
  116. NODE
  117. }
  118. validate_tls_security() {
  119. if [ "${NODE_TLS_REJECT_UNAUTHORIZED:-}" = "0" ]; then
  120. log "Refusing deployment: NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate verification."
  121. exit 1
  122. fi
  123. }
  124. resolve_branch() {
  125. if [ -z "$BRANCH" ]; then
  126. BRANCH="$(git rev-parse --abbrev-ref HEAD)"
  127. fi
  128. if [ "$BRANCH" = "HEAD" ]; then
  129. log "Cannot detect git branch. Please run with BRANCH=main ./restart.sh"
  130. exit 1
  131. fi
  132. }
  133. sync_latest_code() {
  134. log "Force sync code from origin/$BRANCH"
  135. git fetch origin "$BRANCH"
  136. if [ -n "$(git status --porcelain)" ]; then
  137. log "Discard local working tree changes before deploy"
  138. git status --short
  139. fi
  140. git reset --hard "origin/$BRANCH"
  141. git clean -fd
  142. }
  143. install_dependencies() {
  144. log "Install dependencies"
  145. pnpm install --frozen-lockfile
  146. }
  147. build_app() {
  148. log "Build Nuxt app"
  149. pnpm run build
  150. if [ ! -f ".output/server/index.mjs" ]; then
  151. log "Build output missing: .output/server/index.mjs"
  152. exit 1
  153. fi
  154. }
  155. restart_app() {
  156. log "Reload app with pm2 ecosystem config"
  157. mkdir -p logs
  158. APP_NAME="$APP_NAME" pm2 startOrReload ecosystem.config.cjs --env production --update-env
  159. pm2 save >/dev/null 2>&1 || true
  160. }
  161. main() {
  162. require_command git
  163. require_command node
  164. require_command pnpm
  165. require_command pm2
  166. validate_tls_security
  167. validate_api_base
  168. validate_server_api_base
  169. resolve_branch
  170. log "Deploy start: $APP_NAME"
  171. log "Node: $(node -v 2>/dev/null || echo 'unknown')"
  172. log "pnpm: $(pnpm -v)"
  173. log "pm2: $(pm2 -v)"
  174. log "API: $NUXT_PUBLIC_API_BASE"
  175. log "Server API: $NUXT_API_SERVER_BASE"
  176. verify_server_api
  177. sync_latest_code
  178. install_dependencies
  179. build_app
  180. restart_app
  181. log "Deploy done: $APP_NAME"
  182. }
  183. main "$@"