Switch production deployment from Docker to PM2 on Ubuntu.
Add Express gateway, ecosystem config, and one-click install with native PostgreSQL, Node, and Python venv on port 23566. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -0,0 +1,61 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const root = path.resolve(__dirname, '../..')
|
||||
const envPath = path.join(root, '.env')
|
||||
|
||||
function loadEnv() {
|
||||
const env = { NODE_ENV: 'production' }
|
||||
if (!fs.existsSync(envPath)) return env
|
||||
for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#')) continue
|
||||
const idx = trimmed.indexOf('=')
|
||||
if (idx === -1) continue
|
||||
const key = trimmed.slice(0, idx).trim()
|
||||
let value = trimmed.slice(idx + 1).trim()
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1)
|
||||
}
|
||||
env[key] = value
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
const env = loadEnv()
|
||||
const webPort = env.WEB_PORT || '23566'
|
||||
const apiPort = env.API_PORT || '8000'
|
||||
const venvPython = path.join(root, 'backend', 'venv', 'bin', 'python')
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'grade-api',
|
||||
cwd: path.join(root, 'backend'),
|
||||
script: venvPython,
|
||||
args: `-m uvicorn app.main:app --host 127.0.0.1 --port ${apiPort}`,
|
||||
interpreter: 'none',
|
||||
env: {
|
||||
...env,
|
||||
UPLOAD_DIR: env.UPLOAD_DIR || path.join(root, 'uploads'),
|
||||
},
|
||||
max_restarts: 10,
|
||||
restart_delay: 5000,
|
||||
},
|
||||
{
|
||||
name: 'grade-web',
|
||||
cwd: path.join(root, 'deploy', 'pm2'),
|
||||
script: 'server.js',
|
||||
env: {
|
||||
...env,
|
||||
WEB_PORT: webPort,
|
||||
API_TARGET: env.API_TARGET || `http://127.0.0.1:${apiPort}`,
|
||||
},
|
||||
max_restarts: 10,
|
||||
restart_delay: 3000,
|
||||
},
|
||||
],
|
||||
}
|
||||
Generated
+1052
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "grade-archive-gateway",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Static web + API reverse proxy for PM2 deployment",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"http-proxy-middleware": "^3.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const express = require('express')
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware')
|
||||
|
||||
const envPath = path.join(__dirname, '../../.env')
|
||||
if (fs.existsSync(envPath)) {
|
||||
require('dotenv').config({ path: envPath })
|
||||
}
|
||||
|
||||
const PORT = Number(process.env.WEB_PORT || 23566)
|
||||
const API_TARGET = process.env.API_TARGET || 'http://127.0.0.1:8000'
|
||||
const STATIC_ROOT = path.join(__dirname, '../../frontend/dist')
|
||||
|
||||
const app = express()
|
||||
app.use(
|
||||
'/api',
|
||||
createProxyMiddleware({
|
||||
target: API_TARGET,
|
||||
changeOrigin: true,
|
||||
}),
|
||||
)
|
||||
app.use(express.static(STATIC_ROOT, { maxAge: '1h', index: false }))
|
||||
app.get(/^\/(?!api).*/, (_req, res) => {
|
||||
res.sendFile(path.join(STATIC_ROOT, 'index.html'))
|
||||
})
|
||||
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Grade archive web gateway listening on http://0.0.0.0:${PORT}`)
|
||||
console.log(`API proxy target: ${API_TARGET}`)
|
||||
})
|
||||
Reference in New Issue
Block a user