Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69d9f98190 | ||
|
|
ab255e78c5 | ||
|
|
2628678d82 | ||
|
|
eaf9b7f049 | ||
|
|
b8df4d0b79 | ||
|
|
eb315f34dc | ||
|
|
af535757a8 | ||
|
|
600e030c6d | ||
|
|
e86b813551 | ||
|
|
af6653dfeb | ||
|
|
f93e0c868c | ||
|
|
e53aaddc9c | ||
|
|
d3ebb99131 |
26
CHANGES
26
CHANGES
@@ -1438,3 +1438,29 @@
|
||||
* Add support for hyphenated subdomains
|
||||
* Add domain, mail events to eventlog
|
||||
|
||||
[3.3.3]
|
||||
* Use new addons with REST APIs
|
||||
* Ubuntu 18.04 LTS support
|
||||
* Custom env vars can be set per application
|
||||
* Add a button to renew certs
|
||||
* Add better support for private builds
|
||||
* cloudflare: Fix crash when using bad email
|
||||
* cloudflare: HTTP proxying works now
|
||||
* add new exoscale-sos regions
|
||||
* Add UI to toggle dynamic DNS
|
||||
* Add support for hyphenated subdomains
|
||||
* Add domain, mail events to eventlog
|
||||
|
||||
[3.3.4]
|
||||
* Use new addons with REST APIs
|
||||
* Ubuntu 18.04 LTS support
|
||||
* Custom env vars can be set per application
|
||||
* Add a button to renew certs
|
||||
* Add better support for private builds
|
||||
* cloudflare: Fix crash when using bad email
|
||||
* cloudflare: HTTP proxying works now
|
||||
* add new exoscale-sos regions
|
||||
* Add UI to toggle dynamic DNS
|
||||
* Add support for hyphenated subdomains
|
||||
* Add domain, mail events to eventlog
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script collects diagnostic information to help debug server related issues
|
||||
# It also enables SSH access for the cloudron support team
|
||||
|
||||
PASTEBIN="https://paste.cloudron.io"
|
||||
OUT="/tmp/cloudron-support.log"
|
||||
LINE="\n========================================================\n"
|
||||
@@ -11,13 +14,31 @@ if [[ ${EUID} -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if at least 10mb root partition space is available
|
||||
if [[ "`df --output="avail" / | sed -n 2p`" -lt "10240" ]]; then
|
||||
echo "No more space left on /"
|
||||
echo "This is likely the root case of the issue. Free up some space and also check other partitions below:"
|
||||
echo ""
|
||||
df -h
|
||||
echo ""
|
||||
echo "To recover from a full disk, follow the guide at https://cloudron.io/documentation/server/#recovery-after-disk-full"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check for at least 5mb free /tmp space for the log file
|
||||
if [[ "`df --output="avail" /tmp | sed -n 2p`" -lt "5120" ]]; then
|
||||
echo "Not enough space left on /tmp"
|
||||
echo "Free up some space first by deleting files from /tmp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n "Generating Cloudron Support stats..."
|
||||
|
||||
# clear file
|
||||
rm -rf $OUT
|
||||
|
||||
ssh_port=$(cat /etc/ssh/sshd_config | grep "Port " | sed -e "s/.*Port //")
|
||||
if [[ $SUDO_USER === "" ]]; then
|
||||
if [[ $SUDO_USER == "" ]]; then
|
||||
ssh_user="root"
|
||||
ssh_folder="/root/.ssh/"
|
||||
authorized_key_file="${ssh_folder}/authorized_keys"
|
||||
@@ -35,7 +56,9 @@ echo -e $LINE"cloudron.conf"$LINE >> $OUT
|
||||
cat /etc/cloudron/cloudron.conf &>> $OUT
|
||||
|
||||
echo -e $LINE"Docker container"$LINE >> $OUT
|
||||
docker ps -a &>> $OUT
|
||||
if ! timeout --kill-after 10s 15s docker ps -a &>> $OUT 2>&1; then
|
||||
echo -e "Docker is not responding" >> $OUT
|
||||
fi
|
||||
|
||||
echo -e $LINE"Filesystem stats"$LINE >> $OUT
|
||||
df -h &>> $OUT
|
||||
@@ -61,6 +84,6 @@ chmod 600 "${authorized_key_file}"
|
||||
echo "Done"
|
||||
|
||||
echo ""
|
||||
echo "Please send the following link to support@cloudron.io"
|
||||
echo "Please email the following link to support@cloudron.io"
|
||||
echo ""
|
||||
echo "${PASTEBIN}/${paste_key}"
|
||||
|
||||
@@ -833,6 +833,32 @@ function teardownMySql(app, options, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function pipeRequestToFile(url, filename, callback) {
|
||||
assert.strictEqual(typeof url, 'string');
|
||||
assert.strictEqual(typeof filename, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
const writeStream = fs.createWriteStream(filename);
|
||||
|
||||
const done = once(function (error) { // the writeStream and the request can both error
|
||||
if (error) writeStream.close();
|
||||
callback(error);
|
||||
});
|
||||
|
||||
writeStream.on('error', done);
|
||||
writeStream.on('open', function () {
|
||||
// note: do not attach to post callback handler because this will buffer the entire reponse!
|
||||
// see https://github.com/request/request/issues/2270
|
||||
const req = request.post(url, { rejectUnauthorized: false });
|
||||
req.on('error', done); // network error, dns error, request errored in middle etc
|
||||
req.on('response', function (response) {
|
||||
if (response.statusCode !== 200) return done(new Error(`Unexpected response code: ${response.statusCode} message: ${response.statusMessage} filename: ${filename}`));
|
||||
|
||||
response.pipe(writeStream).on('finish', done); // this is hit after data written to disk
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function backupMySql(app, options, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
@@ -842,21 +868,11 @@ function backupMySql(app, options, callback) {
|
||||
|
||||
debugApp(app, 'Backing up mysql');
|
||||
|
||||
callback = once(callback); // protect from multiple returns with streams
|
||||
|
||||
getAddonDetails('mysql', 'CLOUDRON_MYSQL_TOKEN', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const writeStream = fs.createWriteStream(dumpPath('mysql', app.id));
|
||||
writeStream.on('error', callback);
|
||||
|
||||
const req = request.post(`https://${result.ip}:3000/` + (options.multipleDatabases ? 'prefixes' : 'databases') + `/${database}/backup?access_token=${result.token}`, { rejectUnauthorized: false }, function (error, response) {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error(`Unexpected response from mysql addon ${response.statusCode} message: ${response.body.message}`));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
req.pipe(writeStream);
|
||||
const url = `https://${result.ip}:3000/` + (options.multipleDatabases ? 'prefixes' : 'databases') + `/${database}/backup?access_token=${result.token}`;
|
||||
pipeRequestToFile(url, dumpPath('mysql', app.id), callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1027,21 +1043,11 @@ function backupPostgreSql(app, options, callback) {
|
||||
|
||||
const { database } = postgreSqlNames(app.id);
|
||||
|
||||
callback = once(callback); // protect from multiple returns with streams
|
||||
|
||||
getAddonDetails('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const writeStream = fs.createWriteStream(dumpPath('postgresql', app.id));
|
||||
writeStream.on('error', callback);
|
||||
|
||||
const req = request.post(`https://${result.ip}:3000/databases/${database}/backup?access_token=${result.token}`, { rejectUnauthorized: false }, function (error, response) {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error(`Unexpected response from postgresql addon ${response.statusCode} message: ${response.body.message}`));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
req.pipe(writeStream);
|
||||
const url = `https://${result.ip}:3000/databases/${database}/backup?access_token=${result.token}`;
|
||||
pipeRequestToFile(url, dumpPath('postgresql', app.id), callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1201,21 +1207,11 @@ function backupMongoDb(app, options, callback) {
|
||||
|
||||
debugApp(app, 'Backing up mongodb');
|
||||
|
||||
callback = once(callback); // protect from multiple returns with streams
|
||||
|
||||
getAddonDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const writeStream = fs.createWriteStream(dumpPath('mongodb', app.id));
|
||||
writeStream.on('error', callback);
|
||||
|
||||
const req = request.post(`https://${result.ip}:3000/databases/${app.id}/backup?access_token=${result.token}`, { rejectUnauthorized: false }, function (error, response) {
|
||||
if (error) return callback(error);
|
||||
if (response.statusCode !== 200) return callback(new Error(`Unexpected response from mongodb addon ${response.statusCode} message: ${response.body.message}`));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
req.pipe(writeStream);
|
||||
const url = `https://${result.ip}:3000/databases/${app.id}/backup?access_token=${result.token}`;
|
||||
pipeRequestToFile(url, dumpPath('mongodb', app.id), callback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1381,21 +1377,11 @@ function backupRedis(app, options, callback) {
|
||||
|
||||
debugApp(app, 'Backing up redis');
|
||||
|
||||
callback = once(callback); // protect from multiple returns with streams
|
||||
|
||||
getAddonDetails('redis-' + app.id, 'CLOUDRON_REDIS_TOKEN', function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const writeStream = fs.createWriteStream(dumpPath('redis', app.id));
|
||||
writeStream.on('error', callback);
|
||||
|
||||
const req = request.post(`https://${result.ip}:3000/backup?access_token=${result.token}`, { rejectUnauthorized: false }, function (error, response) {
|
||||
if (error) return callback(new Error('Error backing up redis: ' + error));
|
||||
if (response.statusCode !== 200) return callback(new Error(`Error backing up redis. Status code: ${response.statusCode} message: ${response.body.message}`));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
req.pipe(writeStream);
|
||||
const url = `https://${result.ip}:3000/backup?access_token=${result.token}`;
|
||||
pipeRequestToFile(url, dumpPath('redis', app.id), callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ exports = module.exports = {
|
||||
ACTION_BACKUP_START: 'backup.start',
|
||||
ACTION_BACKUP_CLEANUP: 'backup.cleanup',
|
||||
|
||||
ACTION_CERTIFICATE_NEW: 'certificate.new',
|
||||
ACTION_CERTIFICATE_RENEWAL: 'certificate.renew',
|
||||
|
||||
ACTION_DOMAIN_ADD: 'domain.add',
|
||||
|
||||
@@ -80,33 +80,29 @@ ReverseProxyError.INTERNAL_ERROR = 'Internal Error';
|
||||
ReverseProxyError.INVALID_CERT = 'Invalid certificate';
|
||||
ReverseProxyError.NOT_FOUND = 'Not Found';
|
||||
|
||||
function getCertApi(domain, callback) {
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
function getCertApi(domainObject, callback) {
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
domains.get(domain, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
if (domainObject.tlsConfig.provider === 'fallback') return callback(null, fallback, { fallback: true });
|
||||
|
||||
if (result.tlsConfig.provider === 'fallback') return callback(null, fallback, { fallback: true });
|
||||
var api = domainObject.tlsConfig.provider === 'caas' ? caas : acme2;
|
||||
|
||||
var api = result.tlsConfig.provider === 'caas' ? caas : acme2;
|
||||
var options = { prod: false, performHttpAuthorization: false, wildcard: false, email: '' };
|
||||
if (domainObject.tlsConfig.provider !== 'caas') { // matches 'le-prod' or 'letsencrypt-prod'
|
||||
options.prod = domainObject.tlsConfig.provider.match(/.*-prod/) !== null;
|
||||
options.performHttpAuthorization = domainObject.provider.match(/noop|manual|wildcard/) !== null;
|
||||
options.wildcard = !!domainObject.tlsConfig.wildcard;
|
||||
}
|
||||
|
||||
var options = { prod: false, performHttpAuthorization: false, wildcard: false, email: '' };
|
||||
if (result.tlsConfig.provider !== 'caas') { // matches 'le-prod' or 'letsencrypt-prod'
|
||||
options.prod = result.tlsConfig.provider.match(/.*-prod/) !== null;
|
||||
options.performHttpAuthorization = result.provider.match(/noop|manual|wildcard/) !== null;
|
||||
options.wildcard = !!result.tlsConfig.wildcard;
|
||||
}
|
||||
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
|
||||
// we cannot use admin@fqdn because the user might not have set it up.
|
||||
// we simply update the account with the latest email we have each time when getting letsencrypt certs
|
||||
// https://github.com/ietf-wg-acme/acme/issues/30
|
||||
users.getOwner(function (error, owner) {
|
||||
options.email = error ? 'support@cloudron.io' : (owner.fallbackEmail || owner.email); // can error if not activated yet
|
||||
|
||||
// registering user with an email requires A or MX record (https://github.com/letsencrypt/boulder/issues/1197)
|
||||
// we cannot use admin@fqdn because the user might not have set it up.
|
||||
// we simply update the account with the latest email we have each time when getting letsencrypt certs
|
||||
// https://github.com/ietf-wg-acme/acme/issues/30
|
||||
users.getOwner(function (error, owner) {
|
||||
options.email = error ? 'support@cloudron.io' : (owner.fallbackEmail || owner.email); // can error if not activated yet
|
||||
|
||||
callback(null, api, options);
|
||||
});
|
||||
callback(null, api, options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,27 +119,31 @@ function isExpiringSync(certFilePath, hours) {
|
||||
return result.status === 1; // 1 - expired 0 - not expired
|
||||
}
|
||||
|
||||
function providerMatchesSync(certFilePath, apiOptions) {
|
||||
// checks if the certificate matches the options provided by user (like wildcard, le-staging etc)
|
||||
function providerMatchesSync(domainObject, certFilePath, apiOptions) {
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
assert.strictEqual(typeof certFilePath, 'string');
|
||||
assert.strictEqual(typeof apiOptions, 'object');
|
||||
|
||||
if (!fs.existsSync(certFilePath)) return false; // not found
|
||||
|
||||
if (apiOptions.fallback) {
|
||||
return certFilePath.includes('.host.cert');
|
||||
}
|
||||
if (apiOptions.fallback) return certFilePath.includes('.host.cert');
|
||||
|
||||
const subjectAndIssuer = safe.child_process.execSync(`/usr/bin/openssl x509 -noout -subject -issuer -in "${certFilePath}"`, { encoding: 'utf8' });
|
||||
|
||||
const isWildcardCert = subjectAndIssuer.match(/^subject=(.*)$/m)[1].includes('*');
|
||||
const isLetsEncryptProd = subjectAndIssuer.match(/^issuer=(.*)$/m)[1].includes('Let\'s Encrypt Authority');
|
||||
const subject = subjectAndIssuer.match(/^subject=(.*)$/m)[1];
|
||||
const domain = subject.substr(subject.indexOf('=') + 1).trim(); // subject can be /CN=, CN=, CN = and other forms
|
||||
const issuer = subjectAndIssuer.match(/^issuer=(.*)$/m)[1];
|
||||
const isWildcardCert = domain.includes('*');
|
||||
const isLetsEncryptProd = issuer.includes('Let\'s Encrypt Authority');
|
||||
|
||||
const mismatch = ((apiOptions.wildcard && !isWildcardCert)
|
||||
|| (!apiOptions.wildcard && isWildcardCert)
|
||||
|| (apiOptions.prod && !isLetsEncryptProd)
|
||||
|| (!apiOptions.prod && isLetsEncryptProd));
|
||||
const issuerMismatch = (apiOptions.prod && !isLetsEncryptProd) || (!apiOptions.prod && isLetsEncryptProd);
|
||||
// bare domain is not part of wildcard SAN
|
||||
const wildcardMismatch = (domain !== domainObject.domain) && (apiOptions.wildcard && !isWildcardCert) || (!apiOptions.wildcard && isWildcardCert);
|
||||
|
||||
debug(`providerMatchesSync: ${certFilePath} ${subjectAndIssuer.trim().replace('\n', ' ')} wildcard=${isWildcardCert}/${apiOptions.wildcard} prod=${isLetsEncryptProd}/${apiOptions.prod} match=${!mismatch}`);
|
||||
const mismatch = issuerMismatch || wildcardMismatch;
|
||||
|
||||
debug(`providerMatchesSync: ${certFilePath} subject=${subject} domain=${domain} issuer=${issuer} wildcard=${isWildcardCert}/${apiOptions.wildcard} prod=${isLetsEncryptProd}/${apiOptions.prod} match=${!mismatch}`);
|
||||
|
||||
return !mismatch;
|
||||
}
|
||||
@@ -278,9 +278,9 @@ function setAppCertificateSync(location, domainObject, certificate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCertificateByHostname(hostname, domain, callback) {
|
||||
function getCertificateByHostname(hostname, domainObject, callback) {
|
||||
assert.strictEqual(typeof hostname, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
let certFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.user.cert`);
|
||||
@@ -288,34 +288,34 @@ function getCertificateByHostname(hostname, domain, callback) {
|
||||
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
|
||||
domains.get(domain, function (error, domainObject) {
|
||||
if (error) return callback(error);
|
||||
if (hostname !== domainObject.domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
|
||||
let certName = domains.makeWildcard(hostname).replace('*.', '_.');
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${certName}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${certName}.key`);
|
||||
|
||||
if (hostname !== domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
|
||||
let certName = domains.makeWildcard(hostname).replace('*.', '_.');
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${certName}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${certName}.key`);
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
} else {
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.key`);
|
||||
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
} else {
|
||||
certFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.cert`);
|
||||
keyFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.key`);
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
}
|
||||
|
||||
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function getCertificate(app, callback) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCertificateByHostname(app.fqdn, app.domain, function (error, result) {
|
||||
if (error || result) return callback(error, result);
|
||||
domains.get(app.domain, function (error, domainObject) {
|
||||
if (error) return callback(error);
|
||||
|
||||
return getFallbackCertificate(app.domain, callback);
|
||||
getCertificateByHostname(app.fqdn, domainObject, function (error, result) {
|
||||
if (error || result) return callback(error, result);
|
||||
|
||||
return getFallbackCertificate(app.domain, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -325,36 +325,40 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getCertApi(domain, function (error, api, apiOptions) {
|
||||
domains.get(domain, function (error, domainObject) {
|
||||
if (error) return callback(error);
|
||||
|
||||
getCertificateByHostname(vhost, domain, function (error, result) {
|
||||
if (result) {
|
||||
debug(`ensureCertificate: ${vhost} certificate already exists at ${result.keyFilePath}`);
|
||||
getCertApi(domainObject, function (error, api, apiOptions) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (result.certFilePath.endsWith('.user.cert')) return callback(null, result); // user certs cannot be renewed
|
||||
if (!isExpiringSync(result.certFilePath, 24 * 30) && providerMatchesSync(result.certFilePath, apiOptions)) return callback(null, result);
|
||||
debug(`ensureCertificate: ${vhost} cert require renewal`);
|
||||
} else {
|
||||
debug(`ensureCertificate: ${vhost} cert does not exist`);
|
||||
}
|
||||
getCertificateByHostname(vhost, domainObject, function (error, currentBundle) {
|
||||
if (currentBundle) {
|
||||
debug(`ensureCertificate: ${vhost} certificate already exists at ${currentBundle.keyFilePath}`);
|
||||
|
||||
debug('ensureCertificate: getting certificate for %s with options %j', vhost, apiOptions);
|
||||
|
||||
api.getCertificate(vhost, domain, apiOptions, function (error, certFilePath, keyFilePath) {
|
||||
var errorMessage = error ? error.message : '';
|
||||
|
||||
if (error) {
|
||||
debug('ensureCertificate: could not get certificate. using fallback certs', error);
|
||||
mailer.certificateRenewalError(vhost, errorMessage);
|
||||
if (currentBundle.certFilePath.endsWith('.user.cert')) return callback(null, currentBundle); // user certs cannot be renewed
|
||||
if (!isExpiringSync(currentBundle.certFilePath, 24 * 30) && providerMatchesSync(domainObject, currentBundle.certFilePath, apiOptions)) return callback(null, currentBundle);
|
||||
debug(`ensureCertificate: ${vhost} cert require renewal`);
|
||||
} else {
|
||||
debug(`ensureCertificate: ${vhost} cert does not exist`);
|
||||
}
|
||||
|
||||
eventlog.add(eventlog.ACTION_CERTIFICATE_RENEWAL, auditSource, { domain: vhost, errorMessage: errorMessage });
|
||||
debug('ensureCertificate: getting certificate for %s with options %j', vhost, apiOptions);
|
||||
|
||||
// if no cert was returned use fallback. the fallback/caas provider will not provide any for example
|
||||
if (!certFilePath || !keyFilePath) return getFallbackCertificate(domain, callback);
|
||||
api.getCertificate(vhost, domain, apiOptions, function (error, certFilePath, keyFilePath) {
|
||||
var errorMessage = error ? error.message : '';
|
||||
|
||||
callback(null, { certFilePath, keyFilePath, type: 'new-le' });
|
||||
if (error) {
|
||||
debug('ensureCertificate: could not get certificate. using fallback certs', error);
|
||||
mailer.certificateRenewalError(vhost, errorMessage);
|
||||
}
|
||||
|
||||
eventlog.add(currentBundle ? eventlog.ACTION_CERTIFICATE_RENEWAL : eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: vhost, errorMessage: errorMessage });
|
||||
|
||||
// if no cert was returned use fallback. the fallback/caas provider will not provide any for example
|
||||
if (!certFilePath || !keyFilePath) return getFallbackCertificate(domain, callback);
|
||||
|
||||
callback(null, { certFilePath, keyFilePath, type: 'new-le' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -523,7 +527,7 @@ function renewCerts(options, auditSource, callback) {
|
||||
ensureCertificate(appDomain.fqdn, appDomain.domain, auditSource, function (error, bundle) {
|
||||
if (error) return iteratorCallback(error); // this can happen if cloudron is not setup yet
|
||||
|
||||
// hack to check if the app's cert changed or not
|
||||
// hack to check if the app's cert changed or not. this doesn't handle prod/staging le change since they use same file name
|
||||
let currentNginxConfig = safe.fs.readFileSync(appDomain.nginxConfigFilename, 'utf8') || '';
|
||||
if (currentNginxConfig.includes(bundle.certFilePath)) return iteratorCallback();
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ function getUpdateInfo(req, res, next) {
|
||||
}
|
||||
|
||||
function checkForUpdates(req, res, next) {
|
||||
// it can take a while sometimes to get all the app updates one by one
|
||||
req.clearTimeout();
|
||||
|
||||
async.series([
|
||||
updateChecker.checkAppUpdates,
|
||||
updateChecker.checkBoxUpdates
|
||||
|
||||
@@ -98,6 +98,7 @@ function startNextTask() {
|
||||
startAppTask(gPendingTasks.shift(), NOOP_CALLBACK);
|
||||
}
|
||||
|
||||
// WARNING callback has to be called in sync for the concurrency check to work!
|
||||
function startAppTask(appId, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
@@ -126,39 +127,39 @@ function startAppTask(appId, callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// ensure log folder
|
||||
mkdirp.sync(path.join(paths.LOG_DIR, appId));
|
||||
var logFilePath = path.join(paths.LOG_DIR, appId, 'apptask.log');
|
||||
var fd;
|
||||
|
||||
// will autoclose
|
||||
fs.open(logFilePath, 'a', function (error, fd) {
|
||||
if (error) {
|
||||
debug('Unable to open log file, queueing task for %s', appId, error);
|
||||
gPendingTasks.push(appId);
|
||||
return callback();
|
||||
// have to use sync here to avoid async callback, breaking concurrency check
|
||||
try {
|
||||
mkdirp.sync(path.join(paths.LOG_DIR, appId)); // ensure log folder
|
||||
fd = fs.openSync(logFilePath, 'a'); // will autoclose
|
||||
} catch (e) {
|
||||
debug('Unable to get log filedescriptor, queueing task for %s', appId, e);
|
||||
gPendingTasks.push(appId);
|
||||
return callback();
|
||||
}
|
||||
|
||||
// when parent process dies, apptask processes are killed because KillMode=control-group in systemd unit file
|
||||
gActiveTasks[appId] = child_process.fork(__dirname + '/apptask.js', [ appId ], { stdio: [ 'pipe', fd, fd, 'ipc' ]});
|
||||
|
||||
var pid = gActiveTasks[appId].pid;
|
||||
debug('Started task of %s pid: %s. See logs at %s', appId, pid, logFilePath);
|
||||
|
||||
gActiveTasks[appId].once('exit', function (code, signal) {
|
||||
debug('Task for %s pid %s completed with status %s', appId, pid, code);
|
||||
if (code === null /* signal */ || (code !== 0 && code !== 50)) { // apptask crashed
|
||||
debug('Apptask crashed with code %s and signal %s', code, signal);
|
||||
sendFailureLogs('apptask', { unit: 'box' });
|
||||
appdb.update(appId, { installationState: appdb.ISTATE_ERROR, installationProgress: 'Apptask crashed with code ' + code + ' and signal ' + signal }, NOOP_CALLBACK);
|
||||
} else if (code === 50) {
|
||||
sendFailureLogs('apptask', { unit: 'box' });
|
||||
}
|
||||
|
||||
// when parent process dies, apptask processes are killed because KillMode=control-group in systemd unit file
|
||||
gActiveTasks[appId] = child_process.fork(__dirname + '/apptask.js', [ appId ], { stdio: [ 'pipe', fd, fd, 'ipc' ]});
|
||||
|
||||
var pid = gActiveTasks[appId].pid;
|
||||
debug('Started task of %s pid: %s. See logs at %s', appId, pid, logFilePath);
|
||||
|
||||
gActiveTasks[appId].once('exit', function (code, signal) {
|
||||
debug('Task for %s pid %s completed with status %s', appId, pid, code);
|
||||
if (code === null /* signal */ || (code !== 0 && code !== 50)) { // apptask crashed
|
||||
debug('Apptask crashed with code %s and signal %s', code, signal);
|
||||
sendFailureLogs('apptask', { unit: 'box' });
|
||||
appdb.update(appId, { installationState: appdb.ISTATE_ERROR, installationProgress: 'Apptask crashed with code ' + code + ' and signal ' + signal }, NOOP_CALLBACK);
|
||||
} else if (code === 50) {
|
||||
sendFailureLogs('apptask', { unit: 'box' });
|
||||
}
|
||||
delete gActiveTasks[appId];
|
||||
locker.unlock(locker.OP_APPTASK); // unlock event will trigger next task
|
||||
});
|
||||
|
||||
callback();
|
||||
delete gActiveTasks[appId];
|
||||
locker.unlock(locker.OP_APPTASK); // unlock event will trigger next task
|
||||
});
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
function stopAppTask(appId, callback) {
|
||||
|
||||
Reference in New Issue
Block a user