diff --git a/dashboard/src/views/app.js b/dashboard/src/views/app.js index 56bee67ea..2342806b0 100644 --- a/dashboard/src/views/app.js +++ b/dashboard/src/views/app.js @@ -774,8 +774,11 @@ angular.module('Application').controller('AppController', ['$scope', '$location' $scope.storage.error.storageVolumePrefix = error.message; $scope.storage.busyDataDir = false; return; + } else if (error) { + Client.error(error); + $scope.storage.busyDataDir = false; + return; } - if (error) return Client.error(error); $scope.storageDataDirForm.$setPristine(); diff --git a/src/apps.js b/src/apps.js index dbfaaee74..9260bf61b 100644 --- a/src/apps.js +++ b/src/apps.js @@ -510,6 +510,8 @@ async function checkStorage(app, volumeId, prefix) { if (prefix !== '' && path.normalize(prefix) !== prefix) throw new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" is not a normalized path`); const sourceDir = await getStorageDir(app); + if (sourceDir === null) throw new BoxError(BoxError.BAD_STATE, 'App does not use localstorage addon'); + const targetDir = path.join(volume.hostPath, prefix); const rel = path.relative(sourceDir, targetDir); if (!rel.startsWith('../') && rel.split('/').length > 1) throw new BoxError(BoxError.BAD_FIELD, 'Only one level subdirectory moves are supported'); @@ -557,6 +559,8 @@ function getDuplicateErrorDetails(errorMessage, locations, portBindings) { async function getStorageDir(app) { assert.strictEqual(typeof app, 'object'); + if (!app.manifest.addons?.localstorage) return null; + if (!app.storageVolumeId) return path.join(paths.APPS_DATA_DIR, app.id, 'data'); const volume = await volumes.get(app.storageVolumeId); if (!volume) throw new BoxError(BoxError.NOT_FOUND, 'Volume not found'); // not possible diff --git a/src/apptask.js b/src/apptask.js index a55101653..4f3edee65 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -211,6 +211,7 @@ async function downloadIcon(app) { async function moveDataDir(app, targetVolumeId, targetVolumePrefix) { assert.strictEqual(typeof app, 'object'); + assert.ok(app.manifest.addons.localstorage, 'should have local storage addon'); assert(targetVolumeId === null || typeof targetVolumeId === 'string'); assert(targetVolumePrefix === null || typeof targetVolumePrefix === 'string'); @@ -532,8 +533,10 @@ async function migrateDataDir(app, args, progressCallback) { await progressCallback({ percent: 50, message: 'Setting up addons' }); await services.setupAddons(Object.assign({}, app, { storageVolumeId: newStorageVolumeId, storageVolumePrefix: newStorageVolumePrefix }), app.manifest.addons); - await progressCallback({ percent: 60, message: 'Moving data dir' }); - await moveDataDir(app, newStorageVolumeId, newStorageVolumePrefix); + if (app.manifest.addons?.localstorage) { + await progressCallback({ percent: 60, message: 'Moving data dir' }); + await moveDataDir(app, newStorageVolumeId, newStorageVolumePrefix); + } await progressCallback({ percent: 90, message: 'Creating container' }); await createContainer(app); @@ -625,6 +628,7 @@ async function update(app, args, progressCallback) { // only delete unused addons after backup await services.teardownAddons(app, unusedAddons); + if (Object.keys(unusedAddons).includes('localstorage')) await updateApp(app, { storageVolumeId: null, storageVolumePrefix: null }); // lose reference // free unused ports const currentPorts = app.portBindings || {}; diff --git a/src/docker.js b/src/docker.js index c6d3151c1..7b5de4367 100644 --- a/src/docker.js +++ b/src/docker.js @@ -164,15 +164,14 @@ async function downloadImage(manifest) { async function getVolumeMounts(app) { assert.strictEqual(typeof app, 'object'); - let mounts = []; - if (app.mounts.length === 0) return []; const result = await volumes.list(); - - let volumesById = {}; + const volumesById = {}; result.forEach(r => volumesById[r.id] = r); + const mounts = []; + for (const mount of app.mounts) { const volume = volumesById[mount.volumeId]; @@ -190,8 +189,7 @@ async function getVolumeMounts(app) { async function getAddonMounts(app) { assert.strictEqual(typeof app, 'object'); - let mounts = []; - + const mounts = []; const addons = app.manifest.addons; if (!addons) return mounts; @@ -265,8 +263,7 @@ async function createSubcontainer(app, name, cmd, options) { assert(!cmd || Array.isArray(cmd)); assert.strictEqual(typeof options, 'object'); - let isAppContainer = !cmd; // non app-containers are like scheduler - + const isAppContainer = !cmd; // non app-containers are like scheduler const manifest = app.manifest; const exposedPorts = {}, dockerPortBindings = { }; const domain = app.fqdn; diff --git a/src/sftp.js b/src/sftp.js index 21b61cf54..9ef8763af 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -69,7 +69,7 @@ async function start(existingInfra) { if (!app.manifest.addons?.localstorage || !app.storageVolumeId) continue; const hostDir = await apps.getStorageDir(app), mountDir = `/mnt/app-${app.id}`; // see also sftp:userSearchSftp - if (!safe.fs.existsSync(hostDir)) { // this can fail if external mount does not have permissions for yellowtent user + if (hostDir === null || !safe.fs.existsSync(hostDir)) { // this can fail if external mount does not have permissions for yellowtent user // do not create host path when cloudron is restoring. this will then create dir with root perms making restore logic fail debug(`Ignoring app data dir ${hostDir} for ${app.id} since it does not exist`); continue; diff --git a/src/system.js b/src/system.js index 828b3bd06..f6cff8730 100644 --- a/src/system.js +++ b/src/system.js @@ -157,6 +157,7 @@ async function getDisks() { if (!app.manifest.addons?.localstorage) continue; const dataDir = await apps.getStorageDir(app); + if (dataDir === null) continue; const [, dfResult] = await safe(df.file(dataDir)); const filesystem = dfResult?.filesystem || rootDisk.filesystem; if (disks[filesystem]) disks[filesystem].contents.push({ type: 'app', id: app.id, path: dataDir });