cleanup: rework constructor and README (#11)

This commit is contained in:
AJ ONeal 2019-05-10 15:54:55 +00:00 committed by Gitea
parent b4c490f8a0
commit 096a94f25b
4 changed files with 206 additions and 101 deletions

221
README.md
View File

@ -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);
``` ```

View File

@ -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);
}; };

View File

@ -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;

View File

@ -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;