-
-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes #495
- Loading branch information
Showing
5 changed files
with
209 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
'use strict'; | ||
const fs = require('fs-extra'); | ||
const os = require('os'); | ||
const url = require('url'); | ||
const path = require('path'); | ||
|
||
const cli = require('../../lib'); | ||
|
||
function migrateSSL(ctx, migrateTask) { | ||
const replace = require('replace-in-file'); | ||
const acme = require('./acme'); | ||
|
||
const parsedUrl = url.parse(ctx.instance.config.get('url')); | ||
const confFile = path.join(ctx.instance.dir, 'system', 'files', `${parsedUrl.hostname}-ssl.conf`); | ||
const rootPath = path.resolve(ctx.instance.dir, 'system', 'nginx-root'); | ||
|
||
if (!fs.existsSync(confFile)) { | ||
return migrateTask.skip('SSL config has not been set up for this domain'); | ||
} | ||
|
||
const originalAcmePath = path.join(os.homedir(), '.acme.sh'); | ||
|
||
// 1. parse ~/.acme.sh/account.conf to get the email | ||
const accountConf = fs.readFileSync(path.join(originalAcmePath, 'account.conf'), {encoding: 'utf8'}); | ||
const parsed = accountConf.match(/ACCOUNT_EMAIL='(.*)'\n/); | ||
|
||
if (!parsed) { | ||
throw new cli.errors.SystemError('Unable to parse letsencrypt account email'); | ||
} | ||
|
||
return this.ui.listr([{ | ||
// 2. install acme.sh in /etc/letsencrypt if that hasn't been done already | ||
title: 'Installing acme.sh in new location', | ||
task: (ctx, task) => acme.install(this.ui, task) | ||
}, { | ||
// 3. run install cert for new acme.sh instance | ||
title: 'Regenerating SSL certificate in new location', | ||
task: () => acme.generate(this.ui, parsedUrl.hostname, rootPath, parsed[1], false) | ||
}, { | ||
// 4. Update cert locations in nginx-ssl.conf | ||
title: 'Updating nginx config', | ||
task: () => { | ||
const acmeFolder = path.join('/etc/letsencrypt', parsedUrl.hostname); | ||
|
||
return replace({ | ||
files: confFile, | ||
from: [ | ||
/ssl_certificate .*/, | ||
/ssl_certificate_key .*/ | ||
], | ||
to: [ | ||
`ssl_certificate ${path.join(acmeFolder, 'fullchain.cer')};`, | ||
`ssl_certificate_key ${path.join(acmeFolder, `${parsedUrl.hostname}.key`)};` | ||
] | ||
}); | ||
} | ||
}, { | ||
title: 'Restarting Nginx', | ||
task: () => this.restartNginx() | ||
}, { | ||
// 5. run acme.sh --remove -d domain in old acme.sh directory to remove the old cert from renewal | ||
title: 'Disabling renewal for old certificate', | ||
task: () => acme.remove(parsedUrl.hostname, this.ui, originalAcmePath) | ||
}], false); | ||
} | ||
|
||
module.exports = { | ||
migrateSSL: migrateSSL | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
'use strict'; | ||
|
||
const expect = require('chai').expect; | ||
const sinon = require('sinon'); | ||
const proxyquire = require('proxyquire').noCallThru(); | ||
|
||
const modulePath = '../migrations'; | ||
|
||
const cli = require('../../../lib'); | ||
|
||
const context = { | ||
instance: { | ||
dir: '/var/www/ghost', | ||
config: { | ||
get: () => 'https://ghost.org' | ||
} | ||
} | ||
}; | ||
|
||
describe('Unit: Extensions > Nginx > Migrations', function () { | ||
describe('migrateSSL', function () { | ||
it('skips if ssl is not set up', function () { | ||
const existsStub = sinon.stub().returns(false); | ||
const skipStub = sinon.stub(); | ||
|
||
const migrate = proxyquire(modulePath, { | ||
'fs-extra': {existsSync: existsStub} | ||
}); | ||
|
||
migrate.migrateSSL(context, {skip: skipStub}); | ||
|
||
expect(existsStub.calledOnce).to.be.true; | ||
expect(existsStub.calledWithExactly('/var/www/ghost/system/files/ghost.org-ssl.conf')).to.be.true; | ||
expect(skipStub.calledOnce).to.be.true; | ||
}); | ||
|
||
it('throws an error if it can\'t parse the letsencrypt account email', function () { | ||
const existsStub = sinon.stub().returns(true); | ||
const rfsStub = sinon.stub().returns(''); | ||
|
||
const migrate = proxyquire(modulePath, { | ||
'fs-extra': {existsSync: existsStub, readFileSync: rfsStub}, | ||
os: {homedir: () => '/home/ghost'} | ||
}); | ||
|
||
try { | ||
migrate.migrateSSL(context); | ||
expect(false, 'error should have been thrown').to.be.true; | ||
} catch (e) { | ||
expect(e).to.be.an.instanceof(cli.errors.SystemError); | ||
expect(e.message).to.equal('Unable to parse letsencrypt account email'); | ||
|
||
expect(rfsStub.calledWithExactly('/home/ghost/.acme.sh/account.conf')); | ||
} | ||
}); | ||
|
||
it('runs tasks correctly', function () { | ||
const existsStub = sinon.stub().returns(true); | ||
const rfsStub = sinon.stub().returns('ACCOUNT_EMAIL=\'[email protected]\'\n'); | ||
const restartStub = sinon.stub().resolves(); | ||
const replaceStub = sinon.stub().resolves(); | ||
|
||
const acme = { | ||
install: sinon.stub().resolves(), | ||
generate: sinon.stub().resolves(), | ||
remove: sinon.stub().resolves() | ||
}; | ||
const ui = { | ||
listr: sinon.stub() | ||
}; | ||
|
||
const migrate = proxyquire(modulePath, { | ||
'fs-extra': {existsSync: existsStub, readFileSync: rfsStub}, | ||
'replace-in-file': replaceStub, | ||
'./acme': acme, | ||
os: {homedir: () => '/home/ghost'} | ||
}); | ||
|
||
const fn = migrate.migrateSSL.bind({ui: ui, restartNginx: restartStub}); | ||
|
||
fn(context); | ||
|
||
expect(existsStub.calledOnce).to.be.true; | ||
expect(rfsStub.calledOnce).to.be.true; | ||
expect(ui.listr.calledOnce).to.be.true; | ||
|
||
const tasks = ui.listr.getCall(0).args[0]; | ||
expect(tasks).to.have.length(5); | ||
|
||
return tasks[0].task(null).then(() => { | ||
expect(acme.install.calledOnce).to.be.true; | ||
|
||
return tasks[1].task(); | ||
}).then(() => { | ||
expect(acme.generate.calledOnce).to.be.true; | ||
expect(acme.generate.calledWithExactly( | ||
ui, | ||
'ghost.org', | ||
'/var/www/ghost/system/nginx-root', | ||
'[email protected]', | ||
false | ||
)).to.be.true; | ||
|
||
return tasks[2].task(); | ||
}).then(() => { | ||
expect(replaceStub.calledOnce).to.be.true; | ||
|
||
return tasks[3].task(); | ||
}).then(() => { | ||
expect(restartStub.calledOnce).to.be.true; | ||
|
||
return tasks[4].task(); | ||
}).then(() => { | ||
expect(acme.remove.calledOnce).to.be.true; | ||
expect(acme.remove.calledWithExactly('ghost.org', ui, '/home/ghost/.acme.sh')); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3131,6 +3131,14 @@ repeating@^2.0.0: | |
dependencies: | ||
is-finite "^1.0.0" | ||
|
||
[email protected]: | ||
version "2.6.4" | ||
resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-2.6.4.tgz#a80e25c5c0e0efe9d04afe01a4a57ff98e8b6461" | ||
dependencies: | ||
chalk "^2.1.0" | ||
glob "^7.1.2" | ||
yargs "^8.0.2" | ||
|
||
request@^2.79.0, request@^2.83.0: | ||
version "2.83.0" | ||
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" | ||
|
@@ -3962,7 +3970,7 @@ yargs@^3.19.0: | |
window-size "^0.1.4" | ||
y18n "^3.2.0" | ||
|
||
yargs@^8.0.1: | ||
yargs@^8.0.1, yargs@^8.0.2: | ||
version "8.0.2" | ||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" | ||
dependencies: | ||
|