diff --git a/apps/cli/package.json b/apps/cli/package.json index 595f1aca..0a655ae4 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -36,6 +36,7 @@ "@types/dockerode": "^3.3.10", "as-table": "^1.0.55", "chalk": "4.1.2", + "crypto-random-string": "3.3.1", "dockerode": "^3.3.4", "dotenv": "^16.0.0", "filesize": "^8.0.7", diff --git a/apps/cli/src/commands/docker/init.ts b/apps/cli/src/commands/docker/init.ts index a7d6e34b..b69929d0 100644 --- a/apps/cli/src/commands/docker/init.ts +++ b/apps/cli/src/commands/docker/init.ts @@ -5,10 +5,18 @@ import got from 'got'; import path from 'path'; import chalk from 'chalk'; import inquirer from 'inquirer'; +import randomString from 'crypto-random-string'; // https://docs.docker.com/engine/api/v1.41/ -const initWelcome = chalk.green(`================ +const initWelcome = `================ +正在为您初始化 Tailchat 配置与环境变量 +完整的环境变量列表可以访问: ${chalk.underline( + 'https://tailchat.msgbyte.com/docs/deployment/environment' +)} 以了解更多 +================`; + +const initCompleted = chalk.green(`================ 恭喜你已经成功安装了, 你的配置文件已经准备就绪,距离成功就差一步了! 你的tailchat配置文件都被存储在: ${chalk.underline( @@ -33,17 +41,95 @@ export const dockerInitCommand: CommandModule = { async handler(args) { const spinner = ora(); try { + console.log(initWelcome); + const { dir, secret, apiUrl, fileLimit } = await inquirer.prompt([ + { + name: 'dir', + type: 'input', + default: './tailchat', + message: '配置存放目录', + }, + { + name: 'secret', + type: 'input', + default: randomString({ length: 16 }), + message: + '(SECRET)请输入任意字符串,这将会作为Tailchat进行用户身份签名的秘钥,泄露该字符串则会产生身份伪造的风险, 默认随机生成一个16位字符串', + }, + { + name: 'apiUrl', + type: 'input', + message: + '(API_URL)请配置外网能够访问的地址,这个会影响文件的存储路径与访问地址, 示例: https://tailchat.example.com', + }, + { + name: 'fileLimit', + type: 'number', + default: 1048576, + message: '(FILE_LIMIT)文件上传体积限制, 默认为 1048576(1m)', + }, + ]); + spinner.start('开始下载最新的配置文件'); - const [rawEnv, rawConfig] = await Promise.all([ + // eslint-disable-next-line prefer-const + let [rawEnv, rawConfig] = await Promise.all([ got(envUrl).then((res) => res.body), got(configUrl).then((res) => res.body), ]); spinner.info('配置文件下载完毕'); - // TODO: 需要实现交互式初始化,引导用户配置SECRET,API_URL, SMTP 服务 + if (secret) { + rawEnv = setEnvValue(rawEnv, 'SECRET', secret); + } + + if (apiUrl) { + rawEnv = setEnvValue(rawEnv, 'API_URL', apiUrl); + } + + if (fileLimit) { + rawEnv = setEnvValue(rawEnv, 'FILE_LIMIT', fileLimit); + } + + if ( + await promptConfirm( + '需要配置邮件服务么?邮件服务可以用于密码找回以及邮件通知等功能。' + ) + ) { + const { stmpURI, stmpSender } = await inquirer.prompt([ + { + name: 'stmpURI', + type: 'input', + message: + '(SMTP_URI)请配置邮件服务SMTP的连接地址,示例: smtps://username:password@example.mailserver.com/?pool=true', + }, + { + name: 'stmpSender', + type: 'input', + message: + '(SMTP_SENDER)邮件发送人,示例: "Tailchat" tailchat@example.mailserver.com', + }, + ]); + + if (stmpURI) { + rawEnv = setEnvValue(rawEnv, 'SMTP_URI', stmpURI); + } + if (stmpSender) { + rawEnv = setEnvValue(rawEnv, 'SMTP_SENDER', stmpSender); + } - spinner.info('正在创建目录 tailchat ...'); - await fs.mkdir('./tailchat'); + if (stmpURI && stmpSender) { + if ( + await promptConfirm( + '是否需要开启邮箱校验? 开启后当用户注册时需要校验邮箱通过以后才能继续注册' + ) + ) { + rawEnv = setEnvValue(rawEnv, 'EMAIL_VERIFY', 'true'); + } + } + } + + spinner.info(`正在创建目录 ${dir} ...`); + await fs.mkdirp(dir); spinner.info('正在写入配置文件 ...'); @@ -51,12 +137,42 @@ export const dockerInitCommand: CommandModule = { fs.writeFile('./tailchat/docker-compose.env', rawEnv), fs.writeFile('./tailchat/docker-compose.yml', rawConfig), ]); - spinner.succeed('配置初始化完毕'); - console.log(initWelcome); + + console.log(initCompleted); } catch (err) { spinner.fail('Tailchat with docker 初始化出现意外'); console.error(err); } }, }; + +/** + * 设置环境变量值 + */ +function setEnvValue(text: string, key: string, value: string): string { + const re = new RegExp(`${key}=(.*?)\n`); + if (re.test(text)) { + // 配置文件已经有了 + return text.replace(re, `${key}=${value}\n`); + } + + // 配置文件还没有 + return text + `\n${key}=${value}\n`; +} + +/** + * 设置更多配置的确认项 + */ +async function promptConfirm(message: string): Promise { + const { res } = await inquirer.prompt([ + { + name: 'res', + type: 'confirm', + message, + default: false, + }, + ]); + + return res; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b4a0008..52428196 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,7 @@ importers: as-table: ^1.0.55 chalk: 4.1.2 cross-env: ^7.0.3 + crypto-random-string: 3.3.1 dockerode: ^3.3.4 dotenv: ^16.0.0 filesize: ^8.0.7 @@ -99,6 +100,7 @@ importers: '@types/dockerode': 3.3.10 as-table: 1.0.55 chalk: 4.1.2 + crypto-random-string: 3.3.1 dockerode: 3.3.4 dotenv: 16.0.2 filesize: 8.0.7