diff --git a/README.md b/README.md index 35b9411..b12b3c4 100644 --- a/README.md +++ b/README.md @@ -2,70 +2,197 @@ > A database-driven Greenlock storage plugin with wildcard support. +## Features + +* Many [Supported SQL Databases](http://docs.sequelizejs.com/manual/getting-started.html) + * [x] PostgreSQL (**best**) + * [x] SQLite3 (**easiest**) + * [x] Microsoft SQL Server (mssql) + * [x] MySQL, MariaDB +* Works on all platforms + * [x] Mac, Linux, VPS + * [x] AWS, Heroku, Akkeris, Docker + * [x] Windows + ## Usage -To use, provide this Greenlock storage plugin as the `store` attribute when you -invoke `create`. +To use, provide this Greenlock storage plugin as the `store` option when you +invoke `create`: ```js -greenlock.create({ - store: require('le-store-sequelize') +Greenlock.create({ + store: require('greenlock-store-sequelize') + ... }); ``` ## Configuration -### Defaults +
SQLite3 (default) -No configuration is required. By default, you'll get a baked-in Sequelize -database running [sqlite3](https://www.npmjs.com/package/sqlite3). +SQLite3 is the default database, however, since it has a large number of dependencies +and may require a native module to be built, you must explicitly install +[sqlite3](https://www.npmjs.com/package/sqlite3): -### Database Connection +```bash +npm install --save sqlite3 +``` -Without `config.dbOptions`, the baked-in sequelize object uses sqlite3 with -defaults. If `config.dbOptions` is provided, you can configure the database -connection per the Sequelize documentation. +The default db file will be written wherever Greenlock's `configDir` is set to, +which is probably `~/acme` or `~/letsencrypt`. -If a dialect other than sqlite3 is used, dependencies will need to be -installed. +```bash +~/acme/db.sqlite3 +``` -```javascript -greenlock.create({ - store: require('le-store-sequelize')({ - dbConfig: { - username: 'mysqluser', - password: 'mysqlpassword', - database: 'mysqldatabase, - host: '127.0.0.1', - dialect: 'mysql' +If you wish to set special options you may do so by passing a pre-configured `Sequelize` instance: + +```js +var Sequelize = require('sequelize'); +var db = new Sequelize({ dialect: 'sqlite', storage: '/Users/me/acme/db.sqlite3' }); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` +
+ +
PostgreSQL, SQL Server, and lesser databases... + +The general format of a DATABASE_URL is something like this: + +> `schema://user:pass@server:port/service?option=foo` + +For example: + +> `postgres://aj:secret123@127.0.0.1:5432/greenlock` + +For each database the exact format may be slightly different: + +* `postgres://user:pass@hostname:port/database?option=foo` +* `sqlserver://user:pass@datasource:port/instance/catalog?database=dbname` (mssql) +* `mysql://user:pass@hostname:port/database?option=foo` +* `mariadb://user:pass@hostname:port/database?option=foo` + +There's also a way to specify objects instead of using the standard connection strings. + +See the next section for more information. +
+ +
Database URLs / Connection Strings +You may use database URLs (also known as 'connection strings') to initialize sequelize: + +```js +var dbUrl = 'postgres://user:pass@hostname:port/database'; + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ storeDatabaseUrl: dbUrl }) + ... +}); +``` + +If you need to use **custom options**, just instantiate sequelize directly: + +```js +var Sequelize = require('sequelize'); +var db = new Sequelize('postgres://user:pass@hostname:port/database'); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` + +See the [Sequelize Getting Started](http://docs.sequelizejs.com/manual/getting-started.html) docs for more info +on database options for sequelize. +
+ +
Environment variables (AWS, Docker, Heroku, Akkeris) +If your database connection string is in an environment variable, +you would use the usual standard for your platform. + +For example, if you're using Heroku, Akkeris, or Docker you're +database connection string is probably `DATABASE_URL`, so you'd do something like this: + +```js +var Sequelize = require('sequelize'); +var databaseUrl = process.env['DATABASE_URL']; +var db = new Sequelize(databaseUrl); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... +}); +``` +
+ +
Table Prefixes +The default table names are as follows: + +* Keypair +* Domain +* Certificate +* Chain + +If you'd like to add a table name prefix or define a specific schema within the database (PostgreSQL, SQL Server), +you can do so like this: + +```js +var Sequelize = require('sequelize'); +var databaseUrl = process.env['DATABASE_URL']; +var db = new Sequelize(databaseUrl, { + hooks: { + beforeDefine: function (columns, model) { + model.tableName = 'MyPrefix' + model.name.plural; + //model.schema = 'public'; + } } - }) +}); + +Greenlock.create({ + store: require('greenlock-store-sequelize').create({ db: db }) + ... }); ``` +
-The database can also be configured using an env variable. +## Table Structure -```javascript -greenlock.create({ - store: require('greenlock-store-sequelize')({ - dbConfig: { - use_env_variable: 'DB_URL' - } - }) -}); -``` - -### Custom Database Object - -If you already have a Sequelize object, you can pass that in as `config.db`, -circumventing the baked-in database entirely. - -```javascript -var db = require('./db'); // your db - -greenlock.create({ - store: require('le-store-sequelize')({ - db - }) -}); +This is the table structure that's created. + +```sql +CREATE TABLE `Keypairs` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `xid` VARCHAR(255) UNIQUE, + `content` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Domains` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `subject` VARCHAR(255) UNIQUE, + `altnames` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Certificates` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `subject` VARCHAR(255) UNIQUE, + `cert` TEXT, + `issuedAt` DATETIME, + `expiresAt` DATETIME, + `altnames` TEXT, + `chain` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL); + +CREATE TABLE `Chains` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `xid` VARCHAR(255) UNIQUE, + `content` TEXT, + `createdAt` DATETIME NOT NULL, + `updatedAt` DATETIME NOT NULL, + `CertificateId` INTEGER REFERENCES + `Certificates` (`id`) ON DELETE SET NULL ON UPDATE CASCADE); ``` diff --git a/db/index.js b/db/index.js index 89495db..a804e0d 100644 --- a/db/index.js +++ b/db/index.js @@ -1,49 +1,25 @@ 'use strict'; -var fs = require('fs'); var path = require('path'); -var basename = path.basename(__filename); -var Sequelize = require('sequelize'); var sync = require('../sync.js'); -module.exports = function (config) { +module.exports = function (sequelize) { var db = {}; - db.Sequelize = Sequelize; - - if (!config) { - config = { - dialect: "sqlite", - storage: "./db.sqlite" - }; - } - - if (config.use_env_variable) { - db.sequelize = new db.Sequelize(process.env[config.use_env_variable], config); - } - else { - db.sequelize = new db.Sequelize(config.database, config.username, config.password, config); - } - - fs.readdirSync(__dirname).filter(function (file) { - return ('.' !== file[0]) && (file !== basename) && (file.slice(-3) === '.js'); - }).forEach(function (file) { - var model = db.sequelize['import'](path.join(__dirname, file)); + [ 'keypair.js' + , 'domain.js' + , 'certificate.js' + , 'chain.js' + ].forEach(function (file) { + var model = sequelize['import'](path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(function (modelName) { - if (db[modelName].associate) { - db[modelName].associate(db); - } + db[modelName].associate(db); }); - var synced = false; - if (!synced) { - return sync(db).then(function () { - synced = true; - return db; - }); - } - return Promise.resolve(db); + return sync(db).then(function () { + return db; + }); }; diff --git a/greenlock-store-sequelize.js b/greenlock-store-sequelize.js index 2edd794..5631e7d 100644 --- a/greenlock-store-sequelize.js +++ b/greenlock-store-sequelize.js @@ -6,21 +6,30 @@ module.exports.create = function (config={}) { accounts: {}, certificates: {} }; + var Sequelize; + var sequelize = config.db; + var confDir = config.configDir || (require('os').homedir() + '/acme'); // The user can provide their own db, but if they don't, we'll use the // baked-in db. - if (!config.db) { + if (!sequelize) { // If the user provides options for the baked-in db, we'll use them. If // they don't, we'll use the baked-in db with its defaults. - config.db = require('./db')(config.dbConfig || null); - } - else { - // This library expects config.db to resolve the db object. We'll ensure - // that this is the case with the provided db, as it was with the baked-in - // db. - config.db = Promise.resolve(config.db); + Sequelize = require('sequelize'); + if (config.storeDatabaseUrl) { + sequelize = new Sequelize(config.storeDatabaseUrl); + } else { + sequelize = new Sequelize({ dialect: 'sqlite', storage: confDir + '/db.sqlite3' }); + } } + // This library expects config.db to resolve the db object. We'll ensure + // that this is the case with the provided db, as it was with the baked-in + // db. + config.db = Promise.resolve(sequelize).then(function (sequelize) { + return require('./db')(sequelize); + }); + store.certificates.check = function (opts) { return config.db.then(function (db) { return db.Certificate.findOne({ @@ -49,7 +58,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; @@ -77,7 +86,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; @@ -119,7 +128,7 @@ module.exports.create = function (config={}) { err.code = 'ENOENT'; throw err; }).catch(function (err) { - if (err.code == 'ENOENT') { + if (err.code === 'ENOENT') { return null; } throw err; diff --git a/sync.js b/sync.js index 7544b20..7155806 100644 --- a/sync.js +++ b/sync.js @@ -6,17 +6,10 @@ function sync(db) { function next() { var modelName = keys.shift(); if (!modelName) { return; } - if (isModel(modelName)) { - return db[modelName].sync().then(next); - } - return next(); + return db[modelName].sync().then(next); } return Promise.resolve().then(next); } -function isModel(key) { - return !(['sequelize','Sequelize'].includes(key)); -} - module.exports = sync;