cleanup: rework constructor and README #11
221
README.md
221
README.md
|
@ -2,70 +2,197 @@
|
||||||
|
|
||||||
> A database-driven Greenlock storage plugin with wildcard support.
|
> 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
|
## Usage
|
||||||
|
|
||||||
To use, provide this Greenlock storage plugin as the `store` attribute when you
|
To use, provide this Greenlock storage plugin as the `store` option when you
|
||||||
invoke `create`.
|
invoke `create`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
greenlock.create({
|
Greenlock.create({
|
||||||
store: require('le-store-sequelize')
|
store: require('greenlock-store-sequelize')
|
||||||
|
...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Defaults
|
<details><summary>SQLite3 (default)</summary>
|
||||||
|
|
||||||
No configuration is required. By default, you'll get a baked-in Sequelize
|
SQLite3 is the default database, however, since it has a large number of dependencies
|
||||||
database running [sqlite3](https://www.npmjs.com/package/sqlite3).
|
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
|
The default db file will be written wherever Greenlock's `configDir` is set to,
|
||||||
defaults. If `config.dbOptions` is provided, you can configure the database
|
which is probably `~/acme` or `~/letsencrypt`.
|
||||||
connection per the Sequelize documentation.
|
|
||||||
|
|
||||||
If a dialect other than sqlite3 is used, dependencies will need to be
|
```bash
|
||||||
installed.
|
~/acme/db.sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
```javascript
|
If you wish to set special options you may do so by passing a pre-configured `Sequelize` instance:
|
||||||
greenlock.create({
|
|
||||||
store: require('le-store-sequelize')({
|
```js
|
||||||
dbConfig: {
|
var Sequelize = require('sequelize');
|
||||||
username: 'mysqluser',
|
var db = new Sequelize({ dialect: 'sqlite', storage: '/Users/me/acme/db.sqlite3' });
|
||||||
password: 'mysqlpassword',
|
|
||||||
database: 'mysqldatabase,
|
Greenlock.create({
|
||||||
host: '127.0.0.1',
|
store: require('greenlock-store-sequelize').create({ db: db })
|
||||||
dialect: 'mysql'
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>PostgreSQL, SQL Server, and lesser databases...</summary>
|
||||||
|
|
||||||
|
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.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>Database URLs / Connection Strings</summary>
|
||||||
|
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.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>Environment variables (AWS, Docker, Heroku, Akkeris)</summary>
|
||||||
|
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 })
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details><summary>Table Prefixes</summary>
|
||||||
|
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 })
|
||||||
|
...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
The database can also be configured using an env variable.
|
## Table Structure
|
||||||
|
|
||||||
```javascript
|
This is the table structure that's created.
|
||||||
greenlock.create({
|
|
||||||
store: require('greenlock-store-sequelize')({
|
```sql
|
||||||
dbConfig: {
|
CREATE TABLE `Keypairs` (
|
||||||
use_env_variable: 'DB_URL'
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
}
|
`xid` VARCHAR(255) UNIQUE,
|
||||||
})
|
`content` TEXT,
|
||||||
});
|
`createdAt` DATETIME NOT NULL,
|
||||||
```
|
`updatedAt` DATETIME NOT NULL);
|
||||||
|
|
||||||
### Custom Database Object
|
CREATE TABLE `Domains` (
|
||||||
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
If you already have a Sequelize object, you can pass that in as `config.db`,
|
`subject` VARCHAR(255) UNIQUE,
|
||||||
circumventing the baked-in database entirely.
|
`altnames` TEXT,
|
||||||
|
`createdAt` DATETIME NOT NULL,
|
||||||
```javascript
|
`updatedAt` DATETIME NOT NULL);
|
||||||
var db = require('./db'); // your db
|
|
||||||
|
CREATE TABLE `Certificates` (
|
||||||
greenlock.create({
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
store: require('le-store-sequelize')({
|
`subject` VARCHAR(255) UNIQUE,
|
||||||
db
|
`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);
|
||||||
```
|
```
|
||||||
|
|
46
db/index.js
46
db/index.js
|
@ -1,49 +1,25 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var basename = path.basename(__filename);
|
|
||||||
var Sequelize = require('sequelize');
|
|
||||||
var sync = require('../sync.js');
|
var sync = require('../sync.js');
|
||||||
|
|
||||||
module.exports = function (config) {
|
module.exports = function (sequelize) {
|
||||||
var db = {};
|
var db = {};
|
||||||
|
|
||||||
db.Sequelize = Sequelize;
|
[ 'keypair.js'
|
||||||
|
, 'domain.js'
|
||||||
if (!config) {
|
, 'certificate.js'
|
||||||
config = {
|
, 'chain.js'
|
||||||
dialect: "sqlite",
|
].forEach(function (file) {
|
||||||
storage: "./db.sqlite"
|
var model = sequelize['import'](path.join(__dirname, file));
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
db[model.name] = model;
|
db[model.name] = model;
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(db).forEach(function (modelName) {
|
Object.keys(db).forEach(function (modelName) {
|
||||||
if (db[modelName].associate) {
|
db[modelName].associate(db);
|
||||||
db[modelName].associate(db);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var synced = false;
|
return sync(db).then(function () {
|
||||||
if (!synced) {
|
return db;
|
||||||
return sync(db).then(function () {
|
});
|
||||||
synced = true;
|
|
||||||
return db;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.resolve(db);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,21 +6,30 @@ module.exports.create = function (config={}) {
|
||||||
accounts: {},
|
accounts: {},
|
||||||
certificates: {}
|
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
|
// The user can provide their own db, but if they don't, we'll use the
|
||||||
// baked-in db.
|
// baked-in db.
|
||||||
if (!config.db) {
|
if (!sequelize) {
|
||||||
// If the user provides options for the baked-in db, we'll use them. If
|
// 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.
|
// they don't, we'll use the baked-in db with its defaults.
|
||||||
config.db = require('./db')(config.dbConfig || null);
|
Sequelize = require('sequelize');
|
||||||
}
|
if (config.storeDatabaseUrl) {
|
||||||
else {
|
sequelize = new Sequelize(config.storeDatabaseUrl);
|
||||||
// This library expects config.db to resolve the db object. We'll ensure
|
} else {
|
||||||
// that this is the case with the provided db, as it was with the baked-in
|
sequelize = new Sequelize({ dialect: 'sqlite', storage: confDir + '/db.sqlite3' });
|
||||||
// db.
|
}
|
||||||
config.db = Promise.resolve(config.db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
store.certificates.check = function (opts) {
|
||||||
return config.db.then(function (db) {
|
return config.db.then(function (db) {
|
||||||
return db.Certificate.findOne({
|
return db.Certificate.findOne({
|
||||||
|
@ -49,7 +58,7 @@ module.exports.create = function (config={}) {
|
||||||
err.code = 'ENOENT';
|
err.code = 'ENOENT';
|
||||||
throw err;
|
throw err;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (err.code == 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -77,7 +86,7 @@ module.exports.create = function (config={}) {
|
||||||
err.code = 'ENOENT';
|
err.code = 'ENOENT';
|
||||||
throw err;
|
throw err;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (err.code == 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
@ -119,7 +128,7 @@ module.exports.create = function (config={}) {
|
||||||
err.code = 'ENOENT';
|
err.code = 'ENOENT';
|
||||||
throw err;
|
throw err;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (err.code == 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
|
9
sync.js
9
sync.js
|
@ -6,17 +6,10 @@ function sync(db) {
|
||||||
function next() {
|
function next() {
|
||||||
var modelName = keys.shift();
|
var modelName = keys.shift();
|
||||||
if (!modelName) { return; }
|
if (!modelName) { return; }
|
||||||
if (isModel(modelName)) {
|
return db[modelName].sync().then(next);
|
||||||
return db[modelName].sync().then(next);
|
|
||||||
}
|
|
||||||
return next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve().then(next);
|
return Promise.resolve().then(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isModel(key) {
|
|
||||||
return !(['sequelize','Sequelize'].includes(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = sync;
|
module.exports = sync;
|
||||||
|
|
Loading…
Reference in New Issue