|
| 1 | +# 进阶部署 |
| 2 | + |
| 3 | +import { Callout } from 'nextra/components'; |
| 4 | + |
| 5 | +<Callout type="error"> |
| 6 | +在您进行进阶部署前,我们给予最后的警告:如果你**不是开发者**,或者**不想折腾**,请不要选择**进阶部署**,因为它需要你具备一定的开发能力。 |
| 7 | + |
| 8 | +由于自身技术原因导致的问题,我们将**不会提供任何技术支持**。情节严重者,我们将**永久拉黑**您的账号。 |
| 9 | +</Callout> |
| 10 | + |
| 11 | +## 要求 |
| 12 | + |
| 13 | +- 已安装 [Node.js](https://nodejs.org/zh-cn/) 16.0.0 或以上版本 |
| 14 | +- 已安装 [Git](https://git-scm.com/downloads), [PNPM](https://pnpm.io/installation), [PM2](https://pm2.keymetrics.io/docs/usage/quick-start/) |
| 15 | +- 已安装 [MongoDB](https://www.mongodb.com/try/download/community), [Redis](https://redis.io/download) 并正常运行 |
| 16 | + |
| 17 | +import { Steps } from 'nextra/components'; |
| 18 | + |
| 19 | +<Steps> |
| 20 | +### 1. 克隆并安装 |
| 21 | + |
| 22 | +```bash |
| 23 | +git clone https://github.com/mx-space/core.git --depth=1 |
| 24 | +cd core |
| 25 | +pnpm i |
| 26 | +``` |
| 27 | + |
| 28 | +### 2. 构建 & Bundle |
| 29 | + |
| 30 | +```bash |
| 31 | +pnpm build |
| 32 | +pnpm bundle |
| 33 | +``` |
| 34 | + |
| 35 | +### 3. 配置 ecosystem.config.js |
| 36 | + |
| 37 | +<Configurator |
| 38 | + args={['--color', '--encrypt_enable']} |
| 39 | + env={{ |
| 40 | + MX_ENCRYPT_KEY: { |
| 41 | + type: 'password', |
| 42 | + tip: '加密密钥,可选', |
| 43 | + default: 'mx-space', |
| 44 | + }, |
| 45 | + PORT: { |
| 46 | + type: 'number', |
| 47 | + tip: '服务端口,可选', |
| 48 | + default: 3000, |
| 49 | + }, |
| 50 | + ALLOWED_ORIGINS: { |
| 51 | + type: 'text', |
| 52 | + tip: '允许跨域的域名,多个域名用逗号分隔', |
| 53 | + default: 'innei.ren,www.innei.ren', |
| 54 | + }, |
| 55 | + JWT_SECRET: { |
| 56 | + type: 'password', |
| 57 | + tip: 'JWT 密钥,用于生成 JWT Token,可选', |
| 58 | + default: 'asdoiasjdoiasjdioasjdioasjdio', |
| 59 | + }, |
| 60 | + }} |
| 61 | + template={`const { cpus } = require('os') |
| 62 | +const { execSync } = require('child_process') |
| 63 | +const nodePath = execSync(\`npm root --quiet -g\`, { encoding: 'utf-8' }).split( |
| 64 | + '\n', |
| 65 | +)[0] |
| 66 | +const cpuLen = cpus().length |
| 67 | +module.exports = { |
| 68 | + apps: [ |
| 69 | + { |
| 70 | + name: 'mx-server', |
| 71 | + script: 'out/index.js', |
| 72 | + autorestart: true, |
| 73 | + exec_mode: 'cluster', |
| 74 | + watch: false, |
| 75 | + instances: cpuLen, |
| 76 | + max_memory_restart: '520M', |
| 77 | + args: '', |
| 78 | + env: { |
| 79 | + NODE_ENV: 'production', |
| 80 | + NODE_PATH: nodePath, |
| 81 | + MX_ENCRYPT_KEY: process.env.MX_ENCRYPT_KEY, |
| 82 | + PORT: process.env.PORT, |
| 83 | + }, |
| 84 | + }, |
| 85 | + ], |
| 86 | +} |
| 87 | +`} |
| 88 | +/> |
| 89 | + |
| 90 | +### 4. 启动 |
| 91 | + |
| 92 | +1. 进入 `./apps/core/out`,创建一个 `ecosystem.config.js` 文件 |
| 93 | +2. 将上方复制的内容黏贴进去,然后执行以下命令启动服务 |
| 94 | + |
| 95 | +```bash |
| 96 | +pm2 start ecosystem.config.js |
| 97 | +``` |
| 98 | + |
| 99 | +### 5. 反向代理 or ... |
| 100 | + |
| 101 | +剩下的就是你的事了,你可以使用 [Nginx](https://nginx.org/en/download.html) 或者 [Caddy](https://caddyserver.com/download) 等反向代理工具,也可以使用 [Cloudflare](https://www.cloudflare.com/) 等 CDN 服务。本文不再赘述。 |
| 102 | + |
| 103 | +</Steps> |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +import { useState, useEffect } from 'react'; |
| 109 | +import copy from 'copy-to-clipboard'; |
| 110 | + |
| 111 | +export const Configurator = ({ args, template, env }) => { |
| 112 | + const [selectedArgs, setSelectedArgs] = useState([]); |
| 113 | + const [envValues, setEnvValues] = useState( |
| 114 | + Object.keys(env).map((key) => env[key].default) |
| 115 | + ); |
| 116 | + const [nowTemplate, setNowTemplate] = useState(template); |
| 117 | + |
| 118 | + const handleToggleArg = (arg) => { |
| 119 | + if (selectedArgs.includes(arg)) { |
| 120 | + setSelectedArgs(selectedArgs.filter((item) => item !== arg)); |
| 121 | + } else { |
| 122 | + setSelectedArgs([...selectedArgs, arg]); |
| 123 | + } |
| 124 | + }; |
| 125 | + |
| 126 | + const handleChange = (index, value) => { |
| 127 | + const updatedEnvValues = [...envValues]; |
| 128 | + updatedEnvValues[index] = value; |
| 129 | + setEnvValues(updatedEnvValues); |
| 130 | + }; |
| 131 | + |
| 132 | + const handleCopyToClipboard = () => { |
| 133 | + copy(updatedEnvTemplate); |
| 134 | + }; |
| 135 | + |
| 136 | + useEffect(() => { |
| 137 | + const updatedTemplate = nowTemplate.replace( |
| 138 | + /args:\s*'([^']*)'/, |
| 139 | + `args: '${selectedArgs.join(' ')}'` |
| 140 | + ); |
| 141 | + const updatedEnvTemplate = updatedTemplate.replace( |
| 142 | + /env:\s*{([^}]*)}/, |
| 143 | + `env: { |
| 144 | + ${Object.keys(env) |
| 145 | + .map((key, index) => `${key}: '${envValues[index]}'`) |
| 146 | + .join(', \n ')} |
| 147 | + }` |
| 148 | + ); |
| 149 | + setNowTemplate(updatedEnvTemplate); |
| 150 | + }, [selectedArgs, envValues]); |
| 151 | + |
| 152 | + return ( |
| 153 | + <div> |
| 154 | + <div className="p-4 mt-2"> |
| 155 | + |
| 156 | + <div id="config" className="border rounded-lg overflow-hidden h-12 mb-4" style={{ |
| 157 | + height: "66.4px", |
| 158 | + transition: 'height 0.5s ease-in-out', |
| 159 | + }}> |
| 160 | + <div className="flex cursor-pointer items-center justify-between bg-gray-100 p-4" onClick={() => { |
| 161 | + const config = document.getElementById('config'); |
| 162 | + const h12Exist = config.classList.contains('h-12'); // 12 在那就是未展开, auto 就是展开 |
| 163 | + if (!h12Exist) { |
| 164 | + const height = config.scrollHeight; |
| 165 | + for (let i = 0; i < 11; i++) { |
| 166 | + if (i === 10) { |
| 167 | + setTimeout(() => { |
| 168 | + config.style.height = '66.4px'; |
| 169 | + }, i * 50); |
| 170 | + break; |
| 171 | + } |
| 172 | + setTimeout(() => { |
| 173 | + config.style.height = `${height - (height / 10) * i}px`; |
| 174 | + }, i * 50); |
| 175 | + } |
| 176 | + config.classList.add('h-12'); |
| 177 | + } else { // 收起 |
| 178 | + const height = config.scrollHeight; |
| 179 | + for (let i = 0; i < 11; i++) { |
| 180 | + setTimeout(() => { |
| 181 | + config.style.height = `${(height / 10) * i}px`; |
| 182 | + }, i * 50); |
| 183 | + } |
| 184 | + config.classList.remove('h-12'); |
| 185 | + } |
| 186 | + }}> |
| 187 | + <h2 className="text-sm text-gray-500"> |
| 188 | + <span className="text-black">ecosystem.config.js</span> 配置文件 (点击展开) |
| 189 | + </h2> |
| 190 | + <button |
| 191 | + type="button" |
| 192 | + className="text-gray-500 focus:outline-none hover:text-black transition duration-300" |
| 193 | + > |
| 194 | + <svg |
| 195 | + xmlns="http://www.w3.org/2000/svg" |
| 196 | + className="h-4 w-4" |
| 197 | + viewBox="0 0 20 20" |
| 198 | + fill="currentColor" |
| 199 | + > |
| 200 | + <path |
| 201 | + fillRule="evenodd" |
| 202 | + d="M10 12a2 2 0 100-4 2 2 0 000 4z" |
| 203 | + clipRule="evenodd" |
| 204 | + /> |
| 205 | + <path |
| 206 | + fillRule="evenodd" |
| 207 | + d="M10 2a8 8 0 100 16 8 8 0 000-16zM2 10a8 8 0 1116 0 8 8 0 01-16 0z" |
| 208 | + clipRule="evenodd" |
| 209 | + /> |
| 210 | + </svg> |
| 211 | + </button> |
| 212 | + </div> |
| 213 | + <div className="p-4"> |
| 214 | + <pre className="rounded-lg text-sm"> |
| 215 | + <code>{nowTemplate}</code> |
| 216 | + </pre> |
| 217 | + </div> |
| 218 | + </div> |
| 219 | + <h2 className="text-xl font-semibold mb-4">环境变量</h2> |
| 220 | + {Object.keys(env).map((key, index) => ( |
| 221 | + <div key={key} className="flex items-center space-x-4 mb-4"> |
| 222 | + <input |
| 223 | + type="text" |
| 224 | + className="border rounded px-2 py-2 w-1/2 bg-transparent focus:outline-none focus:border-black hover:border-white-400 transition duration-300 font-[400] font-sans text-sm cursor-not-allowed" |
| 225 | + value={`${key} (${env[key].tip})`} |
| 226 | + disabled |
| 227 | + /> |
| 228 | + <input |
| 229 | + type={env[key].type} |
| 230 | + className="border rounded px-2 py-2 w-1/2 focus:outline-none focus:border-black hover:border-gray-400 transition duration-300 font-[400] font-sans text-sm" style={{ outline: "none", boxShadow: "none" }} |
| 231 | + placeholder={`Enter value...`} |
| 232 | + value={envValues[Object.keys(env).indexOf(key)]} |
| 233 | + onChange={(e) => handleChange(index, e.target.value)} |
| 234 | + /> |
| 235 | + </div> |
| 236 | + ))} |
| 237 | + </div> |
| 238 | + <div className="flex p-4"> |
| 239 | + <h2 className="text-xl font-semibold mb-4">配置选项</h2> |
| 240 | + {args.map((arg) => ( |
| 241 | + <button |
| 242 | + key={arg} |
| 243 | + className={`border rounded px-2 py-2 mr-4 mb-4 focus:outline-none focus:border-black hover:border-gray-400 transition duration-300 font-[400] font-sans text-sm ${ |
| 244 | + selectedArgs.includes(arg) ? 'bg-gray-200' : 'bg-transparent' |
| 245 | + }`} |
| 246 | + onClick={() => handleToggleArg(arg)} |
| 247 | + > |
| 248 | + {arg} |
| 249 | + </button> |
| 250 | + ))} |
| 251 | + </div> |
| 252 | + |
| 253 | + |
| 254 | + <button |
| 255 | + type="button" |
| 256 | + className="border bg-black w-full text-white px-4 py-2 rounded-lg text-sm transform transition-all duration-300 focus:outline-none hover:bg-gray-700" |
| 257 | + onClick={handleCopyToClipboard} |
| 258 | + > |
| 259 | + 复制 |
| 260 | + </button> |
| 261 | + </div> |
| 262 | + ); |
| 263 | +}; |
0 commit comments