mirror of https://github.com/certd/certd
feat: 自动申请证书
parent
62e3945d30
commit
458486dd6b
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"packages": [
|
||||
"packages/deploy/*",
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"lerna": "^3.18.4"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "standard",
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "certd",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"exports": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "Greper",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"acme-client": "^4.1.2",
|
||||
"chai": "^4.2.0",
|
||||
"dayjs": "^1.9.7",
|
||||
"lodash": "^4.17.20",
|
||||
"log4js": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.15.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"mocha": "^8.2.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import acme from 'acme-client'
|
||||
import log from './utils/util.log.js'
|
||||
import _ from 'lodash'
|
||||
import sleep from './utils/util.sleep.js'
|
||||
export class AcmeService {
|
||||
constructor (store) {
|
||||
this.store = store
|
||||
}
|
||||
|
||||
async getAccountKey (email) {
|
||||
let key = this.store.get(this.buildAccountKeyPath(email))
|
||||
if (key == null) {
|
||||
key = await this.createNewKey({ email })
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
buildAccountKeyPath (email) {
|
||||
return email + '/acme/account.key'
|
||||
}
|
||||
|
||||
setAccountKey (email, privateKey) {
|
||||
this.store.set(this.buildAccountKeyPath(email), privateKey)
|
||||
}
|
||||
|
||||
async getAcmeClient (email) {
|
||||
const key = await this.getAccountKey(email)
|
||||
const client = new acme.Client({
|
||||
directoryUrl: acme.directory.letsencrypt.staging,
|
||||
accountKey: key
|
||||
})
|
||||
return client
|
||||
}
|
||||
|
||||
async createNewKey ({ email }) {
|
||||
const privateKey = await acme.forge.createPrivateKey()
|
||||
this.setAccountKey(email, privateKey)
|
||||
}
|
||||
|
||||
async loggerin () {
|
||||
|
||||
}
|
||||
|
||||
async challengeCreateFn (authz, challenge, keyAuthorization, dnsProvider) {
|
||||
log.info('Triggered challengeCreateFn()')
|
||||
|
||||
/* http-01 */
|
||||
if (challenge.type === 'http-01') {
|
||||
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`
|
||||
const fileContents = keyAuthorization
|
||||
|
||||
log.info(`Creating challenge response for ${authz.identifier.value} at path: ${filePath}`)
|
||||
|
||||
/* Replace this */
|
||||
log.info(`Would write "${fileContents}" to path "${filePath}"`)
|
||||
// await fs.writeFileAsync(filePath, fileContents);
|
||||
} else if (challenge.type === 'dns-01') {
|
||||
/* dns-01 */
|
||||
const dnsRecord = `_acme-challenge.${authz.identifier.value}`
|
||||
const recordValue = keyAuthorization
|
||||
|
||||
log.info(`Creating TXT record for ${authz.identifier.value}: ${dnsRecord}`)
|
||||
|
||||
/* Replace this */
|
||||
log.info(`Would create TXT record "${dnsRecord}" with value "${recordValue}"`)
|
||||
|
||||
try {
|
||||
await dnsProvider.createRecord(dnsRecord, 'TXT', recordValue)
|
||||
} catch (e) {
|
||||
if (e.code === 'DomainRecordDuplicate') {
|
||||
await dnsProvider.removeRecord(dnsRecord, 'TXT')
|
||||
await sleep(1000)
|
||||
await dnsProvider.createRecord(dnsRecord, 'TXT', recordValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to remove an ACME challenge response
|
||||
*
|
||||
* @param {object} authz Authorization object
|
||||
* @param {object} challenge Selected challenge
|
||||
* @param {string} keyAuthorization Authorization key
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
async challengeRemoveFn (authz, challenge, keyAuthorization, dnsProvider) {
|
||||
log.info('Triggered challengeRemoveFn()')
|
||||
|
||||
/* http-01 */
|
||||
if (challenge.type === 'http-01') {
|
||||
const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`
|
||||
|
||||
log.info(`Removing challenge response for ${authz.identifier.value} at path: ${filePath}`)
|
||||
|
||||
/* Replace this */
|
||||
log.info(`Would remove file on path "${filePath}"`)
|
||||
// await fs.unlinkAsync(filePath);
|
||||
} else if (challenge.type === 'dns-01') {
|
||||
const dnsRecord = `_acme-challenge.${authz.identifier.value}`
|
||||
const recordValue = keyAuthorization
|
||||
|
||||
log.info(`Removing TXT record for ${authz.identifier.value}: ${dnsRecord}`)
|
||||
|
||||
/* Replace this */
|
||||
log.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`)
|
||||
await dnsProvider.removeRecord(dnsRecord, 'TXT', keyAuthorization)
|
||||
}
|
||||
}
|
||||
|
||||
async order ({ email, domains, dnsProvider, csrInfo }) {
|
||||
const client = await this.getAcmeClient(email)
|
||||
/* Create CSR */
|
||||
const { commonName, altNames } = this.buildCommonNameByDomains(domains)
|
||||
|
||||
const [key, csr] = await acme.forge.createCsr({
|
||||
commonName,
|
||||
...csrInfo,
|
||||
altNames
|
||||
})
|
||||
|
||||
/* Certificate */
|
||||
const crt = await client.auto({
|
||||
csr,
|
||||
email: email,
|
||||
termsOfServiceAgreed: true,
|
||||
challengePriority: ['dns-01', 'http-01'],
|
||||
challengeCreateFn: (authz, challenge, keyAuthorization) => {
|
||||
return this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider)
|
||||
},
|
||||
challengeRemoveFn: (authz, challenge, keyAuthorization) => {
|
||||
return this.challengeRemoveFn(authz, challenge, keyAuthorization, dnsProvider)
|
||||
}
|
||||
})
|
||||
|
||||
/* Done */
|
||||
log.info(`CSR:\n${csr.toString()}`)
|
||||
log.info(`Private key:\n${key.toString()}`)
|
||||
log.info(`Certificate:\n${crt.toString()}`)
|
||||
|
||||
return { key, crt, csr }
|
||||
}
|
||||
|
||||
buildCommonNameByDomains (domains) {
|
||||
if (typeof domains === 'string') {
|
||||
domains = domains.split(',')
|
||||
}
|
||||
if (domains.length === 0) {
|
||||
throw new Error('domain can not be empty')
|
||||
}
|
||||
const ret = {
|
||||
commonName: domains[0]
|
||||
}
|
||||
if (domains.length > 1) {
|
||||
ret.altNames = _.slice(domains, 1)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export class DnsProviderFactory {
|
||||
static async createByType (type, options) {
|
||||
const ProviderModule = await import('./impl/' + type + '.js')
|
||||
const Provider = ProviderModule.default
|
||||
return new Provider(options)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export class DnsProvider {
|
||||
createRecord (dnsRecord, type, recordValue) {
|
||||
|
||||
}
|
||||
|
||||
removeRecord (dnsRecord, type) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import { DnsProvider } from '../dns-provider.js'
|
||||
import Core from '@alicloud/pop-core'
|
||||
import _ from 'lodash'
|
||||
import log from '../../utils/util.log.js'
|
||||
export default class AliyunDnsProvider extends DnsProvider {
|
||||
constructor (dnsProviderConfig) {
|
||||
super()
|
||||
this.client = new Core({
|
||||
accessKeyId: dnsProviderConfig.accessKeyId,
|
||||
accessKeySecret: dnsProviderConfig.accessKeySecret,
|
||||
endpoint: 'https://alidns.aliyuncs.com',
|
||||
apiVersion: '2015-01-09'
|
||||
})
|
||||
}
|
||||
|
||||
async getDomainList () {
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou'
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const ret = await this.client.request('DescribeDomains', params, requestOption)
|
||||
return ret.Domains.Domain
|
||||
}
|
||||
|
||||
async matchDomain (dnsRecord) {
|
||||
const list = await this.getDomainList()
|
||||
let domain = null
|
||||
for (const item of list) {
|
||||
if (_.endsWith(dnsRecord, item.DomainName)) {
|
||||
domain = item.DomainName
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!domain) {
|
||||
throw new Error('can not find Domain ,' + dnsRecord)
|
||||
}
|
||||
return domain
|
||||
}
|
||||
|
||||
async getRecords (domain, rr, value) {
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
DomainName: domain,
|
||||
RRKeyWord: rr
|
||||
}
|
||||
if (value) {
|
||||
params.ValueKeyWord = value
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const ret = await this.client.request('DescribeDomainRecords', params, requestOption)
|
||||
return ret.DomainRecords.Record
|
||||
}
|
||||
|
||||
async createRecord (dnsRecord, type, recordValue) {
|
||||
const domain = await this.matchDomain(dnsRecord)
|
||||
const rr = dnsRecord.replace('.' + domain, '')
|
||||
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
DomainName: domain,
|
||||
RR: rr,
|
||||
Type: type,
|
||||
Value: recordValue
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = await this.client.request('AddDomainRecord', params, requestOption)
|
||||
return ret.RecordId
|
||||
} catch (e) {
|
||||
// e.code === 'DomainRecordDuplicate'
|
||||
console.log('添加域名解析出错', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async removeRecord (dnsRecord, type, value) {
|
||||
const domain = await this.matchDomain(dnsRecord)
|
||||
const rr = dnsRecord.replace('.' + domain, '')
|
||||
|
||||
const record = await this.getRecords(domain, rr, value)
|
||||
|
||||
const params = {
|
||||
RegionId: 'cn-hangzhou',
|
||||
RecordId: record[0].RecordId
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const ret = await this.client.request('DeleteDomainRecord', params, requestOption)
|
||||
log.info('delete record success:', ret.RecordId)
|
||||
return ret.RecordId
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { AcmeService } from './acme.js'
|
||||
import { FileStore } from './store/file-store.js'
|
||||
import { DnsProviderFactory } from './dns-provider/dns-provider-factory.js'
|
||||
import dayjs from 'dayjs'
|
||||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
import fs from 'fs'
|
||||
import util from './utils/util.js'
|
||||
import forge from 'node-forge'
|
||||
export class Certd {
|
||||
constructor () {
|
||||
this.store = new FileStore()
|
||||
this.acme = new AcmeService(this.store)
|
||||
}
|
||||
|
||||
buildCertDir (email, domains) {
|
||||
let domainStr = _.join(domains)
|
||||
domainStr = domainStr.replace(/\*/g, '')
|
||||
const dir = path.join(email, '/certs/', domainStr)
|
||||
return dir
|
||||
}
|
||||
|
||||
async certApply (options) {
|
||||
const certOptions = options.cert
|
||||
const providers = options.providers
|
||||
const providerOptions = providers[certOptions.challenge.dnsProvider]
|
||||
const dnsProvider = await DnsProviderFactory.createByType(providerOptions.providerType, providerOptions)
|
||||
const cert = await this.acme.order({
|
||||
email: certOptions.email,
|
||||
domains: certOptions.domains,
|
||||
dnsProvider: dnsProvider,
|
||||
csrInfo: certOptions.csrInfo
|
||||
|
||||
})
|
||||
|
||||
this.writeCert(certOptions.email, certOptions.domains, cert)
|
||||
const { detail, expires } = this.getDetailFromCrt(cert.crt)
|
||||
return {
|
||||
...cert,
|
||||
detail,
|
||||
expires
|
||||
}
|
||||
}
|
||||
|
||||
writeCert (email, domains, cert) {
|
||||
const certFilesRootDir = this.buildCertDir(email, domains)
|
||||
const dirPath = path.join(certFilesRootDir, dayjs().format('YYYY.MM.DD.HHmmss'))
|
||||
this.store.set(path.join(dirPath, '/cert.crt'), cert.crt)
|
||||
this.store.set(path.join(dirPath, '/cert.key'), cert.key)
|
||||
this.store.set(path.join(dirPath, '/cert.csr'), cert.csr)
|
||||
|
||||
const linkPath = path.join(util.getUserBasePath(), certFilesRootDir, 'current')
|
||||
const lastPath = path.join(util.getUserBasePath(), dirPath)
|
||||
// if (!fs.existsSync(linkPath)) {
|
||||
// fs.mkdirSync(linkPath)
|
||||
// }
|
||||
fs.symlinkSync(lastPath, linkPath)
|
||||
}
|
||||
|
||||
readCurrentCert (email, domains) {
|
||||
const certFilesRootDir = this.buildCertDir(email, domains)
|
||||
const currentPath = path.join(certFilesRootDir, 'current')
|
||||
|
||||
const crt = this.store.get(currentPath + '/cert.crt')
|
||||
const key = this.store.get(currentPath + '/cert.key')
|
||||
const csr = this.store.get(currentPath + '/cert.csr')
|
||||
|
||||
const { detail, expires } = this.getDetailFromCrt(crt)
|
||||
|
||||
const cert = {
|
||||
crt, key, csr, detail, expires
|
||||
}
|
||||
return cert
|
||||
}
|
||||
|
||||
getDetailFromCrt (crt) {
|
||||
const pki = forge.pki
|
||||
const detail = pki.certificateFromPem(crt.toString())
|
||||
const expires = detail.validity.notAfter
|
||||
return { detail, expires }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { Store } from './store.js'
|
||||
import util from '../utils/util.js'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
export class FileStore extends Store {
|
||||
constructor () {
|
||||
super()
|
||||
this.rootDir = util.getUserBasePath()
|
||||
}
|
||||
|
||||
getPathByKey (key) {
|
||||
return path.join(this.rootDir, key)
|
||||
}
|
||||
|
||||
set (key, value) {
|
||||
const filePath = this.getPathByKey(key)
|
||||
const dir = path.dirname(filePath)
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
fs.writeFileSync(filePath, value)
|
||||
return filePath
|
||||
}
|
||||
|
||||
get (key) {
|
||||
const filePath = this.getPathByKey(key)
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null
|
||||
}
|
||||
return fs.readFileSync(filePath)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export class Store {
|
||||
set (key, value) {
|
||||
|
||||
}
|
||||
|
||||
get (key) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import path from 'path'
|
||||
|
||||
function getUserBasePath () {
|
||||
const userHome = process.env.USERPROFILE
|
||||
return path.resolve(userHome, './.certd')
|
||||
}
|
||||
export default {
|
||||
getUserBasePath
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import util from './util.js'
|
||||
import log4js from 'log4js'
|
||||
import path from 'path'
|
||||
const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'
|
||||
const filename = path.join(util.getUserBasePath(), '/logs/certd.log')
|
||||
log4js.configure({
|
||||
appenders: { std: { type: 'stdout' }, file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename } },
|
||||
categories: { default: { appenders: ['file', 'std'], level: level } }
|
||||
})
|
||||
const logger = log4js.getLogger('certd')
|
||||
export default logger
|
|
@ -0,0 +1,7 @@
|
|||
export default function (timeout) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, timeout)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import pkg from 'chai'
|
||||
import options from '../options.js'
|
||||
import AliyunDnsProvider from '../../src/dns-provider/impl/aliyun.js'
|
||||
const { expect } = pkg
|
||||
describe('AliyunDnsProvider', function () {
|
||||
it('#getDomainList', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const domainList = await aliyunDnsProvider.getDomainList()
|
||||
console.log('domainList', domainList)
|
||||
expect(domainList.length).gt(0)
|
||||
})
|
||||
|
||||
it('#getRecords', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*')
|
||||
console.log('recordList', recordList)
|
||||
expect(recordList.length).gt(0)
|
||||
})
|
||||
|
||||
it('#createRecord', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const recordId = await aliyunDnsProvider.createRecord('___certd___.__test__.docmirror.cn', 'TXT', 'aaaa')
|
||||
console.log('recordId', recordId)
|
||||
expect(recordId != null).ok
|
||||
})
|
||||
|
||||
it('#removeRecord', async function () {
|
||||
const aliyunDnsProvider = new AliyunDnsProvider(options.providers.aliyun)
|
||||
const recordId = await aliyunDnsProvider.removeRecord('___certd___.__test__.docmirror.cn', 'TXT', 'aaaa')
|
||||
console.log('recordId', recordId)
|
||||
expect(recordId != null).ok
|
||||
})
|
||||
})
|
|
@ -0,0 +1,36 @@
|
|||
import pkg from 'chai'
|
||||
import { Certd } from '../src/index.js'
|
||||
import options from './options.js'
|
||||
import forge from 'node-forge'
|
||||
const { expect } = pkg
|
||||
describe('Certd', function () {
|
||||
it('#buildCertDir', function () {
|
||||
const certd = new Certd()
|
||||
const rootDir = certd.buildCertDir('xiaojunnuo@qq.com', options.cert.domains)
|
||||
console.log('rootDir', rootDir)
|
||||
expect(rootDir).match(/xiaojunnuo@qq.com\\cert\\(.*)\\(.*)/)
|
||||
})
|
||||
it('#writeCert', async function () {
|
||||
const certd = new Certd()
|
||||
certd.writeCert('xiaojunnuo@qq.com', ['*.domain.cn'], { csr: 'csr', crt: 'aaa', key: 'bbb' })
|
||||
})
|
||||
it('#certApply', async function () {
|
||||
this.timeout(80000)
|
||||
const certd = new Certd()
|
||||
const cert = await certd.certApply(options)
|
||||
expect(cert).ok
|
||||
expect(cert.cert).ok
|
||||
expect(cert.key).to.be.ok
|
||||
})
|
||||
|
||||
it('#readCurrentCert', async function () {
|
||||
const certd = new Certd()
|
||||
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
|
||||
expect(cert).to.be.ok
|
||||
expect(cert.crt).ok
|
||||
expect(cert.key).to.be.ok
|
||||
expect(cert.detail).to.be.ok
|
||||
expect(cert.expires).to.be.ok
|
||||
console.log('expires:', cert.expires)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,87 @@
|
|||
import _ from 'lodash'
|
||||
import optionsPrivate from './options.private.js'
|
||||
const defaultOptions = {
|
||||
providers: {
|
||||
aliyun: {
|
||||
providerType: 'aliyun',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: ''
|
||||
},
|
||||
myLinux: {
|
||||
providerType: 'SSH',
|
||||
username: 'xxx',
|
||||
password: 'xxx',
|
||||
host: '1111.com',
|
||||
port: 22,
|
||||
publicKey: ''
|
||||
}
|
||||
},
|
||||
cert: {
|
||||
domains: ['*.docmirror.cn', 'docmirror.cn'],
|
||||
email: 'xiaojunnuo@qq.com',
|
||||
challenge: {
|
||||
challengeType: 'dns',
|
||||
dnsProvider: 'aliyun'
|
||||
},
|
||||
csrInfo: {
|
||||
country: 'CN',
|
||||
state: 'GuangDong',
|
||||
locality: 'ShengZhen',
|
||||
organization: 'CertD Org.',
|
||||
organizationUnit: 'IT Department',
|
||||
emailAddress: 'xiaojunnuo@qq.com'
|
||||
}
|
||||
},
|
||||
deploy: [
|
||||
{
|
||||
deployName: '流程1-部署到阿里云系列产品',
|
||||
tasks: [
|
||||
{
|
||||
name: '上传证书到云',
|
||||
taskType: 'uploadCertToCloud',
|
||||
certStore: 'aliyun'
|
||||
},
|
||||
{
|
||||
name: '部署证书到SLB',
|
||||
taskType: 'deployCertToAliyunSLB',
|
||||
certStore: 'aliyun'
|
||||
},
|
||||
{
|
||||
name: '部署证书到阿里云集群Ingress',
|
||||
taskType: 'deployCertToAliyunK8sIngress',
|
||||
certStore: 'aliyun'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
deployName: '流程2-部署到nginx服务器',
|
||||
tasks: [
|
||||
{
|
||||
name: '上传证书到服务器,并重启nginx',
|
||||
taskType: 'sshAndExecute',
|
||||
ssh: 'myLinux',
|
||||
upload: [
|
||||
{ from: '{certPath}', to: '/xxx/xxx/xxx.cert.pem' },
|
||||
{ from: '{keyPath}', to: '/xxx/xxx/xxx.key' }
|
||||
],
|
||||
script: 'sudo systemctl restart nginx'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
deployName: '流程3-触发jenkins任务',
|
||||
tasks: [
|
||||
{
|
||||
name: '触发jenkins任务',
|
||||
taskType: 'sshAndExecute',
|
||||
ssh: 'myLinux',
|
||||
script: 'sudo systemctl restart nginx'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
_.merge(defaultOptions, optionsPrivate)
|
||||
|
||||
export default defaultOptions
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue