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)
Changes
@@@ -599,8 +599,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fiif (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');}});});@@@ -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. */nnew file mode 100644@@@ -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');
@@ -599,8 +599,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
@@ -599,8 +599,12 @@ console.log(`[Server] /shared: serving ${SHARED_BROWSER_FILES.length} browser fi
if (hashed) res.setHeader('CDN-Cache-Control', cache.IMMUTABLE);
if (hashed) res.setHeader('CDN-Cache-Control', cache.IMMUTABLE);
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
res.sendFile(filePath, (err) => {
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) {
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');
}
}
});
});
});
});
@@ -12,12 +12,21 @@
@@ -12,12 +12,21 @@
* (LIVE_DRILL, see ./drill.js) those are ignored: the drill loads the production env file, whose
* (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.
* values point at production's directories. Only DATA_DIR counts there.
*/
*/
+const fs = require('fs');
const path = require('path');
const path = require('path');
const drill = require('./drill');
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() {
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. */
/** A path under the data directory. */
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -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');