fix
This commit is contained in:
@ -1,86 +0,0 @@
|
||||
import {Cert} from "./index.ts";
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* schedule a retry of certificate request
|
||||
*/
|
||||
export let scheduleRetry = (domainArg:string,certClassArg:Cert) => {
|
||||
let done = plugins.q.defer();
|
||||
setTimeout(() => {
|
||||
certClassArg.getDomainCert(domainArg)
|
||||
.then(done.resolve);
|
||||
},60000);
|
||||
return done.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* check if a given domainCert is still valid
|
||||
*/
|
||||
export let checkDomainsStillValid = (domainNameArg: string, sslDirArg: string): boolean => {
|
||||
let domainConfigPath = plugins.path.join(sslDirArg, domainNameArg, "config.json");
|
||||
if (plugins.smartfile.fs.fileExistsSync(domainConfigPath)) {
|
||||
let domainConfig = plugins.smartfile.fs.toObjectSync(
|
||||
domainConfigPath,
|
||||
"json"
|
||||
);
|
||||
if (Date.now() >= ((domainConfig.expires - 604800) * 1000)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface certConfig {
|
||||
domainName: string;
|
||||
created: number;
|
||||
expires: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* update a ssl directory
|
||||
*/
|
||||
export let updateSslDirSync = (sslDirArg: string, domainNameArg: string) => {
|
||||
plugins.smartfile.fs.ensureDirSync(sslDirArg);
|
||||
let domainCertFolder = plugins.path.join(paths.certDir, domainNameArg)
|
||||
if (plugins.smartfile.fs.listFoldersSync(paths.certDir).indexOf(domainNameArg) != -1) {
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "fullchain.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "fullchain.pem")
|
||||
);
|
||||
plugins.smartfile.fs.copySync(
|
||||
plugins.path.join(domainCertFolder, "privkey.pem"),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "privkey.pem")
|
||||
);
|
||||
// create cert config
|
||||
let certRegex = /.*\-([0-9]*)\.pem/;
|
||||
let certFileNameWithTime: string = plugins.smartfile.fs.listFilesSync(domainCertFolder, certRegex)[0];
|
||||
let certTime = parseInt(certRegex.exec(certFileNameWithTime)[1]);
|
||||
let certConfig: certConfig = {
|
||||
domainName: domainNameArg,
|
||||
created: certTime,
|
||||
expires: certTime + 7776000
|
||||
};
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(certConfig),
|
||||
plugins.path.join(sslDirArg, domainNameArg, "config.json")
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const enum gitSyncDirection {
|
||||
toOrigin,
|
||||
fromOrigin
|
||||
}
|
||||
|
||||
let updateGitOrigin = (syncDirectionArg: gitSyncDirection) => {
|
||||
|
||||
};
|
||||
|
||||
updateGitOrigin(gitSyncDirection.toOrigin);
|
@ -1,148 +1,92 @@
|
||||
import * as q from 'q'
|
||||
import { Stringmap, Objectmap } from 'lik'
|
||||
|
||||
import * as plugins from './cert.plugins'
|
||||
import * as paths from './cert.paths'
|
||||
import * as helpers from './cert.classes.cert.helpers'
|
||||
|
||||
// classes
|
||||
import { Certificate } from './cert.classes.certificate'
|
||||
import { CertRepo } from './cert.classes.certrepo'
|
||||
import { Letsencrypt, TLeEnv } from './cert.classes.letsencrypt'
|
||||
import { ChallengeHandler } from './cert.classes.challengehandler'
|
||||
|
||||
|
||||
export interface ICertConstructorOptions {
|
||||
cfEmail: string,
|
||||
cfKey: string,
|
||||
sslDir?: string,
|
||||
sslDirPath?: string,
|
||||
gitOriginRepo?: string,
|
||||
testMode?: boolean
|
||||
leEnv?: TLeEnv
|
||||
}
|
||||
|
||||
export class Cert {
|
||||
domainCertRequestMap: plugins.lik.Stringmap = new plugins.lik.Stringmap()
|
||||
certificatesPresent: Certificate[]
|
||||
certificatesValid: Certificate[]
|
||||
private _cfEmail: string
|
||||
private _cfKey: string
|
||||
private _sslDir: string
|
||||
private _gitOriginRepo: string
|
||||
private _testMode: boolean
|
||||
private letsencryptInstance
|
||||
domainStringRequestMap = new Stringmap()
|
||||
certificateMap = new Objectmap<Certificate>()
|
||||
letsencrypt: Letsencrypt
|
||||
private _challengeHandler: ChallengeHandler
|
||||
private _certRepo: CertRepo
|
||||
|
||||
/**
|
||||
* Constructor for Cert object
|
||||
*/
|
||||
constructor(optionsArg: ICertConstructorOptions) {
|
||||
this._cfEmail = optionsArg.cfEmail
|
||||
this._cfKey = optionsArg.cfKey
|
||||
this._sslDir = optionsArg.sslDir
|
||||
this._gitOriginRepo = optionsArg.gitOriginRepo
|
||||
this._testMode = optionsArg.testMode
|
||||
// write hook config
|
||||
let config = {
|
||||
cfEmail: this._cfEmail,
|
||||
cfKey: this._cfKey
|
||||
}
|
||||
plugins.smartfile.memory.toFsSync(
|
||||
JSON.stringify(config),
|
||||
plugins.path.join(__dirname, 'assets/config.json')
|
||||
)
|
||||
// setup sslDir
|
||||
if (!this._sslDir) this._sslDir = paths.defaultSslDir
|
||||
// setup Git
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.init(this._sslDir)
|
||||
plugins.smartgit.remote.add(this._sslDir, 'origin', this._gitOriginRepo)
|
||||
this.sslGitOriginPull()
|
||||
}
|
||||
|
||||
// set up challengehandler
|
||||
this._challengeHandler = new ChallengeHandler({
|
||||
cfEmail: optionsArg.cfEmail,
|
||||
cfKey: optionsArg.cfKey
|
||||
})
|
||||
|
||||
// setup Letsencrypt
|
||||
this.letsencrypt = new Letsencrypt({
|
||||
leEnv: optionsArg.leEnv,
|
||||
sslDir: optionsArg.sslDirPath,
|
||||
challengeHandler: this._challengeHandler
|
||||
})
|
||||
|
||||
// setup CertRpo
|
||||
this._certRepo = new CertRepo({
|
||||
sslDirPath: optionsArg.sslDirPath,
|
||||
gitOriginRepo: optionsArg.gitOriginRepo,
|
||||
certInstance: this
|
||||
})
|
||||
|
||||
this._certRepo
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls already requested certificates from git origin
|
||||
* adds a Certificate for a given domain
|
||||
*/
|
||||
sslGitOriginPull = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.pull(this._sslDir, 'origin', 'master')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes all new requested certificates to git origin
|
||||
*/
|
||||
sslGitOriginAddCommitPush = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.add.addAll(this._sslDir)
|
||||
plugins.smartgit.commit(this._sslDir, 'added new SSL certificates and deleted obsolete ones.')
|
||||
plugins.smartgit.push(this._sslDir, 'origin', 'master')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a ssl cert for a given domain
|
||||
*/
|
||||
getDomainCert(domainNameArg: string, optionsArg: { force: boolean } = { force: false }) {
|
||||
addCertificate(domainNameArg: string, optionsArg: { force: boolean } = { force: false }) {
|
||||
let done = q.defer()
|
||||
let domainStringData = new plugins.smartstring.Domain(domainNameArg)
|
||||
let sameZoneRequesting: boolean = this.domainCertRequestMap.checkMinimatch('*' + domainStringData.zoneName)
|
||||
// make sure no one else requires the same domain at the same time
|
||||
if (!this.domainCertRequestMap.checkString(domainNameArg)) {
|
||||
this.domainCertRequestMap.addString(domainNameArg)
|
||||
if (!helpers.checkDomainsStillValid(domainNameArg, this._sslDir) || optionsArg.force) {
|
||||
if (!sameZoneRequesting) {
|
||||
this.sslGitOriginPull()
|
||||
plugins.smartfile.fs.ensureDir(paths.certDir)
|
||||
plugins.beautylog.info(`getting cert for ${domainNameArg}`)
|
||||
plugins.shelljs.exec(
|
||||
`bash -c "${paths.letsencryptSh} -c --no-lock -f ${paths.leShConfig} -d ${domainNameArg} -t dns-01 -k ${paths.certHook} -o ${paths.certDir}"`,
|
||||
{
|
||||
silent: true
|
||||
},
|
||||
(codeArg, stdoutArg) => {
|
||||
if (codeArg === 0) {
|
||||
console.log(stdoutArg)
|
||||
let fetchedCertsArray: string[] = plugins.smartfile.fs.listFoldersSync(paths.certDir)
|
||||
if (fetchedCertsArray.indexOf(domainNameArg) != -1) {
|
||||
helpers.updateSslDirSync(this._sslDir, domainNameArg)
|
||||
plugins.smartfile.fs.removeSync(plugins.path.join(paths.certDir, domainNameArg))
|
||||
this.sslGitOriginAddCommitPush()
|
||||
} else {
|
||||
plugins.beautylog.error(`Couldn't copy final certificate for ${domainNameArg}!`)
|
||||
}
|
||||
done.resolve()
|
||||
} else {
|
||||
plugins.beautylog.warn(`${domainNameArg} scheduled for retry. Waiting 1 minute!`)
|
||||
helpers.scheduleRetry(domainNameArg, this).then(done.resolve)
|
||||
}
|
||||
this.domainCertRequestMap.removeString(domainNameArg)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
plugins.beautylog.info(`${domainNameArg} is waiting for domains names of same zone to finish`)
|
||||
this.domainCertRequestMap.removeString(domainNameArg)
|
||||
this.domainCertRequestMap.registerUntilTrue(
|
||||
() => {
|
||||
return !this.domainCertRequestMap.checkMinimatch('*' + domainStringData.zoneName)
|
||||
},
|
||||
() => {
|
||||
this.getDomainCert(domainNameArg).then(done.resolve)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
plugins.beautylog.info('certificate for ' + domainNameArg + ' is still valid! Not fetching new one!')
|
||||
this.domainCertRequestMap.removeString(domainNameArg)
|
||||
done.resolve()
|
||||
}
|
||||
let certificateForDomain = this.certificateMap.find((certificate) => {
|
||||
return certificate.domainName === domainNameArg
|
||||
})
|
||||
if (certificateForDomain instanceof Certificate) {
|
||||
certificateForDomain.renew()
|
||||
.then(done.resolve)
|
||||
} else {
|
||||
plugins.beautylog.warn(`${domainNameArg} is already requesting`)
|
||||
certificateForDomain = new Certificate({
|
||||
certInstance: this,
|
||||
domainName: domainNameArg
|
||||
})
|
||||
certificateForDomain.renew()
|
||||
.then(done.resolve)
|
||||
}
|
||||
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* cleans up old certificates
|
||||
*/
|
||||
cleanOldCertificates() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class Certificate {
|
||||
domainName: string
|
||||
creationDate: Date
|
||||
expiryDate: Date
|
||||
constructor() {
|
||||
/**
|
||||
* executes the current batch of jobs
|
||||
*/
|
||||
deploy() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
129
ts/cert.classes.certificate.ts
Normal file
129
ts/cert.classes.certificate.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import * as q from 'q'
|
||||
|
||||
import * as plugins from './cert.plugins'
|
||||
import * as paths from './cert.paths'
|
||||
|
||||
// import classes
|
||||
import { Cert } from './cert.classes.cert'
|
||||
import { Letsencrypt } from './cert.classes.letsencrypt'
|
||||
import { CertRepo } from './cert.classes.certrepo'
|
||||
|
||||
export interface ICertificateFsConfig {
|
||||
domainName: string
|
||||
creationTime: number
|
||||
expiryTime: number
|
||||
}
|
||||
|
||||
export interface ICertificateConstructorOptions {
|
||||
domainName: string,
|
||||
certInstance: Cert
|
||||
}
|
||||
|
||||
export type TCertificateStatus = 'unregistered' | 'valid' | 'expiring' | 'expired'
|
||||
|
||||
export class Certificate {
|
||||
domainName: string
|
||||
certInstance: Cert
|
||||
domainData: plugins.smartstring.Domain
|
||||
creationDate: Date = null
|
||||
expiryDate: Date = null
|
||||
publicKey: string = null
|
||||
privKey: string = null
|
||||
|
||||
/**
|
||||
* run when creating a new instance of Certificate
|
||||
*/
|
||||
constructor(optionsArg: ICertificateConstructorOptions) {
|
||||
this.domainName = optionsArg.domainName
|
||||
this.domainData = new plugins.smartstring.Domain(this.domainName)
|
||||
this.certInstance = optionsArg.certInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* the status of the Certificate
|
||||
*/
|
||||
get status(): TCertificateStatus {
|
||||
let validTimeRemaining: number = 0
|
||||
if (this.creationDate !== null && this.expiryDate !== null) {
|
||||
validTimeRemaining = this.expiryDate.getTime() - Date.now()
|
||||
}
|
||||
let MonthMilliseconds = 2629746000
|
||||
if (this.publicKey === null || this.privKey === null) {
|
||||
return 'unregistered'
|
||||
} else if (validTimeRemaining >= MonthMilliseconds) {
|
||||
return 'valid'
|
||||
} else if (validTimeRemaining < MonthMilliseconds && validTimeRemaining >= 0) {
|
||||
return 'expiring'
|
||||
} else {
|
||||
return 'expired'
|
||||
}
|
||||
}
|
||||
|
||||
get sameZoneRequesting(): boolean {
|
||||
return this.certInstance.domainStringRequestMap.checkMinimatch('*' + this.domainData.zoneName)
|
||||
}
|
||||
|
||||
/**
|
||||
* schedule a retry of certificate request
|
||||
*/
|
||||
scheduleRetry() {
|
||||
let done = plugins.q.defer()
|
||||
setTimeout(() => {
|
||||
this.renew()
|
||||
.then(done.resolve)
|
||||
}, 60000)
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* renew certificate if needed
|
||||
*/
|
||||
renew(force: boolean = false) {
|
||||
let done = q.defer()
|
||||
if (this.status === 'valid') {
|
||||
plugins.beautylog.log('Certificate still valid for more than 1 month, so it is not renewed now')
|
||||
done.resolve()
|
||||
} else if (this.status === 'expiring' || this.status === 'expired' || this.status === 'unregistered') {
|
||||
plugins.beautylog.info('Certificate not valid currently, going to renew now!')
|
||||
if (this.sameZoneRequesting) {
|
||||
this.certInstance.domainStringRequestMap.registerUntilTrue(
|
||||
() => {
|
||||
return !this.sameZoneRequesting
|
||||
},
|
||||
() => {
|
||||
this.renew().then(done.resolve)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.certInstance.letsencrypt.registerDomain(this.domainName)
|
||||
.then(() => {
|
||||
return this.syncFs()
|
||||
})
|
||||
.then(() => {
|
||||
done.resolve()
|
||||
}).catch((err) => { console.log(err) })
|
||||
}
|
||||
} else {
|
||||
throw Error(`weird status for certificate with domain name ${this.domainName}`)
|
||||
}
|
||||
done.resolve()
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* syncFs syncs the certificate with disk
|
||||
*/
|
||||
syncFs() {
|
||||
let configJsonMemory: ICertificateFsConfig = {
|
||||
domainName: this.domainName,
|
||||
creationTime: this.creationDate.getTime(),
|
||||
expiryTime: this.expiryDate.getTime()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes the certificate
|
||||
*/
|
||||
delete() { }
|
||||
}
|
65
ts/cert.classes.certrepo.ts
Normal file
65
ts/cert.classes.certrepo.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import * as q from 'q'
|
||||
|
||||
import * as plugins from './cert.plugins'
|
||||
import * as paths from './cert.paths'
|
||||
|
||||
import { Cert } from './cert.classes.cert'
|
||||
import { Certificate } from './cert.classes.certificate'
|
||||
|
||||
export interface ICertRepoConstructorOptions {
|
||||
sslDirPath: string
|
||||
gitOriginRepo: string
|
||||
certInstance: Cert
|
||||
}
|
||||
|
||||
export class CertRepo {
|
||||
private _sslDirPath: string
|
||||
private _gitOriginRepo: string
|
||||
private _certInstance: Cert
|
||||
constructor(optionsArg: ICertRepoConstructorOptions) {
|
||||
this._sslDirPath = optionsArg.sslDirPath
|
||||
this._gitOriginRepo = optionsArg.gitOriginRepo
|
||||
this._certInstance = optionsArg.certInstance
|
||||
|
||||
// setup sslDir
|
||||
if (!this._sslDirPath){
|
||||
this._sslDirPath = paths.defaultSslDir
|
||||
}
|
||||
|
||||
// setup Git
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.init(this._sslDirPath)
|
||||
plugins.smartgit.remote.add(this._sslDirPath, 'origin', this._gitOriginRepo)
|
||||
this.sslGitOriginPull()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* syncs an objectmap of Certificates with repo
|
||||
*/
|
||||
syncFs() {
|
||||
let done = q.defer()
|
||||
done.resolve()
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls already requested certificates from git origin
|
||||
*/
|
||||
sslGitOriginPull = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.pull(this._sslDirPath, 'origin', 'master')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes all new requested certificates to git origin
|
||||
*/
|
||||
sslGitOriginAddCommitPush = () => {
|
||||
if (this._gitOriginRepo) {
|
||||
plugins.smartgit.add.addAll(this._sslDirPath)
|
||||
plugins.smartgit.commit(this._sslDirPath, 'added new SSL certificates and deleted obsolete ones.')
|
||||
plugins.smartgit.push(this._sslDirPath, 'origin', 'master')
|
||||
}
|
||||
}
|
||||
}
|
84
ts/cert.classes.challengehandler.ts
Normal file
84
ts/cert.classes.challengehandler.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import * as plugins from './cert.plugins'
|
||||
import * as paths from './cert.paths'
|
||||
|
||||
export interface IChallengehandlerConstructorOptions {
|
||||
cfEmail: string,
|
||||
cfKey: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* class ChallengeHandler handles challenges
|
||||
*/
|
||||
export class ChallengeHandler {
|
||||
private _cfInstance: plugins.cflare.CflareAccount
|
||||
constructor(optionsArg: IChallengehandlerConstructorOptions) {
|
||||
this._cfInstance = new plugins.cflare.CflareAccount()
|
||||
this._cfInstance.auth({
|
||||
email: optionsArg.cfEmail,
|
||||
key: optionsArg.cfKey
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* set a challenge
|
||||
*/
|
||||
setChallenge(domainNameArg: string, challengeArg: string) {
|
||||
let done = plugins.q.defer()
|
||||
plugins.beautylog.log('setting challenge for ' + domainNameArg)
|
||||
this._cfInstance.createRecord(prefixName(domainNameArg), 'TXT', challengeArg).then(() => {
|
||||
plugins.beautylog.ok('Challenge has been set!')
|
||||
plugins.beautylog.info('We need to cool down to let DNS propagate to edge locations!')
|
||||
cooldown().then(() => {
|
||||
done.resolve()
|
||||
})
|
||||
})
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* cleans a challenge
|
||||
*/
|
||||
cleanChallenge(domainNameArg) {
|
||||
let done = plugins.q.defer()
|
||||
plugins.beautylog.log('cleaning challenge for ' + domainNameArg)
|
||||
this._cfInstance.removeRecord(prefixName(domainNameArg), 'TXT')
|
||||
cooldown().then(() => {
|
||||
done.resolve()
|
||||
})
|
||||
return done.promise
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cooldown timer for letting DNS settle before answering the challengerequest
|
||||
*/
|
||||
let cooldown = () => {
|
||||
let done = plugins.q.defer()
|
||||
let cooldowntime = 60000
|
||||
let passedTime = 0
|
||||
plugins.beautylog.log('Cooling down! ' + (cooldowntime / 1000).toString() + ' seconds left')
|
||||
let coolDownCounter = () => {
|
||||
setTimeout(() => {
|
||||
if (cooldowntime <= passedTime) {
|
||||
plugins.beautylog.ok('Cooled down!')
|
||||
done.resolve()
|
||||
} else {
|
||||
passedTime = passedTime + 5000
|
||||
plugins.beautylog.log('Cooling down! '
|
||||
+ ((cooldowntime - passedTime) / 1000).toString()
|
||||
+ ' seconds left'
|
||||
)
|
||||
coolDownCounter()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
coolDownCounter()
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* prefix a domain name to make sure it complies with letsencrypt
|
||||
*/
|
||||
let prefixName = (domainNameArg: string): string => {
|
||||
return '_acme-challenge.' + domainNameArg
|
||||
}
|
139
ts/cert.classes.letsencrypt.ts
Normal file
139
ts/cert.classes.letsencrypt.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import * as q from 'q'
|
||||
let letsencrypt = require('letsencrypt')
|
||||
|
||||
import * as plugins from './cert.plugins'
|
||||
import * as paths from './cert.paths'
|
||||
|
||||
import { ChallengeHandler } from './cert.classes.challengehandler'
|
||||
|
||||
export type TLeEnv = 'production' | 'staging'
|
||||
|
||||
export interface ILetsencryptConstructorOptions {
|
||||
leEnv: TLeEnv,
|
||||
challengeHandler: ChallengeHandler,
|
||||
sslDir: string
|
||||
}
|
||||
|
||||
export class Letsencrypt {
|
||||
leEnv: TLeEnv
|
||||
challengeHandler: ChallengeHandler // this is the format we use
|
||||
sslDir: string
|
||||
|
||||
private _leInstance: any
|
||||
private _leServerUrl: string
|
||||
|
||||
constructor(optionsArg: ILetsencryptConstructorOptions) {
|
||||
// determine leEnv
|
||||
this.leEnv = optionsArg.leEnv
|
||||
this.challengeHandler = optionsArg.challengeHandler
|
||||
this.sslDir = optionsArg.sslDir
|
||||
|
||||
// set letsencrypt environment
|
||||
if (this.leEnv === 'production') {
|
||||
this._leServerUrl = letsencrypt.productionServerUrl
|
||||
} else if (this.leEnv === 'staging') {
|
||||
this._leServerUrl = letsencrypt.stagingServerUrl
|
||||
}
|
||||
|
||||
// create leInstance
|
||||
this._leInstance = letsencrypt.create({
|
||||
server: this._leServerUrl,
|
||||
challenges: { 'dns-01': this._leChallengeHandler() },
|
||||
challengeType: 'dns-01',
|
||||
configDir: paths.leConfigDir,
|
||||
privkeyPath: ':configDir/live/:hostname/privkey.pem', //
|
||||
fullchainPath: ':configDir/live/:hostname/fullchain.pem', // Note: both that :config and :hostname
|
||||
certPath: ':config/live/:hostname/cert.pem', // will be templated as expected
|
||||
chainPath: ':config/live/:hostname/chain.pem',
|
||||
agreeToTerms: this._leAgree,
|
||||
debug: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* register a domain
|
||||
*/
|
||||
registerDomain(domainNameArg: string) {
|
||||
plugins.beautylog.log(`trying to register domain ${domainNameArg}`)
|
||||
let done = q.defer()
|
||||
console.log('test')
|
||||
this._leInstance.register({
|
||||
domains: [domainNameArg],
|
||||
email: 'domains@lossless.org',
|
||||
agreeTos: true,
|
||||
rsaKeySize: 2048,
|
||||
challengeType: 'dns-01'
|
||||
}).then(
|
||||
(results) => {
|
||||
plugins.beautylog.success(`Got certificates for ${domainNameArg}`)
|
||||
this._leCopyToDestination(domainNameArg).then(done.resolve)
|
||||
},
|
||||
(err) => {
|
||||
console.error('[Error]: node-letsencrypt/examples/standalone')
|
||||
console.error(err.stack)
|
||||
done.resolve()
|
||||
}
|
||||
).catch(err => { console.log(err) })
|
||||
return done.promise
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// Translate for official letsencrypt stuff
|
||||
// --------------------------------------------
|
||||
|
||||
private _leCopyToDestination(domainNameArg) {
|
||||
let done = q.defer()
|
||||
return done.promise
|
||||
}
|
||||
|
||||
/**
|
||||
* translates to the format expected by letsencrypt node implementation
|
||||
*/
|
||||
private _leChallengeHandler() {
|
||||
return {
|
||||
getOptions: (...args) => {
|
||||
return {
|
||||
debug: true
|
||||
}
|
||||
},
|
||||
set: (args, domain, challenge, keyAuthorization, cb) => {
|
||||
let keyAuthDigest = require('crypto')
|
||||
.createHash('sha256').update(keyAuthorization || '')
|
||||
.digest('base64')
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/g, '')
|
||||
this.challengeHandler.setChallenge(domain, keyAuthDigest)
|
||||
.then(() => {
|
||||
cb()
|
||||
})
|
||||
},
|
||||
get: (defaults, domain, challenge, cb) => {
|
||||
console.log(defaults)
|
||||
console.log(domain)
|
||||
console.log(challenge)
|
||||
cb()
|
||||
},
|
||||
remove: (args, domain, challenge, cb) => {
|
||||
this.challengeHandler.cleanChallenge(domain)
|
||||
.then(() => {
|
||||
cb()
|
||||
})
|
||||
},
|
||||
loopback: (args, domain, challenge, cb) => {
|
||||
console.log(args)
|
||||
console.log(domain)
|
||||
console.log(challenge)
|
||||
cb()
|
||||
},
|
||||
test: (defaults, domain, challenge, cb) => {
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _leAgree(opts, agreeCb) {
|
||||
// opts = { email, domains, tosUrl }
|
||||
agreeCb(null, opts.tosUrl);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
|
||||
let smartcli = new plugins.smartcli.Smartcli();
|
||||
|
||||
let config = plugins.smartfile.fs.toObjectSync(paths.config);
|
||||
let cflare = new plugins.cflare.CflareAccount();
|
||||
cflare.auth({
|
||||
email: config.cfEmail,
|
||||
key: config.cfKey
|
||||
});
|
||||
|
||||
let setChallenge = (domainNameArg: string, challengeArg: string) => {
|
||||
let done = plugins.q.defer();
|
||||
plugins.beautylog.log("setting challenge for " + domainNameArg);
|
||||
cflare.createRecord(prefixName(domainNameArg), "TXT", challengeArg).then(() => {
|
||||
plugins.beautylog.ok("Challenge has been set!");
|
||||
plugins.beautylog.info("We need to cool down to let DNS propagate to edge locations!");
|
||||
cooldown().then(() => {
|
||||
done.resolve();
|
||||
});
|
||||
});
|
||||
return done.promise;
|
||||
}
|
||||
|
||||
let cleanChallenge = (domainNameArg) => {
|
||||
let done = plugins.q.defer();
|
||||
plugins.beautylog.log("cleaning challenge for " + domainNameArg);
|
||||
cflare.removeRecord(prefixName(domainNameArg), "TXT");
|
||||
cooldown().then(() => {
|
||||
done.resolve();
|
||||
});
|
||||
return done.promise;
|
||||
}
|
||||
|
||||
let cooldown = () => {
|
||||
let done = plugins.q.defer();
|
||||
let cooldowntime = 60000;
|
||||
let passedTime = 0;
|
||||
plugins.beautylog.log("Cooling down! " + (cooldowntime/1000).toString() + " seconds left");
|
||||
let coolDownCounter = () => {
|
||||
setTimeout(() => {
|
||||
if(cooldowntime <= passedTime){
|
||||
plugins.beautylog.ok("Cooled down!");
|
||||
done.resolve();
|
||||
} else {
|
||||
passedTime = passedTime + 5000;
|
||||
plugins.beautylog.log("Cooling down! " + ((cooldowntime - passedTime)/1000).toString() + " seconds left");
|
||||
coolDownCounter();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
coolDownCounter();
|
||||
return done.promise;
|
||||
}
|
||||
|
||||
let prefixName = (domainNameArg: string): string => {
|
||||
return "_acme-challenge." + domainNameArg;
|
||||
}
|
||||
|
||||
smartcli.addCommand({
|
||||
commandName: "deploy_challenge"
|
||||
}).then((argv) => {
|
||||
setChallenge(argv._[1], argv._[3]);
|
||||
});
|
||||
|
||||
smartcli.addCommand({
|
||||
commandName: "clean_challenge"
|
||||
}).then((argv) => {
|
||||
cleanChallenge(argv._[1]);
|
||||
});
|
||||
|
||||
smartcli.startParse();
|
@ -1,14 +1,9 @@
|
||||
import * as plugins from "./cert.plugins";
|
||||
|
||||
//dirs
|
||||
export let certDir = plugins.path.join(__dirname,"assets/certs");
|
||||
export let defaultSslDir = plugins.path.join(__dirname,"assets/defaultSslDir");
|
||||
export let assetDir = plugins.path.join(__dirname,"assets/");
|
||||
export let accountsDir = plugins.path.join(__dirname,"assets/accounts/");
|
||||
|
||||
// files
|
||||
export let certHook = plugins.path.join(__dirname,"cert.hook.js");
|
||||
export let config = plugins.path.join(__dirname,"assets/config.json");
|
||||
export let leShConfig = plugins.path.join(__dirname,"assets/leshconfig.json");
|
||||
export let letsencryptSh = plugins.path.join(__dirname,"assets/letsencrypt.sh");
|
||||
import * as plugins from './cert.plugins'
|
||||
|
||||
// dirs
|
||||
export let projectDir = plugins.path.join(__dirname,'../')
|
||||
export let assetDir = plugins.path.join(projectDir,'assets')
|
||||
export let defaultSslDir = plugins.path.join(assetDir,'defaultSslDir')
|
||||
export let leConfigDir = plugins.path.join(assetDir,'letsencrypt')
|
||||
plugins.smartfile.fs.ensureDirSync(leConfigDir)
|
||||
plugins.smartfile.fs.ensureDirSync(defaultSslDir)
|
||||
|
@ -2,7 +2,6 @@ import 'typings-global'
|
||||
import * as beautylog from 'beautylog'
|
||||
import * as cflare from 'cflare'
|
||||
let fs = require('fs-extra')
|
||||
let letsencrypt = require('letsencrypt')
|
||||
import * as lik from 'lik'
|
||||
import * as path from 'path'
|
||||
import * as q from 'q'
|
||||
@ -16,7 +15,6 @@ export {
|
||||
beautylog,
|
||||
cflare,
|
||||
fs,
|
||||
letsencrypt,
|
||||
lik,
|
||||
path,
|
||||
q,
|
||||
|
@ -1,4 +1 @@
|
||||
import * as plugins from "./cert.plugins";
|
||||
import * as paths from "./cert.paths";
|
||||
|
||||
export * from "./cert.classes.cert";
|
||||
export * from './cert.classes.cert'
|
||||
|
Reference in New Issue
Block a user