Created
January 28, 2024 02:21
-
-
Save agzam/ee0e4a1132940ff39123403147fed679 to your computer and use it in GitHub Desktop.
Update Kinesis 360 ZMK helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// OMG. This all so stupidly dirty and so needlessly tangled. | |
// I imagined this would be a tiny, simple thing, but it came out as this crap. | |
// Fuck Javascript, I should rewrite the whole thing in Clojurescript (but I'm lazy) | |
// | |
// This is a helper script to update Kinesis 360 firmware. | |
// When you put the keyboard in the bootloader mode it becomes unavailable, and you can't type | |
// but the mouse remains connected. | |
// This thing basically loads a tiny html page where you can click things to update and unblock your keyboard. | |
// | |
// Prerequisites: | |
// - needs `gh` cmd-line tool installed | |
// - working gpg, and sudoer password in ~/.authinfo.gpg - change it to your host and user, I'm too lazy to make that configurable. | |
// | |
// When you run the script: | |
// 1. It can grab the latest build from the repository (change down below to point at yours) | |
// 2. Then you put the board into the bootloader mode - Mod+1 for left and Mod+3 for right side, or use the hidden push button | |
// 3. Wait for a few seconds | |
// 4. Mount the drive (click the link on the page) | |
// 5. Upload the file | |
// 6. Repeat for the other half | |
// 7. Clean up | |
const { exec } = require("child_process"); | |
const http = require("http"); | |
const url = require("url"); | |
const fs = require("fs"); | |
function errorCheck(err) { | |
if (err) { | |
console.log(`error: ${err.message}`); | |
process.exit(1); | |
} | |
} | |
function fetchArtifact(cb) { | |
exec( | |
"gh api repos/agzam/Adv360-Pro-ZMK/actions/artifacts", | |
(err, stdout, stderr) => { | |
if (err) { | |
console.log(`error: ${err.message}`); | |
return; | |
} | |
try { | |
const result = JSON.parse(stdout); | |
return cb(result.artifacts[0]); | |
} catch (err) { | |
errorCheck(err); | |
} | |
}, | |
); | |
} | |
function getPaths(dir) { | |
let files = fs | |
.readdirSync(dir) | |
.filter((f) => f !== "firmware.zip") | |
.map((f) => `${dir}/${f}`); | |
let left = files.filter((f) => f.includes("-left.uf2"))[0]; | |
let right = files.filter((f) => f.includes("-right.uf2"))[0]; | |
return { | |
left, | |
right, | |
}; | |
} | |
function home(req, res) { | |
fetchArtifact((artifact) => { | |
res.writeHead(200, { "Content-Type": "text/html" }); | |
const queryObj = url.parse(req.url, true).query; | |
const dir = decodeURIComponent(queryObj.download_directory); | |
let isDir = dir !== undefined && dir !== "" && dir !== "undefined"; | |
let pointer = isDir ? "pointer" : "none"; | |
let files = isDir ? getPaths(dir) : {}; | |
res.end( | |
` | |
<h2>Update Advantage 360 Firmware<h2/> | |
<a href="download?url=${artifact.archive_download_url}"> | |
Download Firmware files for: ${artifact.updated_at} | |
<a/> | |
<br> | |
<a href="mount_bootloader?download_directory=${dir}" | |
style="pointer-events: ${pointer}"> | |
Mount Bootloader Drive | |
<a/> | |
<br> | |
<a href="/update?file=${encodeURIComponent(files.left)}&download_directory=${dir}" | |
style="pointer-events: ${pointer}" | |
>Upload Left Half ${files.left} | |
</a> | |
<br> | |
<a href="/update?file=${encodeURIComponent(files.right)}&download_directory=${dir}" | |
style="pointer-events: ${pointer}" | |
>Upload Right Half ${files.right} | |
</a> | |
<br> | |
<a href="cleanup?download_directory=${dir}"> Cleanup <a/>`, | |
); | |
}); | |
} | |
function download(req, res) { | |
let queryObj = url.parse(req.url, true).query; | |
let downloadUrl = queryObj.url; | |
exec( | |
`tmp_dir=$(mktemp -d -t ci-XXXXXXXXXX) && \ | |
gh api -X GET ${downloadUrl} > $tmp_dir/firmware.zip | |
unzip $tmp_dir/firmware.zip -d $tmp_dir && echo $tmp_dir`, | |
(err, stdout, stderr) => { | |
errorCheck(err); | |
let dir = encodeURIComponent( | |
stdout | |
.split("\n") | |
.filter((x) => x !== "") | |
.slice(-1), | |
); | |
res.writeHead(302, { Location: `/?download_directory=${dir}` }); | |
res.end(); | |
}, | |
); | |
} | |
function mountBootloader(req, res) { | |
console.log("mounting the bootloader drive"); | |
const queryObj = url.parse(req.url, true).query; | |
const dir = decodeURIComponent(queryObj.download_directory); | |
exec( | |
` | |
mkdir -p $HOME/media/ADVKBD && | |
pass=$(gpg2 -qd ~/.authinfo.gpg 2> /dev/null | grep 'machine arch-machina login ag' | awk '{print $NF}') && | |
echo $pass | sudo -S mount /dev/sdd $HOME/media/ADVKBD/ | |
`, | |
(err, stdout, stderr) => { | |
errorCheck(err); | |
console.log(stdout); | |
console.log("drive mounted"); | |
}, | |
); | |
res.writeHead(302, { Location: `/?download_directory=${dir}` }); | |
res.end(); | |
} | |
function update(req, res) { | |
const queryObj = url.parse(req.url, true).query; | |
const file = decodeURIComponent(queryObj.file); | |
const dir = queryObj.download_directory; | |
exec(` | |
pass=$(gpg2 -qd ~/.authinfo.gpg 2> /dev/null | grep 'machine arch-machina login ag' | awk '{print $NF}') && | |
echo $pass | sudo -S cp ${file} $HOME/media/ADVKBD/ | |
`, (err, stdout, stderr) => { | |
errorCheck(err); | |
console.log(stdout); | |
}); | |
res.writeHead(302, { Location: `/?download_directory=${dir}` }); | |
res.end(); | |
} | |
function cleanup(req, res) { | |
const queryObj = url.parse(req.url, true).query; | |
const dir = decodeURIComponent(queryObj.download_directory); | |
exec(` | |
rm -rf ${dir} && | |
pass=$(gpg2 -qd ~/.authinfo.gpg 2> /dev/null | grep 'machine arch-machina login ag' | awk '{print $NF}') && | |
echo $pass | sudo -S umount -R $HOME/media/ADVKBD && | |
rm -rf $HOME/media/ADVKBD/ | |
`, (err, stdout, stderr) => { | |
errorCheck(err); | |
console.log(stdout); | |
}); | |
res.writeHead(302, { Location: `/` }); | |
res.end(); | |
} | |
const routes = { | |
"/": home, | |
"/download": download, | |
"/mount_bootloader": mountBootloader, | |
"/update": update, | |
"/cleanup": cleanup, | |
}; | |
const server = http | |
.createServer((req, res) => { | |
let routeFn = routes[url.parse(req.url).pathname]; | |
if (routeFn) { | |
routeFn(req, res); | |
} | |
}) | |
.listen(3000, () => exec("xdg-open http://localhost:3000")); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment