diff --git a/migrations/20210429224354-settings-add-valueBlob.js b/migrations/20210429224354-settings-add-valueBlob.js new file mode 100644 index 000000000..1a8098824 --- /dev/null +++ b/migrations/20210429224354-settings-add-valueBlob.js @@ -0,0 +1,20 @@ +'use strict'; + +const fs = require('fs'); + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE settings ADD COLUMN valueBlob MEDIUMBLOB', function (error) { + if (error) return callback(error); + + fs.readFile('/home/yellowtent/boxdata/avatar.png', { encoding: 'base64' }, function (error, avatar) { + if (error && error.code === 'ENOENT') return callback(); + if (error) return callback(error); + + db.runSql('INSERT INTO settings (name, valueBlob) VALUES (?, ?)', [ 'cloudron_avatar', avatar ], callback); + }); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index 54142db8c..e197c03bc 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -106,6 +106,7 @@ CREATE TABLE IF NOT EXISTS appPortBindings( CREATE TABLE IF NOT EXISTS settings( name VARCHAR(128) NOT NULL UNIQUE, value TEXT, + valueBlob MEDIUMBLOB, PRIMARY KEY(name)); CREATE TABLE IF NOT EXISTS appAddonConfigs( diff --git a/src/paths.js b/src/paths.js index 2571dad12..32490acfc 100644 --- a/src/paths.js +++ b/src/paths.js @@ -47,7 +47,6 @@ exports = module.exports = { SFTP_KEYS_DIR: path.join(baseDir(), 'boxdata/sftp/ssh'), ACME_ACCOUNT_KEY_FILE: path.join(baseDir(), 'boxdata/acme/acme.key'), APP_CERTS_DIR: path.join(baseDir(), 'boxdata/certs'), - CLOUDRON_AVATAR_FILE: path.join(baseDir(), 'boxdata/avatar.png'), ADDON_TURN_SECRET_FILE: path.join(baseDir(), 'boxdata/addon-turn-secret'), FIREWALL_BLOCKLIST_FILE: path.join(baseDir(), 'boxdata/firewall/blocklist.txt'), diff --git a/src/routes/branding.js b/src/routes/branding.js index a437367da..ee44ec96b 100644 --- a/src/routes/branding.js +++ b/src/routes/branding.js @@ -92,7 +92,8 @@ function setCloudronAvatar(req, res, next) { assert.strictEqual(typeof req.files, 'object'); if (!req.files.avatar) return next(new HttpError(400, 'avatar must be provided')); - var avatar = safe.fs.readFileSync(req.files.avatar.path); + const avatar = safe.fs.readFileSync(req.files.avatar.path); + if (!avatar) return next(500, safe.error.message); settings.setCloudronAvatar(avatar, function (error) { if (error) return next(BoxError.toHttpError(error)); diff --git a/src/settings.js b/src/settings.js index d3fbaab3c..bfca1c42b 100644 --- a/src/settings.js +++ b/src/settings.js @@ -111,7 +111,7 @@ exports = module.exports = { FOOTER_KEY: 'footer', // blobs - CLOUDRON_AVATAR_KEY: 'cloudron_avatar', // not stored in db but can be used for locked flag + CLOUDRON_AVATAR_KEY: 'cloudron_avatar', // testing _setApiServerOrigin: setApiServerOrigin @@ -132,7 +132,6 @@ const assert = require('assert'), settingsdb = require('./settingsdb.js'), sysinfo = require('./sysinfo.js'), translation = require('./translation.js'), - util = require('util'), _ = require('underscore'); let gDefaults = (function () { @@ -296,25 +295,28 @@ function setCloudronName(name, callback) { function getCloudronAvatar(callback) { assert.strictEqual(typeof callback, 'function'); - var avatar = safe.fs.readFileSync(paths.CLOUDRON_AVATAR_FILE); - if (avatar) return callback(null, avatar); + settingsdb.getBlob(exports.CLOUDRON_AVATAR_KEY, function (error, avatar) { + if (error && error.reason !== BoxError.NOT_FOUND) return callback(error); - // try default fallback - avatar = safe.fs.readFileSync(paths.CLOUDRON_DEFAULT_AVATAR_FILE); - if (avatar) return callback(null, avatar); + if (avatar) return callback(null, avatar); - callback(new BoxError(BoxError.FS_ERROR, safe.error)); + // try default fallback + avatar = safe.fs.readFileSync(paths.CLOUDRON_DEFAULT_AVATAR_FILE); + if (avatar) return callback(null, avatar); + + callback(new BoxError(BoxError.FS_ERROR, safe.error)); + }); } function setCloudronAvatar(avatar, callback) { assert(Buffer.isBuffer(avatar)); assert.strictEqual(typeof callback, 'function'); - if (!safe.fs.writeFileSync(paths.CLOUDRON_AVATAR_FILE, avatar)) { - return callback(new BoxError(BoxError.FS_ERROR, safe.error)); - } + settingsdb.setBlob(exports.CLOUDRON_AVATAR_KEY, avatar, function (error) { + if (error) return callback(error); - return callback(null); + return callback(null); + }); } function getDynamicDnsConfig(callback) { diff --git a/src/settingsdb.js b/src/settingsdb.js index 7c2324eef..ebe2a208f 100644 --- a/src/settingsdb.js +++ b/src/settingsdb.js @@ -3,9 +3,11 @@ 'use strict'; exports = module.exports = { - get: get, - getAll: getAll, - set: set, + get, + getAll, + set, + getBlob, + setBlob, _clear: clear }; @@ -14,6 +16,7 @@ var assert = require('assert'), database = require('./database.js'); const SETTINGS_FIELDS = [ 'name', 'value' ].join(','); +const SETTINGS_BLOB_FIELDS = [ 'name', 'valueBlob' ].join(','); function get(key, callback) { assert.strictEqual(typeof key, 'string'); @@ -47,6 +50,31 @@ function set(key, value, callback) { }); } +function getBlob(key, callback) { + assert.strictEqual(typeof key, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query(`SELECT ${SETTINGS_BLOB_FIELDS} FROM settings WHERE name = ?`, [ key ], function (error, result) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Setting not found')); + + callback(null, result[0].valueBlob); + }); +} + +function setBlob(key, value, callback) { + assert.strictEqual(typeof key, 'string'); + assert(value === null || Buffer.isBuffer(value)); + assert.strictEqual(typeof callback, 'function'); + + database.query('INSERT INTO settings (name, valueBlob) VALUES (?, ?) ON DUPLICATE KEY UPDATE valueBlob=VALUES(valueBlob)', [ key, value ], function (error) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); // don't rely on affectedRows here since it gives 2 + + callback(null); + }); +} + + function clear(callback) { database.query('DELETE FROM settings', function (error) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));