Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 26 additions & 15 deletions tools/firmware-builder/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -879,24 +879,35 @@ async function copyConfig() {
/**
* Download configuration files
*/
function downloadConfigFiles() {
const config = wizardState.config;
async function downloadConfigFiles() {
const buildId = window.currentBuildId;
if (!buildId) {
if (typeof showToast === 'function') {
showToast('Click "Build firmware.bin" first to generate the full config files', 'error');
}
return;
}

// Also save the JSON config so they can reload it later
// Save the JSON config (lets the user reload these exact settings later)
exportConfig(false);

if (typeof ConfigGenerator !== 'undefined') {
// Generate Configuration.h
setTimeout(() => {
const configH = ConfigGenerator.generateConfigurationH(config);
downloadConfigFile('Configuration.h', configH);
}, 300);

// Generate Configuration_adv.h
setTimeout(() => {
const configAdvH = ConfigGenerator.generateConfigurationAdvH(config);
downloadConfigFile('Configuration_adv.h', configAdvH);
}, 600);
try {
const [hResp, advResp] = await Promise.all([
fetch(`${BUILDER_URL}/download/${buildId}/Configuration.h`),
fetch(`${BUILDER_URL}/download/${buildId}/Configuration_adv.h`)
]);
if (!hResp.ok || !advResp.ok) {
throw new Error('Configs not ready');
}
const hBlob = await hResp.blob();
const advBlob = await advResp.blob();
downloadBlob(hBlob, 'Configuration.h');
setTimeout(() => downloadBlob(advBlob, 'Configuration_adv.h'), 300);
} catch (err) {
console.error('Config download failed:', err);
if (typeof showToast === 'function') {
showToast('Config files not ready yet — wait for the build to finish, then try again', 'error');
}
}
}

Expand Down
41 changes: 25 additions & 16 deletions tools/firmware-builder/worker/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export default {
return await handleStatusRequest(buildId, env, corsHeaders);
}

// GET /download/:buildId - Download firmware
// GET /download/:buildId[/:filename] - Download firmware or config files
if (url.pathname.startsWith('/download/') && request.method === 'GET') {
const buildId = url.pathname.split('/download/')[1];
return await handleDownloadRequest(buildId, env, corsHeaders);
const parts = url.pathname.split('/').filter(Boolean);
const buildId = parts[1];
const filename = parts[2] || 'firmware.bin';
return await handleDownloadRequest(buildId, filename, env, corsHeaders);
}

// POST /webhook - GitHub Actions callback (internal)
Expand Down Expand Up @@ -223,32 +225,39 @@ async function handleStatusRequest(buildId, env, corsHeaders) {
}

/**
* Handle firmware download request
* Handle download request for firmware.bin or config files.
* Config files are uploaded to R2 by the workflow even on build failure,
* so they don't require a complete build; firmware.bin does.
*/
async function handleDownloadRequest(buildId, env, corsHeaders) {
// Check build status
const buildData = await env.BUILDS.get(`build:${buildId}`, 'json');
async function handleDownloadRequest(buildId, filename, env, corsHeaders) {
const ALLOWED = ['firmware.bin', 'Configuration.h', 'Configuration_adv.h'];
if (!ALLOWED.includes(filename)) {
return new Response('File not allowed', { status: 400, headers: corsHeaders });
}

const buildData = await env.BUILDS.get(`build:${buildId}`, 'json');
if (!buildData) {
return new Response('Build not found', { status: 404, headers: corsHeaders });
}

if (buildData.status !== 'complete') {
if (filename === 'firmware.bin' && buildData.status !== 'complete') {
return new Response('Build not ready', { status: 400, headers: corsHeaders });
}

// Get firmware from R2
const firmware = await env.FIRMWARE.get(`${buildId}/firmware.bin`);

if (!firmware) {
return new Response('Firmware file not found', { status: 404, headers: corsHeaders });
const file = await env.FIRMWARE.get(`${buildId}/${filename}`);
if (!file) {
return new Response('File not found', { status: 404, headers: corsHeaders });
}

return new Response(firmware.body, {
const contentType = filename.endsWith('.bin')
? 'application/octet-stream'
: 'text/plain';

return new Response(file.body, {
headers: {
...corsHeaders,
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="firmware.bin"`
'Content-Type': contentType,
'Content-Disposition': `attachment; filename="${filename}"`
}
});
}
Expand Down