| @@ -0,0 +1,8 @@ | |||||
| { | |||||
| "presets": [ | |||||
| ["es2015", {"loose": true}], | |||||
| "stage-1" | |||||
| ], | |||||
| "plugins": ["transform-runtime"], | |||||
| "sourceMaps": true | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| # Logs | |||||
| logs | |||||
| *.log | |||||
| # Runtime data | |||||
| pids | |||||
| *.pid | |||||
| *.seed | |||||
| # Directory for instrumented libs generated by jscoverage/JSCover | |||||
| lib-cov | |||||
| # Coverage directory used by tools like istanbul | |||||
| coverage/ | |||||
| # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | |||||
| .grunt | |||||
| # node-waf configuration | |||||
| .lock-wscript | |||||
| # Dependency directory | |||||
| # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git | |||||
| node_modules/ | |||||
| # IDE config | |||||
| .idea | |||||
| # output | |||||
| output/ | |||||
| output.tar.gz | |||||
| app/ | |||||
| runtime/ | |||||
| @@ -0,0 +1,5 @@ | |||||
| { | |||||
| "createAt": "2025-03-25 17:17:46", | |||||
| "mode": "module", | |||||
| "es": true | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| Application created by [ThinkJS](http://www.thinkjs.org) | |||||
| ## Install dependencies | |||||
| ``` | |||||
| npm install | |||||
| ``` | |||||
| ## Start server | |||||
| ``` | |||||
| npm start | |||||
| ``` | |||||
| ## Deploy with pm2 | |||||
| Use pm2 to deploy app on production enviroment. | |||||
| ``` | |||||
| pm2 startOrReload pm2.json | |||||
| ``` | |||||
| @@ -0,0 +1,120 @@ | |||||
| server { | |||||
| listen 80; | |||||
| server_name example.com www.example.com; | |||||
| root D:\demo\ssl\ssl_manage/www; | |||||
| set $node_port 8360; | |||||
| index index.js index.html index.htm; | |||||
| if ( -f $request_filename/index.html ){ | |||||
| rewrite (.*) $1/index.html break; | |||||
| } | |||||
| if ( !-f $request_filename ){ | |||||
| rewrite (.*) /index.js; | |||||
| } | |||||
| location = /index.js { | |||||
| proxy_http_version 1.1; | |||||
| proxy_set_header X-Real-IP $remote_addr; | |||||
| proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| proxy_set_header Host $http_host; | |||||
| proxy_set_header X-NginX-Proxy true; | |||||
| proxy_set_header Upgrade $http_upgrade; | |||||
| proxy_set_header Connection "upgrade"; | |||||
| proxy_pass http://127.0.0.1:$node_port$request_uri; | |||||
| proxy_redirect off; | |||||
| } | |||||
| location = /development.js { | |||||
| deny all; | |||||
| } | |||||
| location = /testing.js { | |||||
| deny all; | |||||
| } | |||||
| location = /production.js { | |||||
| deny all; | |||||
| } | |||||
| location ~ /static/ { | |||||
| etag on; | |||||
| expires max; | |||||
| } | |||||
| } | |||||
| ## http/2 nginx conf | |||||
| # server { | |||||
| # listen 80; | |||||
| # server_name example.com www.example.com; | |||||
| # rewrite ^(.*) https://example.com$1 permanent; | |||||
| # } | |||||
| # | |||||
| # server { | |||||
| # listen 443 ssl http2 fastopen=3 reuseport; | |||||
| # server_name www.thinkjs.org thinkjs.org; | |||||
| # set $node_port 8360; | |||||
| # | |||||
| # root D:\demo\ssl\ssl_manage/www; | |||||
| # | |||||
| # keepalive_timeout 70; | |||||
| # | |||||
| # ssl_certificate /path/to/certificate; | |||||
| # ssl_certificate_key /path/to/certificate.key; | |||||
| # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; | |||||
| # ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"; | |||||
| # ssl_prefer_server_ciphers on; | |||||
| # # openssl dhparam -out dhparams.pem 2048 | |||||
| # ssl_dhparam /path/to/dhparams.pem; | |||||
| # | |||||
| # ssl_session_cache shared:SSL:10m; | |||||
| # ssl_session_timeout 10m; | |||||
| # | |||||
| # ssl_session_ticket_key /path/to/tls_session_ticket.key; | |||||
| # ssl_session_tickets on; | |||||
| # | |||||
| # ssl_stapling on; | |||||
| # ssl_stapling_verify on; | |||||
| # ssl_trusted_certificate /path/to/startssl_trust_chain.crt; | |||||
| # | |||||
| # | |||||
| # add_header x-Content-Type-Options nosniff; | |||||
| # add_header X-Frame-Options deny; | |||||
| # add_header Strict-Transport-Security "max-age=16070400"; | |||||
| # | |||||
| # index index.js index.html index.htm; | |||||
| # if ( -f $request_filename/index.html ){ | |||||
| # rewrite (.*) $1/index.html break; | |||||
| # } | |||||
| # if ( !-f $request_filename ){ | |||||
| # rewrite (.*) /index.js; | |||||
| # } | |||||
| # location = /index.js { | |||||
| # proxy_http_version 1.1; | |||||
| # proxy_set_header X-Real-IP $remote_addr; | |||||
| # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | |||||
| # proxy_set_header Host $http_host; | |||||
| # proxy_set_header X-NginX-Proxy true; | |||||
| # proxy_set_header Upgrade $http_upgrade; | |||||
| # proxy_set_header Connection "upgrade"; | |||||
| # proxy_pass http://127.0.0.1:$node_port$request_uri; | |||||
| # proxy_redirect off; | |||||
| # } | |||||
| # | |||||
| # location = /production.js { | |||||
| # deny all; | |||||
| # } | |||||
| # | |||||
| # location = /testing.js { | |||||
| # deny all; | |||||
| # } | |||||
| # | |||||
| # location ~ /static/ { | |||||
| # etag on; | |||||
| # expires max; | |||||
| # } | |||||
| #} | |||||
| @@ -0,0 +1,30 @@ | |||||
| { | |||||
| "name": "thinkjs-application", | |||||
| "description": "application created by thinkjs", | |||||
| "version": "1.0.0", | |||||
| "scripts": { | |||||
| "start": "node www/development.js", | |||||
| "compile": "babel src/ --out-dir app/", | |||||
| "watch-compile": "node -e \"console.log('<npm run watch-compile> no longer need, use <npm start> command direct.');console.log();\"", | |||||
| "watch": "npm run watch-compile" | |||||
| }, | |||||
| "dependencies": { | |||||
| "babel-runtime": "6.x.x", | |||||
| "dayjs": "^1.11.13", | |||||
| "fs-extra": "^11.3.0", | |||||
| "greenlock": "^2.8.9", | |||||
| "greenlock-store-fs": "^3.2.2", | |||||
| "le-challenge-fs": "^2.0.9", | |||||
| "source-map-support": "0.4.0", | |||||
| "thinkjs": "v2" | |||||
| }, | |||||
| "devDependencies": { | |||||
| "babel-cli": "^6.18.0", | |||||
| "babel-preset-es2015": "^6.18.0", | |||||
| "babel-preset-stage-1": "^6.16.0", | |||||
| "babel-plugin-transform-runtime": "^6.15.0", | |||||
| "babel-core": "^6.20.0" | |||||
| }, | |||||
| "repository": "", | |||||
| "license": "MIT" | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| { | |||||
| "apps": [ | |||||
| { | |||||
| "name": "ssl", | |||||
| "script": "www/live.js", | |||||
| "cwd": "/home/program/front/git/ssl_manage", | |||||
| "exec_mode": "cluster", | |||||
| "instances": 1, | |||||
| "max_memory_restart": "2G", | |||||
| "autorestart": true, | |||||
| "node_args": [], | |||||
| "args": [], | |||||
| "env": {}, | |||||
| "error_file": "/data/pm2/logs/ssl-err.log", | |||||
| "out_file": "/data/pm2/logs/ssl-out.log" | |||||
| } | |||||
| ] | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| /** | |||||
| * this file will be loaded before server started | |||||
| * you can define global functions used in controllers, models, templates | |||||
| */ | |||||
| /** | |||||
| * use global.xxx to define global functions | |||||
| * | |||||
| * global.fn1 = function(){ | |||||
| * | |||||
| * } | |||||
| */ | |||||
| @@ -0,0 +1,13 @@ | |||||
| /** | |||||
| * this file will be loaded before server started | |||||
| * you can register middleware | |||||
| * https://thinkjs.org/doc/middleware.html | |||||
| */ | |||||
| /** | |||||
| * | |||||
| * think.middleware('xxx', http => { | |||||
| * | |||||
| * }) | |||||
| * | |||||
| */ | |||||
| @@ -0,0 +1,7 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * config | |||||
| */ | |||||
| export default { | |||||
| //key: value | |||||
| }; | |||||
| @@ -0,0 +1,22 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * db config | |||||
| * @type {Object} | |||||
| */ | |||||
| export default { | |||||
| type: 'mysql', | |||||
| adapter: { | |||||
| mysql: { | |||||
| host: '127.0.0.1', | |||||
| port: '', | |||||
| database: '', | |||||
| user: '', | |||||
| password: '', | |||||
| prefix: '', | |||||
| encoding: 'utf8' | |||||
| }, | |||||
| mongo: { | |||||
| } | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1,15 @@ | |||||
| 'use strict'; | |||||
| export default { | |||||
| domains:[ | |||||
| // "oa.live.educlouddata.com", | |||||
| // "m.live.educlouddata.com", | |||||
| // "yun.live.educlouddata.com", | |||||
| // "yunh5.live.educlouddata.com", | |||||
| // "kms.live.educlouddata.com", | |||||
| // "ams.live.educlouddata.com", | |||||
| // "data.live.educlouddata.com", | |||||
| "live.qqsrx.top", | |||||
| ], | |||||
| CERT_DIR:"/usr/local/nginx/conf/cert" | |||||
| }; | |||||
| @@ -0,0 +1,15 @@ | |||||
| 'use strict'; | |||||
| export default { | |||||
| domains:[ | |||||
| // "oa.live.educlouddata.com", | |||||
| // "m.live.educlouddata.com", | |||||
| // "yun.live.educlouddata.com", | |||||
| // "yunh5.live.educlouddata.com", | |||||
| // "kms.live.educlouddata.com", | |||||
| // "ams.live.educlouddata.com", | |||||
| // "data.live.educlouddata.com", | |||||
| "live.qqsrx.top", | |||||
| ], | |||||
| CERT_DIR:"/usr/local/nginx/conf/cert" | |||||
| }; | |||||
| @@ -0,0 +1,5 @@ | |||||
| 'use strict'; | |||||
| export default { | |||||
| resource_on: false | |||||
| }; | |||||
| @@ -0,0 +1,5 @@ | |||||
| 'use strict'; | |||||
| export default { | |||||
| }; | |||||
| @@ -0,0 +1,9 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * err config | |||||
| */ | |||||
| export default { | |||||
| //key: value | |||||
| key: 'errno', //error number | |||||
| msg: 'errmsg' //error message | |||||
| }; | |||||
| @@ -0,0 +1,9 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * hook config | |||||
| * https://thinkjs.org/doc/middleware.html#toc-df6 | |||||
| */ | |||||
| export default { | |||||
| } | |||||
| @@ -0,0 +1,5 @@ | |||||
| 'use strict'; | |||||
| export default { | |||||
| }; | |||||
| @@ -0,0 +1,20 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * session configs | |||||
| */ | |||||
| export default { | |||||
| name: 'thinkjs', | |||||
| type: 'file', | |||||
| secret: 'VH7!OBT^', | |||||
| timeout: 24 * 3600, | |||||
| cookie: { // cookie options | |||||
| length: 32, | |||||
| httponly: true | |||||
| }, | |||||
| adapter: { | |||||
| file: { | |||||
| path: think.RUNTIME_PATH + '/session', | |||||
| } | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1,14 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * template config | |||||
| */ | |||||
| export default { | |||||
| type: 'ejs', | |||||
| content_type: 'text/html', | |||||
| file_ext: '.html', | |||||
| file_depr: '_', | |||||
| root_path: think.ROOT_PATH + '/view', | |||||
| adapter: { | |||||
| ejs: {} | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1,77 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * error controller | |||||
| */ | |||||
| export default class extends think.controller.base { | |||||
| /** | |||||
| * display error page | |||||
| * @param {Number} status [] | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| displayError(status){ | |||||
| //hide error message on production env | |||||
| if(think.env === 'production'){ | |||||
| this.http.error = null; | |||||
| } | |||||
| let errorConfig = this.config('error'); | |||||
| let message = this.http.error && this.http.error.message || ''; | |||||
| if(this.isJsonp()){ | |||||
| return this.jsonp({ | |||||
| [errorConfig.key]: status, | |||||
| [errorConfig.msg]: message | |||||
| }) | |||||
| }else if(this.isAjax()){ | |||||
| return this.fail(status, message); | |||||
| } | |||||
| let module = 'common'; | |||||
| if(think.mode !== think.mode_module){ | |||||
| module = this.config('default_module'); | |||||
| } | |||||
| let file = `${module}/error/${status}.html`; | |||||
| let options = this.config('tpl'); | |||||
| options = think.extend({}, options, {type: 'base', file_depr: '_'}); | |||||
| this.fetch(file, {}, options).then(content => { | |||||
| content = content.replace('ERROR_MESSAGE', message); | |||||
| this.type(options.content_type); | |||||
| this.end(content); | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * Bad Request | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| _400Action(){ | |||||
| return this.displayError(400); | |||||
| } | |||||
| /** | |||||
| * Forbidden | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| _403Action(){ | |||||
| return this.displayError(403); | |||||
| } | |||||
| /** | |||||
| * Not Found | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| _404Action(){ | |||||
| return this.displayError(404); | |||||
| } | |||||
| /** | |||||
| * Internal Server Error | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| _500Action(){ | |||||
| return this.displayError(500); | |||||
| } | |||||
| /** | |||||
| * Service Unavailable | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| _503Action(){ | |||||
| return this.displayError(503); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * config | |||||
| */ | |||||
| export default { | |||||
| //key: value | |||||
| }; | |||||
| @@ -0,0 +1,7 @@ | |||||
| 'use strict'; | |||||
| export default class extends think.controller.base { | |||||
| /** | |||||
| * some base method in here | |||||
| */ | |||||
| } | |||||
| @@ -0,0 +1,236 @@ | |||||
| 'use strict'; | |||||
| import Base from './base.js'; | |||||
| const Greenlock = require('greenlock'); | |||||
| const path = require('path'); | |||||
| const GreenlockStoreFs = require('greenlock-store-fs'); | |||||
| const LeChallengeFs = require('le-challenge-fs'); | |||||
| const fs = require('fs-extra'); | |||||
| const { execSync } = require('child_process'); | |||||
| const https = require('https'); | |||||
| const dayjs = require('dayjs'); // 需安装dayjs库 | |||||
| // SSL根目录 | |||||
| const ROOT_PATH = path.join(think.ROOT_PATH, 'ssl'); | |||||
| const PEM_PATH = path.join(ROOT_PATH, 'cert'); | |||||
| // 创建存储对象 | |||||
| const leStore = GreenlockStoreFs.create({ | |||||
| configDir: path.join(ROOT_PATH, 'letsencrypt'), | |||||
| }); | |||||
| // 创建验证对象 | |||||
| const leHttpChallenge = LeChallengeFs.create({ | |||||
| webrootPath: path.join(ROOT_PATH, 'lechallenge'), | |||||
| }); | |||||
| // 是否同意协议 | |||||
| function leAgree (opts, agreeCb) { | |||||
| agreeCb(null, opts.tosUrl); | |||||
| } | |||||
| // 证书申请对象 | |||||
| const greenlock = Greenlock.create({ | |||||
| version: 'draft-12', | |||||
| // 测试环境 | |||||
| server: 'https://acme-staging-v02.api.letsencrypt.org/directory', | |||||
| // 生产环境 | |||||
| // server: 'https://acme-v02.api.letsencrypt.org/directory', | |||||
| store: leStore, | |||||
| challenges: { | |||||
| 'http-01': leHttpChallenge, | |||||
| }, | |||||
| challengeType: 'http-01', | |||||
| agreeToTerms: leAgree, | |||||
| debug: true, | |||||
| renewBy: 10 * 24 * 60 * 60 * 1000,// 10倒计时开始续期 | |||||
| }); | |||||
| // 创建SSL证书申请请求 | |||||
| async function CreateSSL (domain, email = '') { | |||||
| const results = await greenlock.register({ | |||||
| domains: [domain], | |||||
| email, | |||||
| agreeTos: true, | |||||
| rsaKeySize: 2048, | |||||
| }); | |||||
| // 如果生成证书就保存一次证书 | |||||
| const dir = path.join(PEM_PATH, domain); | |||||
| if (!fs.existsSync(dir)) { | |||||
| fs.mkdirSync(dir); | |||||
| } | |||||
| if (results.cert && results.chain) { | |||||
| fs.writeFileSync(path.join(dir, `${domain}.crt`), results.cert + results.chain, 'utf-8'); | |||||
| console.log(`证书${domain}.crt创建成功!`); | |||||
| } | |||||
| if (results.privkey) { | |||||
| fs.writeFileSync(path.join(dir, `${domain}.key`), results.privkey, 'utf-8'); | |||||
| console.log(`证书${domain}.key创建成功!`); | |||||
| } | |||||
| return results; | |||||
| } | |||||
| /** | |||||
| * 检查证书过期时间 | |||||
| * @param {*} domain | |||||
| * @returns | |||||
| */ | |||||
| function checkCertExpiry (domain) { | |||||
| return new Promise((resolve, reject) => { | |||||
| const req = https.request({ | |||||
| hostname: domain, | |||||
| port: 443, | |||||
| method: 'GET', | |||||
| agent: new https.Agent({ rejectUnauthorized: false }) // 避免自签名证书报错 | |||||
| }, (res) => { | |||||
| const cert = res.socket.getPeerCertificate(); | |||||
| if (cert.valid_to) { | |||||
| resolve(cert.valid_to); | |||||
| } else { | |||||
| reject(new Error('证书信息缺失')); | |||||
| } | |||||
| }); | |||||
| req.on('error', (err) => reject(err)); | |||||
| req.end(); | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * | |||||
| * @param {*} domain | |||||
| * @returns | |||||
| */ | |||||
| async function getRemainingDays (domain) { | |||||
| try { | |||||
| const validTo = await checkCertExpiry(domain); | |||||
| const expiryDate = dayjs(validTo, 'MMM DD HH:mm:ss YYYY GMT'); // 解析时间格式 | |||||
| const currentDate = dayjs(); | |||||
| const daysLeft = expiryDate.diff(currentDate, 'day'); | |||||
| return daysLeft; | |||||
| } catch (err) { | |||||
| console.error(`检测失败:${err.message}`); | |||||
| return -1; // 错误时返回-1 | |||||
| } | |||||
| } | |||||
| export default class extends Base { | |||||
| /** | |||||
| * index action | |||||
| * @return {Promise} [] | |||||
| */ | |||||
| async indexAction () { | |||||
| let domains = this.config().domains; | |||||
| let msgs = [] | |||||
| for (let domain of domains) { | |||||
| try { | |||||
| const daysLeft = await getRemainingDays(domain); | |||||
| const msg = `证书${domain},剩余${daysLeft}天` | |||||
| console.log(msg); | |||||
| msgs.push(msg); | |||||
| } catch (e) { | |||||
| const msg = `证书${domain},证书缺失或无法访问` | |||||
| console.log(e); | |||||
| msgs.push(msg); | |||||
| } | |||||
| } | |||||
| this.assign({ | |||||
| msgs: msgs.join('\n') | |||||
| }); | |||||
| return this.display(); | |||||
| } | |||||
| /** | |||||
| * 检查并更新证书 | |||||
| */ | |||||
| async checksslAction () { | |||||
| let domains = this.config().domains; | |||||
| let updateList = []; //需要更新的数据 | |||||
| for (let domain of domains) { | |||||
| try { | |||||
| const daysLeft = await getRemainingDays(domain); | |||||
| const msg = `证书${domain},剩余${daysLeft}天`; | |||||
| console.log(msg); | |||||
| if (daysLeft < 90) { | |||||
| updateList.push(domain); | |||||
| } | |||||
| } catch (e) { | |||||
| console.log("==".repeat(20)) | |||||
| console.log('检查证书失败:', e); | |||||
| console.log("==".repeat(20)) | |||||
| } | |||||
| } | |||||
| if (updateList.length) { | |||||
| console.log("==".repeat(20), "开始生成证书", "==".repeat(20)); | |||||
| console.log('开始时间:', dayjs().format('YYYY-MM-DD HH:mm:ss')); | |||||
| for (let domain of updateList) { | |||||
| try { | |||||
| const results = await CreateSSL(domain); | |||||
| console.log(`证书${domain},生成成功`); | |||||
| } catch (error) { | |||||
| console.log("==".repeat(20), "证书生成失败", "==".repeat(20)); | |||||
| console.log("域名:", domain); | |||||
| console.log(e); | |||||
| console.log("==".repeat(20), "证书生成失败", "==".repeat(20)); | |||||
| } | |||||
| } | |||||
| await this.deployCert(updateList); | |||||
| console.log(`更新证书数量:${updateList.length}`); | |||||
| console.log('结束时间:', dayjs().format('YYYY-MM-DD HH:mm:ss')); | |||||
| console.log("==".repeat(20), "结束生成证书", "==".repeat(20)); | |||||
| } | |||||
| this.success(); | |||||
| } | |||||
| async createAction () { | |||||
| const domain = this.post('domain'); | |||||
| const email = this.post('email'); | |||||
| if (!domain || !email) { | |||||
| return this.fail('参数错误'); | |||||
| } | |||||
| try { | |||||
| const results = await CreateSSL(domain, email); | |||||
| this.deployCert([domain]); | |||||
| return this.success(results); | |||||
| } catch (error) { | |||||
| return this.fail(JSON.stringify(error)); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 复制证书到指定文件夹,并重启nginx | |||||
| * @param {*} domains | |||||
| * @returns | |||||
| */ | |||||
| async deployCert (domains) { | |||||
| if (!domains || domains.length === 0) { | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const dir2 = this.config().CERT_DIR; //nginx配置文件中 | |||||
| // 1. 复制证书 | |||||
| for (let domain of domains) { | |||||
| const dir = path.join(PEM_PATH, domain); //生成的证书地址,项目中 | |||||
| let originCrt = `${dir}/${domain}.crt`; | |||||
| let originKey = `${dir}/${domain}.key`; | |||||
| if (fs.existsSync(originCrt) && fs.existsSync(originKey)) { | |||||
| await fs.copy(`${dir}/${domain}.crt`, `${dir2}/${domain}.crt`); | |||||
| await fs.copy(`${dir}/${domain}.key`, `${dir2}/${domain}.key`); | |||||
| } | |||||
| } | |||||
| // 2. 检查配置 | |||||
| execSync('nginx -t'); | |||||
| // 3. 重载服务 | |||||
| execSync('nginx -s reload'); | |||||
| console.log('证书部署成功!'); | |||||
| } catch (error) { | |||||
| console.error('部署失败:', error.message); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * logic | |||||
| * @param {} [] | |||||
| * @return {} [] | |||||
| */ | |||||
| export default class extends think.logic.base { | |||||
| /** | |||||
| * index action logic | |||||
| * @return {} [] | |||||
| */ | |||||
| indexAction(){ | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| 'use strict'; | |||||
| /** | |||||
| * model | |||||
| */ | |||||
| export default class extends think.model.base { | |||||
| } | |||||
| @@ -0,0 +1,72 @@ | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>New ThinkJS Application</title> | |||||
| <style> | |||||
| * { | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| font-size: 16px; | |||||
| line-height: 20px; | |||||
| font-family: arial; | |||||
| } | |||||
| a, | |||||
| a:visited { | |||||
| color: #337ab7; | |||||
| text-decoration: none; | |||||
| } | |||||
| header { | |||||
| padding: 70px 0 70px 0; | |||||
| background-color: #4A6495 | |||||
| } | |||||
| h1 { | |||||
| font-size: 36px; | |||||
| color: #fff; | |||||
| font-weight: normal; | |||||
| } | |||||
| code { | |||||
| padding: 2px 4px; | |||||
| font-size: 90%; | |||||
| color: #c7254e; | |||||
| background-color: #f9f2f4; | |||||
| border-radius: 4px; | |||||
| } | |||||
| .content { | |||||
| width: 1000px; | |||||
| margin: auto | |||||
| } | |||||
| .wrap { | |||||
| width: 1000px; | |||||
| margin: auto | |||||
| } | |||||
| .list { | |||||
| width: 800px; | |||||
| white-space: pre-line; | |||||
| line-height: 3; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <header> | |||||
| <div class="wrap"> | |||||
| <h1>证书自动更新系统</h1> | |||||
| </div> | |||||
| </header> | |||||
| <div class="content"> | |||||
| <div class="list"> | |||||
| <%= msgs %> | |||||
| </div> | |||||
| </div> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,55 @@ | |||||
| ## application | |||||
| ### start server | |||||
| *development* | |||||
| ```js | |||||
| node www/development.js | |||||
| ``` | |||||
| *testing* | |||||
| ```js | |||||
| node www/testing.js | |||||
| ``` | |||||
| *production* | |||||
| ```js | |||||
| node www/production.js | |||||
| ``` | |||||
| or use pm2 to manage node: | |||||
| ``` | |||||
| pm2 start www/production.js | |||||
| ``` | |||||
| ### compile es6 code | |||||
| ``` | |||||
| npm run compile | |||||
| ``` | |||||
| ### how to link resource | |||||
| *in template file* | |||||
| ```html | |||||
| <script src="/static/js/a.js"></script> | |||||
| <img src="/static/img/a.png" alt=""> | |||||
| <link rel="stylesheet" href="/static/css/a.js"> | |||||
| ``` | |||||
| *link image in css* | |||||
| ```css | |||||
| .a{ | |||||
| background: url(../img/a.png) no-repeat; | |||||
| } | |||||
| ``` | |||||
| @@ -0,0 +1,21 @@ | |||||
| var thinkjs = require('thinkjs'); | |||||
| var path = require('path'); | |||||
| var rootPath = path.dirname(__dirname); | |||||
| var instance = new thinkjs({ | |||||
| APP_PATH: rootPath + path.sep + 'app', | |||||
| RUNTIME_PATH: rootPath + path.sep + 'runtime', | |||||
| ROOT_PATH: rootPath, | |||||
| RESOURCE_PATH: __dirname, | |||||
| env: 'development' | |||||
| }); | |||||
| // Build code from src to app directory. | |||||
| instance.compile({ | |||||
| log: true, | |||||
| presets: [], | |||||
| plugins: [] | |||||
| }); | |||||
| instance.run(); | |||||
| @@ -0,0 +1,21 @@ | |||||
| var thinkjs = require('thinkjs'); | |||||
| var path = require('path'); | |||||
| var rootPath = path.dirname(__dirname); | |||||
| var instance = new thinkjs({ | |||||
| APP_PATH: rootPath + path.sep + 'app', | |||||
| RUNTIME_PATH: rootPath + path.sep + 'runtime', | |||||
| ROOT_PATH: rootPath, | |||||
| RESOURCE_PATH: __dirname, | |||||
| env: 'live' | |||||
| }); | |||||
| // Build code from src to app directory. | |||||
| instance.compile({ | |||||
| log: true, | |||||
| presets: [], | |||||
| plugins: [] | |||||
| }); | |||||
| instance.run(); | |||||
| @@ -0,0 +1,14 @@ | |||||
| var thinkjs = require('thinkjs'); | |||||
| var path = require('path'); | |||||
| var rootPath = path.dirname(__dirname); | |||||
| var instance = new thinkjs({ | |||||
| APP_PATH: rootPath + path.sep + 'app', | |||||
| RUNTIME_PATH: rootPath + path.sep + 'runtime', | |||||
| ROOT_PATH: rootPath, | |||||
| RESOURCE_PATH: __dirname, | |||||
| env: 'production' | |||||
| }); | |||||
| instance.run(true); | |||||
| @@ -0,0 +1,14 @@ | |||||
| var thinkjs = require('thinkjs'); | |||||
| var path = require('path'); | |||||
| var rootPath = path.dirname(__dirname); | |||||
| var instance = new thinkjs({ | |||||
| APP_PATH: rootPath + path.sep + 'app', | |||||
| RUNTIME_PATH: rootPath + path.sep + 'runtime', | |||||
| ROOT_PATH: rootPath, | |||||
| RESOURCE_PATH: __dirname, | |||||
| env: 'testing' | |||||
| }); | |||||
| instance.run(); | |||||