openvibers/openvibe.live Commits

The data directory is the real folder behind ./data, and an unreadable /shared file answers 503 no-store: on 10-17 a deploy pruned the release Live still ran from, so the navbar 404'd (Cloudflare cached it) and thumbnails and AI frames failed with ENOENT for 2.5 hours (Host#131 stops the pruning) (#238)

OP OpenVibers committed 27 days ago · cdb4c1ea69 · 3 files, +53 −3

Changes

ddiff --git a/server/index.js b/server/index.js
iindex c722c566..e28ee783 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -599,8 +599,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
if (hashed) res.setHeader('CDN-Cache-Control', cache.IMMUTABLE);
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
res.sendFile(filePath, (err) => {
++ // A listed file that cannot be read is this server's fault, and passing: never a cacheable answer
++ // (its headers above said immutable; Cloudflare kept a 404 navbar through a deploy on 2026-10-17).
if (err && !res.headersSent) {
-- res.status(404).type('text/plain').send('Not found');
++ res.removeHeader('CDN-Cache-Control');
++ res.setHeader('Cache-Control', 'no-store');
++ res.status(503).type('text/plain').send('Unavailable');
}
});
});
ddiff --git a/server/paths.js b/server/paths.js
iindex 390032ac..2a19effd 100644
---- a/server/paths.js
++++ b/server/paths.js
@@@ -12,12 +12,21 @@
* (LIVE_DRILL, see ./drill.js) those are ignored: the drill loads the production env file, whose
* values point at production's directories. Only DATA_DIR counts there.
*/
++const fs = require('fs');
const path = require('path');
const drill = require('./drill');
--/** The data directory (absolute). */
++let real = { from: null, dir: null };
++/**
++ * The data directory (absolute, links resolved). In production ./data is a release's link to the shared data
++ * folder, and a release folder can be removed while the process still runs from it (2026-10-17: thumbnails
++ * and AI frames failed with ENOENT for 2.5 hours), so every path is built on the link's target.
++ */
function dataDir() {
-- return path.resolve(process.env.DATA_DIR || './data');
++ const want = path.resolve(process.env.DATA_DIR || './data');
++ if (real.from === want) return real.dir;
++ try { real = { from: want, dir: fs.realpathSync(want) }; } catch { return want; /* not made yet */ }
++ return real.dir;
}
/** A path under the data directory. */
ddiff --git a/test/paths-real-data.test.js b/test/paths-real-data.test.js
nnew file mode 100644
iindex 00000000..f54eb669
---- /dev/null
++++ b/test/paths-real-data.test.js
@@@ -0,0 +1,37 @@
++/**
++ * The data directory is the real folder behind ./data (server/paths.js). In production ./data is the release's
++ * link to the shared data folder; on 2026-10-17 a deploy removed the release folder Live still ran from, and
++ * every thumbnail and AI frame written through <release>/data failed with ENOENT for 2.5 hours.
++ * And a /shared file the server lists but cannot read answers 503 no-store, never a cacheable 404.
++ *
++ * node test/paths-real-data.test.js
++ */
++'use strict';
++const assert = require('assert');
++const fs = require('fs');
++const os = require('os');
++const path = require('path');
++
++const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-paths-'));
++const shared = path.join(root, 'shared', 'data');
++const release = path.join(root, 'releases', 'r1');
++fs.mkdirSync(shared, { recursive: true });
++fs.mkdirSync(release, { recursive: true });
++fs.symlinkSync('../../shared/data', path.join(release, 'data'));
++process.env.DATA_DIR = path.join(release, 'data');
++const paths = require('../server/paths');
++try {
++ assert.strictEqual(paths.dataDir(), fs.realpathSync(shared), 'the link\'s target');
++ assert.strictEqual(paths.data('live-thumbs'), path.join(fs.realpathSync(shared), 'live-thumbs'));
++ fs.rmSync(release, { recursive: true });
++ assert.strictEqual(paths.data('ai-moments'), path.join(fs.realpathSync(shared), 'ai-moments'), 'still the shared folder once the release is gone');
++ process.env.DATA_DIR = path.join(root, 'not-yet');
++ assert.strictEqual(paths.dataDir(), path.join(root, 'not-yet'), 'a folder not made yet is used as given');
++} finally {
++ fs.rmSync(root, { recursive: true, force: true });
++}
++
++const index = fs.readFileSync(path.join(__dirname, '..', 'server', 'index.js'), 'utf8');
++const shared404 = index.slice(index.indexOf("app.use('/shared', (req, res, next)"), index.indexOf("app.use('/shared', (req, res, next)") + 2200);
++assert.match(shared404, /res\.sendFile\(filePath, \(err\) => \{[\s\S]*?res\.removeHeader\('CDN-Cache-Control'\);\s*res\.setHeader\('Cache-Control', 'no-store'\);\s*res\.status\(503\)/, 'an unreadable /shared file is 503 no-store');
++console.log('paths real data: all checks passed');