tabs -> spaces
This commit is contained in:
parent
8c8d9251cd
commit
fd1723782b
54
README.md
54
README.md
|
@ -50,12 +50,12 @@ TODO
|
|||
// Creates an instance of greenlock with certain default values
|
||||
|
||||
var gl = Greenlock.create({
|
||||
// Staging for testing environments
|
||||
staging: true,
|
||||
// Staging for testing environments
|
||||
staging: true,
|
||||
|
||||
// This should be the contact who receives critical bug and security notifications
|
||||
// Optionally, you may receive other (very few) updates, such as important new features
|
||||
maintainerEmail: 'jon@example.com'
|
||||
// This should be the contact who receives critical bug and security notifications
|
||||
// Optionally, you may receive other (very few) updates, such as important new features
|
||||
maintainerEmail: 'jon@example.com'
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -73,10 +73,10 @@ var gl = Greenlock.create({
|
|||
|
||||
```js
|
||||
greenlock.manager.defaults({
|
||||
// The "Let's Encrypt Subscriber" (often the same as the maintainer)
|
||||
// NOT the end customer (except where that is also the maintainer)
|
||||
subscriberEmail: 'jon@example.com',
|
||||
agreeToTerms: true
|
||||
// The "Let's Encrypt Subscriber" (often the same as the maintainer)
|
||||
// NOT the end customer (except where that is also the maintainer)
|
||||
subscriberEmail: 'jon@example.com',
|
||||
agreeToTerms: true
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -102,8 +102,8 @@ greenlock.manager.defaults({
|
|||
|
||||
```js
|
||||
gl.add({
|
||||
subject: 'example.com',
|
||||
altnames: ['example.com', 'www.example.com', 'exampleapi.com']
|
||||
subject: 'example.com',
|
||||
altnames: ['example.com', 'www.example.com', 'exampleapi.com']
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -119,15 +119,15 @@ gl.add({
|
|||
|
||||
```js
|
||||
return greenlock.get({ servername }).then(function(site) {
|
||||
if (!site) {
|
||||
console.log(servername + ' was not found in any site config');
|
||||
return;
|
||||
}
|
||||
if (!site) {
|
||||
console.log(servername + ' was not found in any site config');
|
||||
return;
|
||||
}
|
||||
|
||||
var privkey = site.pems.privkey;
|
||||
var fullchain = site.pems.cert + '\n' + site.pems.chain + '\n';
|
||||
console.log(privkey);
|
||||
console.log(fullchain);
|
||||
var privkey = site.pems.privkey;
|
||||
var fullchain = site.pems.cert + '\n' + site.pems.chain + '\n';
|
||||
console.log(privkey);
|
||||
console.log(fullchain);
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -141,13 +141,13 @@ This will renew only domains that have reached their `renewAt` or are within the
|
|||
|
||||
```js
|
||||
return greenlock.renew({}).then(function(results) {
|
||||
results.forEach(function(site) {
|
||||
if (site.error) {
|
||||
console.error(site.subject, site.error);
|
||||
return;
|
||||
}
|
||||
console.log('Renewed certificate for', site.subject, site.altnames);
|
||||
});
|
||||
results.forEach(function(site) {
|
||||
if (site.error) {
|
||||
console.error(site.subject, site.error);
|
||||
return;
|
||||
}
|
||||
console.log('Renewed certificate for', site.subject, site.altnames);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -163,7 +163,7 @@ return greenlock.renew({}).then(function(results) {
|
|||
|
||||
```js
|
||||
greenlock.update({ subject, renewAt: 0 }).then(function() {
|
||||
return greenlock.renew({});
|
||||
return greenlock.renew({});
|
||||
});
|
||||
```
|
||||
|
||||
|
|
344
accounts.js
344
accounts.js
|
@ -7,213 +7,213 @@ var E = require('./errors.js');
|
|||
var pending = {};
|
||||
|
||||
A._getOrCreate = function(gnlck, mconf, db, acme, args) {
|
||||
var email = args.subscriberEmail || mconf.subscriberEmail;
|
||||
var email = args.subscriberEmail || mconf.subscriberEmail;
|
||||
|
||||
if (!email) {
|
||||
throw E.NO_SUBSCRIBER('get account', args.subject);
|
||||
}
|
||||
if (!email) {
|
||||
throw E.NO_SUBSCRIBER('get account', args.subject);
|
||||
}
|
||||
|
||||
// TODO send welcome message with benefit info
|
||||
return U._validMx(email)
|
||||
.catch(function() {
|
||||
throw E.NO_SUBSCRIBER('get account', args.subcriberEmail);
|
||||
})
|
||||
.then(function() {
|
||||
if (pending[email]) {
|
||||
return pending[email];
|
||||
}
|
||||
// TODO send welcome message with benefit info
|
||||
return U._validMx(email)
|
||||
.catch(function() {
|
||||
throw E.NO_SUBSCRIBER('get account', args.subcriberEmail);
|
||||
})
|
||||
.then(function() {
|
||||
if (pending[email]) {
|
||||
return pending[email];
|
||||
}
|
||||
|
||||
pending[email] = A._rawGetOrCreate(
|
||||
gnlck,
|
||||
mconf,
|
||||
db,
|
||||
acme,
|
||||
args,
|
||||
email
|
||||
)
|
||||
.catch(function(e) {
|
||||
delete pending[email];
|
||||
throw e;
|
||||
})
|
||||
.then(function(result) {
|
||||
delete pending[email];
|
||||
return result;
|
||||
});
|
||||
pending[email] = A._rawGetOrCreate(
|
||||
gnlck,
|
||||
mconf,
|
||||
db,
|
||||
acme,
|
||||
args,
|
||||
email
|
||||
)
|
||||
.catch(function(e) {
|
||||
delete pending[email];
|
||||
throw e;
|
||||
})
|
||||
.then(function(result) {
|
||||
delete pending[email];
|
||||
return result;
|
||||
});
|
||||
|
||||
return pending[email];
|
||||
});
|
||||
return pending[email];
|
||||
});
|
||||
};
|
||||
|
||||
// What we really need out of this is the private key and the ACME "key" id
|
||||
A._rawGetOrCreate = function(gnlck, mconf, db, acme, args, email) {
|
||||
var p;
|
||||
if (db.check) {
|
||||
p = A._checkStore(gnlck, mconf, db, acme, args, email);
|
||||
} else {
|
||||
p = Promise.resolve(null);
|
||||
}
|
||||
var p;
|
||||
if (db.check) {
|
||||
p = A._checkStore(gnlck, mconf, db, acme, args, email);
|
||||
} else {
|
||||
p = Promise.resolve(null);
|
||||
}
|
||||
|
||||
return p.then(function(fullAccount) {
|
||||
if (!fullAccount) {
|
||||
return A._newAccount(gnlck, mconf, db, acme, args, email, null);
|
||||
}
|
||||
return p.then(function(fullAccount) {
|
||||
if (!fullAccount) {
|
||||
return A._newAccount(gnlck, mconf, db, acme, args, email, null);
|
||||
}
|
||||
|
||||
if (fullAccount.keypair && fullAccount.key && fullAccount.key.kid) {
|
||||
return fullAccount;
|
||||
}
|
||||
if (fullAccount.keypair && fullAccount.key && fullAccount.key.kid) {
|
||||
return fullAccount;
|
||||
}
|
||||
|
||||
return A._newAccount(gnlck, mconf, db, acme, args, email, fullAccount);
|
||||
});
|
||||
return A._newAccount(gnlck, mconf, db, acme, args, email, fullAccount);
|
||||
});
|
||||
};
|
||||
|
||||
A._newAccount = function(gnlck, mconf, db, acme, args, email, fullAccount) {
|
||||
var keyType = args.accountKeyType || mconf.accountKeyType;
|
||||
var query = {
|
||||
subject: args.subject,
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail,
|
||||
account: fullAccount || {},
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
};
|
||||
var keyType = args.accountKeyType || mconf.accountKeyType;
|
||||
var query = {
|
||||
subject: args.subject,
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail,
|
||||
account: fullAccount || {},
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
};
|
||||
|
||||
return U._getOrCreateKeypair(db, args.subject, query, keyType).then(
|
||||
function(kresult) {
|
||||
var keypair = kresult.keypair;
|
||||
var accReg = {
|
||||
subscriberEmail: email,
|
||||
agreeToTerms:
|
||||
args.agreeToTerms ||
|
||||
mconf.agreeToTerms ||
|
||||
gnlck._defaults.agreeToTerms,
|
||||
accountKey: keypair.privateKeyJwk || keypair.private,
|
||||
debug: args.debug
|
||||
};
|
||||
return acme.accounts.create(accReg).then(function(receipt) {
|
||||
var reg = {
|
||||
keypair: keypair,
|
||||
receipt: receipt,
|
||||
// shudder... not actually a KeyID... but so it is called anyway...
|
||||
kid:
|
||||
receipt &&
|
||||
receipt.key &&
|
||||
(receipt.key.kid || receipt.kid),
|
||||
email: args.email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail
|
||||
};
|
||||
return U._getOrCreateKeypair(db, args.subject, query, keyType).then(
|
||||
function(kresult) {
|
||||
var keypair = kresult.keypair;
|
||||
var accReg = {
|
||||
subscriberEmail: email,
|
||||
agreeToTerms:
|
||||
args.agreeToTerms ||
|
||||
mconf.agreeToTerms ||
|
||||
gnlck._defaults.agreeToTerms,
|
||||
accountKey: keypair.privateKeyJwk || keypair.private,
|
||||
debug: args.debug
|
||||
};
|
||||
return acme.accounts.create(accReg).then(function(receipt) {
|
||||
var reg = {
|
||||
keypair: keypair,
|
||||
receipt: receipt,
|
||||
// shudder... not actually a KeyID... but so it is called anyway...
|
||||
kid:
|
||||
receipt &&
|
||||
receipt.key &&
|
||||
(receipt.key.kid || receipt.kid),
|
||||
email: args.email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail
|
||||
};
|
||||
|
||||
var keyP;
|
||||
if (kresult.exists) {
|
||||
keyP = Promise.resolve();
|
||||
} else {
|
||||
query.keypair = keypair;
|
||||
query.receipt = receipt;
|
||||
/*
|
||||
var keyP;
|
||||
if (kresult.exists) {
|
||||
keyP = Promise.resolve();
|
||||
} else {
|
||||
query.keypair = keypair;
|
||||
query.receipt = receipt;
|
||||
/*
|
||||
query.server = gnlck._defaults.directoryUrl.replace(
|
||||
/^https?:\/\//i,
|
||||
''
|
||||
);
|
||||
*/
|
||||
keyP = db.setKeypair(query, keypair);
|
||||
}
|
||||
keyP = db.setKeypair(query, keypair);
|
||||
}
|
||||
|
||||
return keyP
|
||||
.then(function() {
|
||||
if (!db.set) {
|
||||
return Promise.resolve({
|
||||
keypair: keypair
|
||||
});
|
||||
}
|
||||
return db.set(
|
||||
{
|
||||
// id to be set by Store
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail,
|
||||
agreeTos: true,
|
||||
agreeToTerms: true,
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
/*
|
||||
return keyP
|
||||
.then(function() {
|
||||
if (!db.set) {
|
||||
return Promise.resolve({
|
||||
keypair: keypair
|
||||
});
|
||||
}
|
||||
return db.set(
|
||||
{
|
||||
// id to be set by Store
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail,
|
||||
agreeTos: true,
|
||||
agreeToTerms: true,
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
/*
|
||||
server: gnlck._defaults.directoryUrl.replace(
|
||||
/^https?:\/\//i,
|
||||
''
|
||||
)
|
||||
*/
|
||||
},
|
||||
reg
|
||||
);
|
||||
})
|
||||
.then(function(fullAccount) {
|
||||
if (fullAccount && 'object' !== typeof fullAccount) {
|
||||
throw new Error(
|
||||
"accounts.set should either return 'null' or an object with an 'id' string"
|
||||
);
|
||||
}
|
||||
},
|
||||
reg
|
||||
);
|
||||
})
|
||||
.then(function(fullAccount) {
|
||||
if (fullAccount && 'object' !== typeof fullAccount) {
|
||||
throw new Error(
|
||||
"accounts.set should either return 'null' or an object with an 'id' string"
|
||||
);
|
||||
}
|
||||
|
||||
if (!fullAccount) {
|
||||
fullAccount = {};
|
||||
}
|
||||
fullAccount.keypair = keypair;
|
||||
if (!fullAccount.key) {
|
||||
fullAccount.key = {};
|
||||
}
|
||||
fullAccount.key.kid = reg.kid;
|
||||
if (!fullAccount) {
|
||||
fullAccount = {};
|
||||
}
|
||||
fullAccount.keypair = keypair;
|
||||
if (!fullAccount.key) {
|
||||
fullAccount.key = {};
|
||||
}
|
||||
fullAccount.key.kid = reg.kid;
|
||||
|
||||
return fullAccount;
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
return fullAccount;
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
A._checkStore = function(gnlck, mconf, db, acme, args, email) {
|
||||
if ((args.domain || args.domains) && !args.subject) {
|
||||
console.warn("use 'subject' instead of 'domain'");
|
||||
args.subject = args.domain;
|
||||
}
|
||||
if ((args.domain || args.domains) && !args.subject) {
|
||||
console.warn("use 'subject' instead of 'domain'");
|
||||
args.subject = args.domain;
|
||||
}
|
||||
|
||||
var account = args.account;
|
||||
if (!account) {
|
||||
account = {};
|
||||
}
|
||||
var account = args.account;
|
||||
if (!account) {
|
||||
account = {};
|
||||
}
|
||||
|
||||
if (args.accountKey) {
|
||||
console.warn(
|
||||
'rather than passing accountKey, put it directly into your account key store'
|
||||
);
|
||||
// TODO we probably don't need this
|
||||
return U._importKeypair(args.accountKey);
|
||||
}
|
||||
if (args.accountKey) {
|
||||
console.warn(
|
||||
'rather than passing accountKey, put it directly into your account key store'
|
||||
);
|
||||
// TODO we probably don't need this
|
||||
return U._importKeypair(args.accountKey);
|
||||
}
|
||||
|
||||
if (!db.check) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (!db.check) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return db
|
||||
.check({
|
||||
//keypair: undefined,
|
||||
//receipt: undefined,
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail || mconf.customerEmail,
|
||||
account: account,
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
})
|
||||
.then(function(fullAccount) {
|
||||
if (!fullAccount) {
|
||||
return null;
|
||||
}
|
||||
return db
|
||||
.check({
|
||||
//keypair: undefined,
|
||||
//receipt: undefined,
|
||||
email: email,
|
||||
subscriberEmail: email,
|
||||
customerEmail: args.customerEmail || mconf.customerEmail,
|
||||
account: account,
|
||||
directoryUrl:
|
||||
args.directoryUrl ||
|
||||
mconf.directoryUrl ||
|
||||
gnlck._defaults.directoryUrl
|
||||
})
|
||||
.then(function(fullAccount) {
|
||||
if (!fullAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fullAccount;
|
||||
});
|
||||
return fullAccount;
|
||||
});
|
||||
};
|
||||
|
|
690
bin/certonly.js
690
bin/certonly.js
|
@ -4,375 +4,375 @@ var mkdirp = require('@root/mkdirp');
|
|||
var cli = require('./cli.js');
|
||||
|
||||
cli.parse({
|
||||
'directory-url': [
|
||||
false,
|
||||
' ACME Directory Resource URL',
|
||||
'string',
|
||||
'https://acme-v02.api.letsencrypt.org/directory',
|
||||
'server,acme-url'
|
||||
],
|
||||
email: [
|
||||
false,
|
||||
' Email used for registration and recovery contact. (default: null)',
|
||||
'email'
|
||||
],
|
||||
'agree-tos': [
|
||||
false,
|
||||
" Agree to the Greenlock and Let's Encrypt Subscriber Agreements",
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
'community-member': [
|
||||
false,
|
||||
' Submit stats to and get updates from Greenlock',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
domains: [
|
||||
false,
|
||||
' Domain names to apply. For multiple domains you can enter a comma separated list of domains as a parameter. (default: [])',
|
||||
'string'
|
||||
],
|
||||
'renew-offset': [
|
||||
false,
|
||||
' Positive (time after issue) or negative (time before expiry) offset, such as 30d or -45d',
|
||||
'string',
|
||||
'45d'
|
||||
],
|
||||
'renew-within': [
|
||||
false,
|
||||
' (ignored) use renew-offset instead',
|
||||
'ignore',
|
||||
undefined
|
||||
],
|
||||
'cert-path': [
|
||||
false,
|
||||
' Path to where new cert.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/cert.pem'
|
||||
],
|
||||
'fullchain-path': [
|
||||
false,
|
||||
' Path to where new fullchain.pem (cert + chain) is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/fullchain.pem'
|
||||
],
|
||||
'bundle-path': [
|
||||
false,
|
||||
' Path to where new bundle.pem (fullchain + privkey) is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/bundle.pem'
|
||||
],
|
||||
'chain-path': [
|
||||
false,
|
||||
' Path to where new chain.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/chain.pem'
|
||||
],
|
||||
'privkey-path': [
|
||||
false,
|
||||
' Path to where privkey.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/privkey.pem'
|
||||
],
|
||||
'config-dir': [
|
||||
false,
|
||||
' Configuration directory.',
|
||||
'string',
|
||||
'~/letsencrypt/etc/'
|
||||
],
|
||||
store: [
|
||||
false,
|
||||
' The name of the storage module to use',
|
||||
'string',
|
||||
'greenlock-store-fs'
|
||||
],
|
||||
'store-xxxx': [
|
||||
false,
|
||||
' An option for the chosen storage module, such as --store-apikey or --store-bucket',
|
||||
'bag'
|
||||
],
|
||||
'store-json': [
|
||||
false,
|
||||
' A JSON string containing all option for the chosen store module (instead of --store-xxxx)',
|
||||
'json',
|
||||
'{}'
|
||||
],
|
||||
challenge: [
|
||||
false,
|
||||
' The name of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use',
|
||||
'string',
|
||||
'@greenlock/acme-http-01-fs'
|
||||
],
|
||||
'challenge-xxxx': [
|
||||
false,
|
||||
' An option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket',
|
||||
'bag'
|
||||
],
|
||||
'challenge-json': [
|
||||
false,
|
||||
' A JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)',
|
||||
'json',
|
||||
'{}'
|
||||
],
|
||||
'skip-dry-run': [
|
||||
false,
|
||||
' Use with caution (and test with the staging url first). Creates an Order on the ACME server without a self-test.',
|
||||
'boolean'
|
||||
],
|
||||
'skip-challenge-tests': [
|
||||
false,
|
||||
' Use with caution (and with the staging url first). Presents challenges to the ACME server without first testing locally.',
|
||||
'boolean'
|
||||
],
|
||||
'http-01-port': [
|
||||
false,
|
||||
' Required to be 80 for live servers. Do not use. For special test environments only.',
|
||||
'int'
|
||||
],
|
||||
'dns-01': [false, ' Use DNS-01 challange type', 'boolean', false],
|
||||
standalone: [
|
||||
false,
|
||||
' Obtain certs using a "standalone" webserver.',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
manual: [
|
||||
false,
|
||||
' Print the token and key to the screen and wait for you to hit enter, giving you time to copy it somewhere before continuing (uses acme-http-01-cli or acme-dns-01-cli)',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
debug: [false, ' show traces and logs', 'boolean', false],
|
||||
root: [
|
||||
false,
|
||||
' public_html / webroot path (may use the :hostname template such as /srv/www/:hostname)',
|
||||
'string',
|
||||
undefined,
|
||||
'webroot-path'
|
||||
],
|
||||
'directory-url': [
|
||||
false,
|
||||
' ACME Directory Resource URL',
|
||||
'string',
|
||||
'https://acme-v02.api.letsencrypt.org/directory',
|
||||
'server,acme-url'
|
||||
],
|
||||
email: [
|
||||
false,
|
||||
' Email used for registration and recovery contact. (default: null)',
|
||||
'email'
|
||||
],
|
||||
'agree-tos': [
|
||||
false,
|
||||
" Agree to the Greenlock and Let's Encrypt Subscriber Agreements",
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
'community-member': [
|
||||
false,
|
||||
' Submit stats to and get updates from Greenlock',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
domains: [
|
||||
false,
|
||||
' Domain names to apply. For multiple domains you can enter a comma separated list of domains as a parameter. (default: [])',
|
||||
'string'
|
||||
],
|
||||
'renew-offset': [
|
||||
false,
|
||||
' Positive (time after issue) or negative (time before expiry) offset, such as 30d or -45d',
|
||||
'string',
|
||||
'45d'
|
||||
],
|
||||
'renew-within': [
|
||||
false,
|
||||
' (ignored) use renew-offset instead',
|
||||
'ignore',
|
||||
undefined
|
||||
],
|
||||
'cert-path': [
|
||||
false,
|
||||
' Path to where new cert.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/cert.pem'
|
||||
],
|
||||
'fullchain-path': [
|
||||
false,
|
||||
' Path to where new fullchain.pem (cert + chain) is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/fullchain.pem'
|
||||
],
|
||||
'bundle-path': [
|
||||
false,
|
||||
' Path to where new bundle.pem (fullchain + privkey) is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/bundle.pem'
|
||||
],
|
||||
'chain-path': [
|
||||
false,
|
||||
' Path to where new chain.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/chain.pem'
|
||||
],
|
||||
'privkey-path': [
|
||||
false,
|
||||
' Path to where privkey.pem is saved',
|
||||
'string',
|
||||
':configDir/live/:hostname/privkey.pem'
|
||||
],
|
||||
'config-dir': [
|
||||
false,
|
||||
' Configuration directory.',
|
||||
'string',
|
||||
'~/letsencrypt/etc/'
|
||||
],
|
||||
store: [
|
||||
false,
|
||||
' The name of the storage module to use',
|
||||
'string',
|
||||
'greenlock-store-fs'
|
||||
],
|
||||
'store-xxxx': [
|
||||
false,
|
||||
' An option for the chosen storage module, such as --store-apikey or --store-bucket',
|
||||
'bag'
|
||||
],
|
||||
'store-json': [
|
||||
false,
|
||||
' A JSON string containing all option for the chosen store module (instead of --store-xxxx)',
|
||||
'json',
|
||||
'{}'
|
||||
],
|
||||
challenge: [
|
||||
false,
|
||||
' The name of the HTTP-01, DNS-01, or TLS-ALPN-01 challenge module to use',
|
||||
'string',
|
||||
'@greenlock/acme-http-01-fs'
|
||||
],
|
||||
'challenge-xxxx': [
|
||||
false,
|
||||
' An option for the chosen challenge module, such as --challenge-apikey or --challenge-bucket',
|
||||
'bag'
|
||||
],
|
||||
'challenge-json': [
|
||||
false,
|
||||
' A JSON string containing all option for the chosen challenge module (instead of --challenge-xxxx)',
|
||||
'json',
|
||||
'{}'
|
||||
],
|
||||
'skip-dry-run': [
|
||||
false,
|
||||
' Use with caution (and test with the staging url first). Creates an Order on the ACME server without a self-test.',
|
||||
'boolean'
|
||||
],
|
||||
'skip-challenge-tests': [
|
||||
false,
|
||||
' Use with caution (and with the staging url first). Presents challenges to the ACME server without first testing locally.',
|
||||
'boolean'
|
||||
],
|
||||
'http-01-port': [
|
||||
false,
|
||||
' Required to be 80 for live servers. Do not use. For special test environments only.',
|
||||
'int'
|
||||
],
|
||||
'dns-01': [false, ' Use DNS-01 challange type', 'boolean', false],
|
||||
standalone: [
|
||||
false,
|
||||
' Obtain certs using a "standalone" webserver.',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
manual: [
|
||||
false,
|
||||
' Print the token and key to the screen and wait for you to hit enter, giving you time to copy it somewhere before continuing (uses acme-http-01-cli or acme-dns-01-cli)',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
debug: [false, ' show traces and logs', 'boolean', false],
|
||||
root: [
|
||||
false,
|
||||
' public_html / webroot path (may use the :hostname template such as /srv/www/:hostname)',
|
||||
'string',
|
||||
undefined,
|
||||
'webroot-path'
|
||||
],
|
||||
|
||||
//
|
||||
// backwards compat
|
||||
//
|
||||
duplicate: [
|
||||
false,
|
||||
' Allow getting a certificate that duplicates an existing one/is an early renewal',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
'rsa-key-size': [
|
||||
false,
|
||||
' (ignored) use server-key-type or account-key-type instead',
|
||||
'ignore',
|
||||
2048
|
||||
],
|
||||
'server-key-path': [
|
||||
false,
|
||||
' Path to privkey.pem to use for certificate (default: generate new)',
|
||||
'string',
|
||||
undefined,
|
||||
'domain-key-path'
|
||||
],
|
||||
'server-key-type': [
|
||||
false,
|
||||
" One of 'RSA' (2048), 'RSA-3084', 'RSA-4096', 'ECDSA' (P-256), or 'P-384'. For best compatibility, security, and efficiency use the default (More bits != More security)",
|
||||
'string',
|
||||
'RSA'
|
||||
],
|
||||
'account-key-path': [
|
||||
false,
|
||||
' Path to privkey.pem to use for account (default: generate new)',
|
||||
'string'
|
||||
],
|
||||
'account-key-type': [
|
||||
false,
|
||||
" One of 'ECDSA' (P-256), 'P-384', 'RSA', 'RSA-3084', or 'RSA-4096'. Stick with 'ECDSA' (P-256) unless you need 'RSA' (2048) for legacy compatibility. (More bits != More security)",
|
||||
'string',
|
||||
'P-256'
|
||||
],
|
||||
webroot: [false, ' (ignored) for certbot compatibility', 'ignore', false],
|
||||
//, 'standalone-supported-challenges': [ false, " Supported challenges, order preferences are randomly chosen. (default: http-01,tls-alpn-01)", 'string', 'http-01']
|
||||
'work-dir': [
|
||||
false,
|
||||
' for certbot compatibility (ignored)',
|
||||
'string',
|
||||
'~/letsencrypt/var/lib/'
|
||||
],
|
||||
'logs-dir': [
|
||||
false,
|
||||
' for certbot compatibility (ignored)',
|
||||
'string',
|
||||
'~/letsencrypt/var/log/'
|
||||
],
|
||||
'acme-version': [
|
||||
false,
|
||||
' (ignored) ACME is now RFC 8555 and prior drafts are no longer supported',
|
||||
'ignore',
|
||||
'rfc8555'
|
||||
]
|
||||
//
|
||||
// backwards compat
|
||||
//
|
||||
duplicate: [
|
||||
false,
|
||||
' Allow getting a certificate that duplicates an existing one/is an early renewal',
|
||||
'boolean',
|
||||
false
|
||||
],
|
||||
'rsa-key-size': [
|
||||
false,
|
||||
' (ignored) use server-key-type or account-key-type instead',
|
||||
'ignore',
|
||||
2048
|
||||
],
|
||||
'server-key-path': [
|
||||
false,
|
||||
' Path to privkey.pem to use for certificate (default: generate new)',
|
||||
'string',
|
||||
undefined,
|
||||
'domain-key-path'
|
||||
],
|
||||
'server-key-type': [
|
||||
false,
|
||||
" One of 'RSA' (2048), 'RSA-3084', 'RSA-4096', 'ECDSA' (P-256), or 'P-384'. For best compatibility, security, and efficiency use the default (More bits != More security)",
|
||||
'string',
|
||||
'RSA'
|
||||
],
|
||||
'account-key-path': [
|
||||
false,
|
||||
' Path to privkey.pem to use for account (default: generate new)',
|
||||
'string'
|
||||
],
|
||||
'account-key-type': [
|
||||
false,
|
||||
" One of 'ECDSA' (P-256), 'P-384', 'RSA', 'RSA-3084', or 'RSA-4096'. Stick with 'ECDSA' (P-256) unless you need 'RSA' (2048) for legacy compatibility. (More bits != More security)",
|
||||
'string',
|
||||
'P-256'
|
||||
],
|
||||
webroot: [false, ' (ignored) for certbot compatibility', 'ignore', false],
|
||||
//, 'standalone-supported-challenges': [ false, " Supported challenges, order preferences are randomly chosen. (default: http-01,tls-alpn-01)", 'string', 'http-01']
|
||||
'work-dir': [
|
||||
false,
|
||||
' for certbot compatibility (ignored)',
|
||||
'string',
|
||||
'~/letsencrypt/var/lib/'
|
||||
],
|
||||
'logs-dir': [
|
||||
false,
|
||||
' for certbot compatibility (ignored)',
|
||||
'string',
|
||||
'~/letsencrypt/var/log/'
|
||||
],
|
||||
'acme-version': [
|
||||
false,
|
||||
' (ignored) ACME is now RFC 8555 and prior drafts are no longer supported',
|
||||
'ignore',
|
||||
'rfc8555'
|
||||
]
|
||||
});
|
||||
|
||||
// ignore certonly and extraneous arguments
|
||||
cli.main(function(_, options) {
|
||||
console.info('');
|
||||
console.info('');
|
||||
|
||||
[
|
||||
'configDir',
|
||||
'privkeyPath',
|
||||
'certPath',
|
||||
'chainPath',
|
||||
'fullchainPath',
|
||||
'bundlePath'
|
||||
].forEach(function(k) {
|
||||
if (options[k]) {
|
||||
options.storeOpts[k] = options[k];
|
||||
}
|
||||
delete options[k];
|
||||
});
|
||||
[
|
||||
'configDir',
|
||||
'privkeyPath',
|
||||
'certPath',
|
||||
'chainPath',
|
||||
'fullchainPath',
|
||||
'bundlePath'
|
||||
].forEach(function(k) {
|
||||
if (options[k]) {
|
||||
options.storeOpts[k] = options[k];
|
||||
}
|
||||
delete options[k];
|
||||
});
|
||||
|
||||
if (options.workDir) {
|
||||
options.challengeOpts.workDir = options.workDir;
|
||||
delete options.workDir;
|
||||
}
|
||||
if (options.workDir) {
|
||||
options.challengeOpts.workDir = options.workDir;
|
||||
delete options.workDir;
|
||||
}
|
||||
|
||||
if (options.debug) {
|
||||
console.debug(options);
|
||||
}
|
||||
if (options.debug) {
|
||||
console.debug(options);
|
||||
}
|
||||
|
||||
var args = {};
|
||||
var homedir = require('os').homedir();
|
||||
var args = {};
|
||||
var homedir = require('os').homedir();
|
||||
|
||||
Object.keys(options).forEach(function(key) {
|
||||
var val = options[key];
|
||||
Object.keys(options).forEach(function(key) {
|
||||
var val = options[key];
|
||||
|
||||
if ('string' === typeof val) {
|
||||
val = val.replace(/^~/, homedir);
|
||||
}
|
||||
if ('string' === typeof val) {
|
||||
val = val.replace(/^~/, homedir);
|
||||
}
|
||||
|
||||
key = key.replace(/\-([a-z0-9A-Z])/g, function(c) {
|
||||
return c[1].toUpperCase();
|
||||
});
|
||||
args[key] = val;
|
||||
});
|
||||
key = key.replace(/\-([a-z0-9A-Z])/g, function(c) {
|
||||
return c[1].toUpperCase();
|
||||
});
|
||||
args[key] = val;
|
||||
});
|
||||
|
||||
Object.keys(args).forEach(function(key) {
|
||||
var val = args[key];
|
||||
Object.keys(args).forEach(function(key) {
|
||||
var val = args[key];
|
||||
|
||||
if ('string' === typeof val) {
|
||||
val = val.replace(/(\:configDir)|(\:config)/, args.configDir);
|
||||
}
|
||||
if ('string' === typeof val) {
|
||||
val = val.replace(/(\:configDir)|(\:config)/, args.configDir);
|
||||
}
|
||||
|
||||
args[key] = val;
|
||||
});
|
||||
args[key] = val;
|
||||
});
|
||||
|
||||
if (args.domains) {
|
||||
args.domains = args.domains.split(',');
|
||||
}
|
||||
if (args.domains) {
|
||||
args.domains = args.domains.split(',');
|
||||
}
|
||||
|
||||
if (
|
||||
!(Array.isArray(args.domains) && args.domains.length) ||
|
||||
!args.email ||
|
||||
!args.agreeTos ||
|
||||
(!args.server && !args.directoryUrl)
|
||||
) {
|
||||
console.error('\nUsage:\n\ngreenlock certonly --standalone \\');
|
||||
console.error(
|
||||
'\t--agree-tos --email user@example.com --domains example.com \\'
|
||||
);
|
||||
console.error('\t--config-dir ~/acme/etc \\');
|
||||
console.error('\nSee greenlock --help for more details\n');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(Array.isArray(args.domains) && args.domains.length) ||
|
||||
!args.email ||
|
||||
!args.agreeTos ||
|
||||
(!args.server && !args.directoryUrl)
|
||||
) {
|
||||
console.error('\nUsage:\n\ngreenlock certonly --standalone \\');
|
||||
console.error(
|
||||
'\t--agree-tos --email user@example.com --domains example.com \\'
|
||||
);
|
||||
console.error('\t--config-dir ~/acme/etc \\');
|
||||
console.error('\nSee greenlock --help for more details\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.http01Port) {
|
||||
// [@agnat]: Coerce to string. cli returns a number although we request a string.
|
||||
args.http01Port = '' + args.http01Port;
|
||||
args.http01Port = args.http01Port.split(',').map(function(port) {
|
||||
return parseInt(port, 10);
|
||||
});
|
||||
}
|
||||
if (args.http01Port) {
|
||||
// [@agnat]: Coerce to string. cli returns a number although we request a string.
|
||||
args.http01Port = '' + args.http01Port;
|
||||
args.http01Port = args.http01Port.split(',').map(function(port) {
|
||||
return parseInt(port, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function run() {
|
||||
var challenges = {};
|
||||
if (/http.?01/i.test(args.challenge)) {
|
||||
challenges['http-01'] = args.challengeOpts;
|
||||
}
|
||||
if (/dns.?01/i.test(args.challenge)) {
|
||||
challenges['dns-01'] = args.challengeOpts;
|
||||
}
|
||||
if (/alpn.?01/i.test(args.challenge)) {
|
||||
challenges['tls-alpn-01'] = args.challengeOpts;
|
||||
}
|
||||
if (!Object.keys(challenges).length) {
|
||||
throw new Error(
|
||||
"Could not determine the challenge type for '" +
|
||||
args.challengeOpts.module +
|
||||
"'. Expected a name like @you/acme-xxxx-01-foo. Please name the module with http-01, dns-01, or tls-alpn-01."
|
||||
);
|
||||
}
|
||||
args.challengeOpts.module = args.challenge;
|
||||
args.storeOpts.module = args.store;
|
||||
function run() {
|
||||
var challenges = {};
|
||||
if (/http.?01/i.test(args.challenge)) {
|
||||
challenges['http-01'] = args.challengeOpts;
|
||||
}
|
||||
if (/dns.?01/i.test(args.challenge)) {
|
||||
challenges['dns-01'] = args.challengeOpts;
|
||||
}
|
||||
if (/alpn.?01/i.test(args.challenge)) {
|
||||
challenges['tls-alpn-01'] = args.challengeOpts;
|
||||
}
|
||||
if (!Object.keys(challenges).length) {
|
||||
throw new Error(
|
||||
"Could not determine the challenge type for '" +
|
||||
args.challengeOpts.module +
|
||||
"'. Expected a name like @you/acme-xxxx-01-foo. Please name the module with http-01, dns-01, or tls-alpn-01."
|
||||
);
|
||||
}
|
||||
args.challengeOpts.module = args.challenge;
|
||||
args.storeOpts.module = args.store;
|
||||
|
||||
console.log('\ngot to the run step');
|
||||
require(args.challenge);
|
||||
require(args.store);
|
||||
console.log('\ngot to the run step');
|
||||
require(args.challenge);
|
||||
require(args.store);
|
||||
|
||||
var greenlock = require('../').create({
|
||||
maintainerEmail: args.maintainerEmail || 'coolaj86@gmail.com',
|
||||
manager: './manager.js',
|
||||
configFile: '~/.config/greenlock/certs.json',
|
||||
challenges: challenges,
|
||||
store: args.storeOpts,
|
||||
renewOffset: args.renewOffset || '30d',
|
||||
renewStagger: '1d'
|
||||
});
|
||||
var greenlock = require('../').create({
|
||||
maintainerEmail: args.maintainerEmail || 'coolaj86@gmail.com',
|
||||
manager: './manager.js',
|
||||
configFile: '~/.config/greenlock/certs.json',
|
||||
challenges: challenges,
|
||||
store: args.storeOpts,
|
||||
renewOffset: args.renewOffset || '30d',
|
||||
renewStagger: '1d'
|
||||
});
|
||||
|
||||
// for long-running processes
|
||||
if (args.renewEvery) {
|
||||
setInterval(function() {
|
||||
greenlock.renew({
|
||||
period: args.renewEvery
|
||||
});
|
||||
}, args.renewEvery);
|
||||
}
|
||||
// for long-running processes
|
||||
if (args.renewEvery) {
|
||||
setInterval(function() {
|
||||
greenlock.renew({
|
||||
period: args.renewEvery
|
||||
});
|
||||
}, args.renewEvery);
|
||||
}
|
||||
|
||||
// TODO should greenlock.add simply always include greenlock.renew?
|
||||
// the concern is conflating error events
|
||||
return greenlock
|
||||
.add({
|
||||
subject: args.subject,
|
||||
altnames: args.altnames,
|
||||
subscriberEmail: args.subscriberEmail || args.email
|
||||
})
|
||||
.then(function(changes) {
|
||||
console.info(changes);
|
||||
// renew should always
|
||||
return greenlock
|
||||
.renew({
|
||||
subject: args.subject,
|
||||
force: false
|
||||
})
|
||||
.then(function() {});
|
||||
});
|
||||
}
|
||||
// TODO should greenlock.add simply always include greenlock.renew?
|
||||
// the concern is conflating error events
|
||||
return greenlock
|
||||
.add({
|
||||
subject: args.subject,
|
||||
altnames: args.altnames,
|
||||
subscriberEmail: args.subscriberEmail || args.email
|
||||
})
|
||||
.then(function(changes) {
|
||||
console.info(changes);
|
||||
// renew should always
|
||||
return greenlock
|
||||
.renew({
|
||||
subject: args.subject,
|
||||
force: false
|
||||
})
|
||||
.then(function() {});
|
||||
});
|
||||
}
|
||||
|
||||
if ('greenlock-store-fs' !== args.store) {
|
||||
run();
|
||||
return;
|
||||
}
|
||||
if ('greenlock-store-fs' !== args.store) {
|
||||
run();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO remove mkdirp and let greenlock-store-fs do this?
|
||||
mkdirp(args.storeOpts.configDir, function(err) {
|
||||
if (!err) {
|
||||
run();
|
||||
}
|
||||
// TODO remove mkdirp and let greenlock-store-fs do this?
|
||||
mkdirp(args.storeOpts.configDir, function(err) {
|
||||
if (!err) {
|
||||
run();
|
||||
}
|
||||
|
||||
console.error(
|
||||
"Could not create --config-dir '" + args.configDir + "':",
|
||||
err.code
|
||||
);
|
||||
console.error("Try setting --config-dir '/tmp'");
|
||||
return;
|
||||
});
|
||||
console.error(
|
||||
"Could not create --config-dir '" + args.configDir + "':",
|
||||
err.code
|
||||
);
|
||||
console.error("Try setting --config-dir '/tmp'");
|
||||
return;
|
||||
});
|
||||
}, process.argv.slice(3));
|
||||
|
|
372
bin/cli.js
372
bin/cli.js
|
@ -7,228 +7,228 @@ var defaultOpts;
|
|||
var bags = [];
|
||||
|
||||
CLI.parse = function(conf) {
|
||||
var opts = (defaultOpts = {});
|
||||
defaultConf = conf;
|
||||
var opts = (defaultOpts = {});
|
||||
defaultConf = conf;
|
||||
|
||||
Object.keys(conf).forEach(function(k) {
|
||||
var v = conf[k];
|
||||
var aliases = v[5];
|
||||
var bag;
|
||||
var bagName;
|
||||
Object.keys(conf).forEach(function(k) {
|
||||
var v = conf[k];
|
||||
var aliases = v[5];
|
||||
var bag;
|
||||
var bagName;
|
||||
|
||||
// the name of the argument set is now the 0th argument
|
||||
v.unshift(k);
|
||||
// v[0] flagname
|
||||
// v[1] short flagname
|
||||
// v[2] description
|
||||
// v[3] type
|
||||
// v[4] default value
|
||||
// v[5] aliases
|
||||
// the name of the argument set is now the 0th argument
|
||||
v.unshift(k);
|
||||
// v[0] flagname
|
||||
// v[1] short flagname
|
||||
// v[2] description
|
||||
// v[3] type
|
||||
// v[4] default value
|
||||
// v[5] aliases
|
||||
|
||||
if ('bag' === v[3]) {
|
||||
bag = v[0]; // 'bag-option-xxxx' => '--bag-option-'
|
||||
bag = '--' + bag.replace(/xxx.*/, '');
|
||||
bags.push(bag);
|
||||
if ('bag' === v[3]) {
|
||||
bag = v[0]; // 'bag-option-xxxx' => '--bag-option-'
|
||||
bag = '--' + bag.replace(/xxx.*/, '');
|
||||
bags.push(bag);
|
||||
|
||||
bagName = toBagName(bag.replace(/^--/, ''));
|
||||
opts[bagName] = {};
|
||||
}
|
||||
bagName = toBagName(bag.replace(/^--/, ''));
|
||||
opts[bagName] = {};
|
||||
}
|
||||
|
||||
if ('json' === v[3]) {
|
||||
bagName = toBagName(v[0].replace(/-json$/, '')); // 'bag-option-json' => 'bagOptionOpts'
|
||||
opts[bagName] = {};
|
||||
} else if ('ignore' !== v[3] && 'undefined' !== typeof v[4]) {
|
||||
// set the default values (where 'undefined' is not an allowed value)
|
||||
opts[toCamel(k)] = v[4];
|
||||
}
|
||||
if ('json' === v[3]) {
|
||||
bagName = toBagName(v[0].replace(/-json$/, '')); // 'bag-option-json' => 'bagOptionOpts'
|
||||
opts[bagName] = {};
|
||||
} else if ('ignore' !== v[3] && 'undefined' !== typeof v[4]) {
|
||||
// set the default values (where 'undefined' is not an allowed value)
|
||||
opts[toCamel(k)] = v[4];
|
||||
}
|
||||
|
||||
if (!aliases) {
|
||||
aliases = [];
|
||||
} else if ('string' === typeof aliases) {
|
||||
aliases = aliases.split(',');
|
||||
}
|
||||
aliases.forEach(function(alias) {
|
||||
if (alias in conf) {
|
||||
throw new Error(
|
||||
"Cannot alias '" +
|
||||
alias +
|
||||
"' from '" +
|
||||
k +
|
||||
"': option already exists"
|
||||
);
|
||||
}
|
||||
conf[alias] = v;
|
||||
});
|
||||
});
|
||||
if (!aliases) {
|
||||
aliases = [];
|
||||
} else if ('string' === typeof aliases) {
|
||||
aliases = aliases.split(',');
|
||||
}
|
||||
aliases.forEach(function(alias) {
|
||||
if (alias in conf) {
|
||||
throw new Error(
|
||||
"Cannot alias '" +
|
||||
alias +
|
||||
"' from '" +
|
||||
k +
|
||||
"': option already exists"
|
||||
);
|
||||
}
|
||||
conf[alias] = v;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
CLI.main = function(cb, args) {
|
||||
var leftovers = [];
|
||||
var conf = defaultConf;
|
||||
var opts = defaultOpts;
|
||||
var leftovers = [];
|
||||
var conf = defaultConf;
|
||||
var opts = defaultOpts;
|
||||
|
||||
if (!opts) {
|
||||
throw new Error("you didn't call `CLI.parse(configuration)`");
|
||||
}
|
||||
if (!opts) {
|
||||
throw new Error("you didn't call `CLI.parse(configuration)`");
|
||||
}
|
||||
|
||||
// TODO what's the existing API for this?
|
||||
if (!args) {
|
||||
args = process.argv.slice(2);
|
||||
}
|
||||
// TODO what's the existing API for this?
|
||||
if (!args) {
|
||||
args = process.argv.slice(2);
|
||||
}
|
||||
|
||||
var flag;
|
||||
var cnf;
|
||||
var typ;
|
||||
var flag;
|
||||
var cnf;
|
||||
var typ;
|
||||
|
||||
function grab(bag) {
|
||||
var bagName = toBagName(bag);
|
||||
if (bag !== flag.slice(0, bag.length)) {
|
||||
return false;
|
||||
}
|
||||
console.log(bagName, toCamel(flag.slice(bag.length)));
|
||||
opts[bagName][toCamel(flag.slice(bag.length))] = args.shift();
|
||||
return true;
|
||||
}
|
||||
function grab(bag) {
|
||||
var bagName = toBagName(bag);
|
||||
if (bag !== flag.slice(0, bag.length)) {
|
||||
return false;
|
||||
}
|
||||
console.log(bagName, toCamel(flag.slice(bag.length)));
|
||||
opts[bagName][toCamel(flag.slice(bag.length))] = args.shift();
|
||||
return true;
|
||||
}
|
||||
|
||||
while (args.length) {
|
||||
// take one off the top
|
||||
flag = args.shift();
|
||||
while (args.length) {
|
||||
// take one off the top
|
||||
flag = args.shift();
|
||||
|
||||
// mind the gap
|
||||
if ('--' === flag) {
|
||||
leftovers = leftovers.concat(args);
|
||||
break;
|
||||
}
|
||||
// mind the gap
|
||||
if ('--' === flag) {
|
||||
leftovers = leftovers.concat(args);
|
||||
break;
|
||||
}
|
||||
|
||||
// help!
|
||||
if (
|
||||
'--help' === flag ||
|
||||
'-h' === flag ||
|
||||
'/?' === flag ||
|
||||
'help' === flag
|
||||
) {
|
||||
printHelp(conf);
|
||||
process.exit(1);
|
||||
}
|
||||
// help!
|
||||
if (
|
||||
'--help' === flag ||
|
||||
'-h' === flag ||
|
||||
'/?' === flag ||
|
||||
'help' === flag
|
||||
) {
|
||||
printHelp(conf);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// only long names are actually used
|
||||
if ('--' !== flag.slice(0, 2)) {
|
||||
console.error("Unrecognized argument '" + flag + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
// only long names are actually used
|
||||
if ('--' !== flag.slice(0, 2)) {
|
||||
console.error("Unrecognized argument '" + flag + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
cnf = conf[flag.slice(2)];
|
||||
if (!cnf) {
|
||||
// look for arbitrary flags
|
||||
if (bags.some(grab)) {
|
||||
continue;
|
||||
}
|
||||
cnf = conf[flag.slice(2)];
|
||||
if (!cnf) {
|
||||
// look for arbitrary flags
|
||||
if (bags.some(grab)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// other arbitrary args are not used
|
||||
console.error("Unrecognized flag '" + flag + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
// other arbitrary args are not used
|
||||
console.error("Unrecognized flag '" + flag + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// encourage switching to non-aliased version
|
||||
if (flag !== '--' + cnf[0]) {
|
||||
console.warn(
|
||||
"use of '" +
|
||||
flag +
|
||||
"' is deprecated, use '--" +
|
||||
cnf[0] +
|
||||
"' instead"
|
||||
);
|
||||
}
|
||||
// encourage switching to non-aliased version
|
||||
if (flag !== '--' + cnf[0]) {
|
||||
console.warn(
|
||||
"use of '" +
|
||||
flag +
|
||||
"' is deprecated, use '--" +
|
||||
cnf[0] +
|
||||
"' instead"
|
||||
);
|
||||
}
|
||||
|
||||
// look for xxx-json flags
|
||||
if ('json' === cnf[3]) {
|
||||
try {
|
||||
var json = JSON.parse(args.shift());
|
||||
var bagName = toBagName(cnf[0].replace(/-json$/, ''));
|
||||
Object.keys(json).forEach(function(k) {
|
||||
opts[bagName][k] = json[k];
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Could not parse option '" + flag + "' as JSON:");
|
||||
console.error(e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// look for xxx-json flags
|
||||
if ('json' === cnf[3]) {
|
||||
try {
|
||||
var json = JSON.parse(args.shift());
|
||||
var bagName = toBagName(cnf[0].replace(/-json$/, ''));
|
||||
Object.keys(json).forEach(function(k) {
|
||||
opts[bagName][k] = json[k];
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Could not parse option '" + flag + "' as JSON:");
|
||||
console.error(e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// set booleans, otherwise grab the next arg in line
|
||||
typ = cnf[3];
|
||||
// TODO --no-<whatever> to negate
|
||||
if (Boolean === typ || 'boolean' === typ) {
|
||||
opts[toCamel(cnf[0])] = true;
|
||||
continue;
|
||||
}
|
||||
opts[toCamel(cnf[0])] = args.shift();
|
||||
continue;
|
||||
}
|
||||
// set booleans, otherwise grab the next arg in line
|
||||
typ = cnf[3];
|
||||
// TODO --no-<whatever> to negate
|
||||
if (Boolean === typ || 'boolean' === typ) {
|
||||
opts[toCamel(cnf[0])] = true;
|
||||
continue;
|
||||
}
|
||||
opts[toCamel(cnf[0])] = args.shift();
|
||||
continue;
|
||||
}
|
||||