feat(ruffle): seed a fresh data volume from the bundled copy

This commit is contained in:
Darkraise
2026-08-13 17:15:50 +07:00
parent 9b28abc8b9
commit f746bb2f81
3 changed files with 93 additions and 12 deletions
+4 -12
View File
@@ -293,20 +293,12 @@ async function startServer() {
logger.warn('Failed to sweep orphaned download files:', error);
});
// Ruffle installs in the background: it downloads from GitHub, and awaiting it
// here kept the port closed for minutes on slow storage — indefinitely if the
// download stalled. Flash games need it, everything else does not.
// Ruffle resolves in the background: seeding copies ~20 MB and the download
// fallback reaches GitHub, and awaiting either here kept the port closed for
// minutes on slow storage. Flash games need it, everything else does not.
void (async () => {
try {
const ruffleService = new RuffleService();
if (!ruffleService.verifyInstallation()) {
logger.info('🎮 Ruffle not found, downloading latest version...');
await ruffleService.updateRuffle();
logger.info('✅ Ruffle installation complete');
} else {
const version = ruffleService.getCurrentVersion();
logger.info(`✅ Ruffle verified (version: ${version || 'unknown'})`);
}
await new RuffleService().ensureInstalled();
} catch (error) {
logger.error('Failed to install Ruffle:', error);
logger.warn('⚠️ Continuing without Ruffle - Flash games will not work');
@@ -259,3 +259,48 @@ describe('RuffleService.updateRuffle', () => {
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('old build');
});
});
function installBundledRuffle(): void {
const bundled = nodePath.join(distDir, 'ruffle');
nodeFs.mkdirSync(bundled, { recursive: true });
nodeFs.writeFileSync(nodePath.join(bundled, 'ruffle.js'), 'bundled build');
nodeFs.writeFileSync(
nodePath.join(bundled, 'package.json'),
JSON.stringify({ version: '0.2.0-nightly.2026.1.29' })
);
}
describe('RuffleService.ensureInstalled', () => {
it('leaves an existing installation alone', async () => {
installExistingRuffle();
installBundledRuffle();
const result = await new RuffleService().ensureInstalled();
expect(result).toBe('present');
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('old build');
expect(axios.get).not.toHaveBeenCalled();
});
it('seeds from the bundled copy when nothing is installed', async () => {
installBundledRuffle();
const service = new RuffleService();
const result = await service.ensureInstalled();
expect(result).toBe('seeded');
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe(
'bundled build'
);
expect(service.getCurrentVersion()).toBe('0.2.0-nightly.2026.1.29');
expect(axios.get).not.toHaveBeenCalled();
});
it('downloads when neither an installation nor a bundled copy exists', async () => {
const result = await new RuffleService().ensureInstalled();
expect(result).toBe('downloaded');
expect(nodeFs.readFileSync(nodePath.join(ruffleDir(), 'ruffle.js'), 'utf-8')).toBe('new build');
expect(axios.get).toHaveBeenCalled();
});
});
+44
View File
@@ -581,4 +581,48 @@ export class RuffleService {
return false;
}
}
/**
* Copy the Ruffle shipped inside the image into the install directory. A fresh
* data volume otherwise has no player at all until a download finishes — and
* never gets one on a host that cannot reach GitHub.
*/
private seedFromBundle(): boolean {
if (this.bundledPath === this.installPath) {
return false;
}
if (!fs.existsSync(path.join(this.bundledPath, 'ruffle.js'))) {
return false;
}
try {
this.copyDirectory(this.bundledPath, this.installPath);
logger.info(
`✅ Ruffle seeded from the bundled copy (version: ${this.getCurrentVersion() ?? 'unknown'})`
);
return true;
} catch (error: unknown) {
logger.warn('[RuffleService] Could not seed Ruffle from the bundled copy:', error);
return false;
}
}
/**
* Make a Ruffle available, preferring what is already installed, then the copy
* baked into the image, and only then a download.
*/
async ensureInstalled(): Promise<'present' | 'seeded' | 'downloaded'> {
if (this.verifyInstallation()) {
logger.info(`✅ Ruffle verified (version: ${this.getCurrentVersion() ?? 'unknown'})`);
return 'present';
}
if (this.seedFromBundle()) {
return 'seeded';
}
logger.info('🎮 Ruffle not found, downloading latest version...');
await this.updateRuffle();
logger.info('✅ Ruffle installation complete');
return 'downloaded';
}
}