Compare commits

..

6 Commits

Author SHA1 Message Date
bigjakk 13219f3a6c v0.6.3
Build and Release / build-and-release (push) Has been cancelled
2026-04-04 09:10:47 -07:00
bigjakk 0ca6802475 fix: clean up release notes for end users
Build and Release / build-and-release (push) Has been cancelled
2026-04-04 09:09:57 -07:00
bigjakk caf64447b8 add SHA-256 checksum verification to auto-updater
Build and Release / build-and-release (push) Has been cancelled
2026-04-04 09:06:41 -07:00
bigjakk 68344c6465 v0.6.2
Build and Release / build-and-release (push) Has started running
2026-04-04 09:03:55 -07:00
bigjakk 19af04468e fix: broaden updater allowed hosts to all GitHub domains
Build and Release / build-and-release (push) Has been cancelled
2026-04-04 09:03:20 -07:00
bigjakk c96c151851 fix: allow GitHub release asset redirects in auto-updater
Build and Release / build-and-release (push) Has been cancelled
2026-04-04 09:02:07 -07:00
4 changed files with 69 additions and 37 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "krunker-civilian-client", "name": "krunker-civilian-client",
"version": "0.6.1", "version": "0.6.3",
"description": "Cross-platform Krunker game client", "description": "Cross-platform Krunker game client",
"main": "dist/main/index.js", "main": "dist/main/index.js",
"homepage": "https://github.com/bigjakk/Krunker-Civilian-Client", "homepage": "https://github.com/bigjakk/Krunker-Civilian-Client",
+25 -29
View File
@@ -2,12 +2,10 @@
# #
# Generate Markdown release notes from conventional commits. # Generate Markdown release notes from conventional commits.
# Usage: ./scripts/generate-release-notes.sh <tag> [prev-ref] # Usage: ./scripts/generate-release-notes.sh <tag> [prev-ref]
# e.g. ./scripts/generate-release-notes.sh v0.7.0
# e.g. ./scripts/generate-release-notes.sh v0.7.0 abc123f
# #
# Skips version bumps, CI-only changes, and other noise.
# If prev-ref is not provided, tries git describe to find previous tag. # If prev-ref is not provided, tries git describe to find previous tag.
# If no previous ref is found, includes all commits up to HEAD. # If no previous ref is found, includes all commits up to HEAD.
# Uses HEAD as the endpoint (tag may not exist in git yet).
set -eo pipefail set -eo pipefail
@@ -21,32 +19,32 @@ fi
if [ -n "$PREV_REF" ]; then if [ -n "$PREV_REF" ]; then
RANGE="${PREV_REF}..HEAD" RANGE="${PREV_REF}..HEAD"
COMPARE_TEXT="**Full changelog**: \`${PREV_REF}...${TAG}\`"
else else
RANGE="HEAD" RANGE="HEAD"
COMPARE_TEXT="**Initial release**"
fi fi
# Collect commits into temp files by category # Collect commits into temp files by category
TMPDIR_NOTES=$(mktemp -d) TMPDIR_NOTES=$(mktemp -d)
trap 'rm -rf "$TMPDIR_NOTES"' EXIT trap 'rm -rf "$TMPDIR_NOTES"' EXIT
for prefix in feat fix refactor perf docs test chore other; do for prefix in feat fix refactor perf other; do
: > "${TMPDIR_NOTES}/${prefix}" : > "${TMPDIR_NOTES}/${prefix}"
done done
while IFS= read -r line; do while IFS= read -r line; do
[ -z "$line" ] && continue [ -z "$line" ] && continue
# Skip version bump commits (e.g. "v0.6.1", "v0.6.2 — description")
[[ "$line" =~ ^v[0-9] ]] && continue
# Skip chore/docs/test/ci commits — not user-facing
[[ "$line" =~ ^(chore|docs|test|ci)(\(.*\))?: ]] && continue
MATCHED=false MATCHED=false
for prefix in feat fix refactor perf docs test chore; do for prefix in feat fix refactor perf; do
if [[ "$line" =~ ^${prefix}(\(.*\))?:\ (.+)$ ]]; then if [[ "$line" =~ ^${prefix}(\(.*\))?:\ (.+)$ ]]; then
SCOPE="${BASH_REMATCH[1]}"
MSG="${BASH_REMATCH[2]}" MSG="${BASH_REMATCH[2]}"
if [ -n "$SCOPE" ]; then echo "- ${MSG}" >> "${TMPDIR_NOTES}/${prefix}"
echo "- **${SCOPE}**: ${MSG}" >> "${TMPDIR_NOTES}/${prefix}"
else
echo "- ${MSG}" >> "${TMPDIR_NOTES}/${prefix}"
fi
MATCHED=true MATCHED=true
break break
fi fi
@@ -59,36 +57,34 @@ done < <(git log --format="%s" "$RANGE" 2>/dev/null)
# Section display names # Section display names
section_title() { section_title() {
case "$1" in case "$1" in
feat) echo "Features" ;; feat) echo "## New" ;;
fix) echo "Bug Fixes" ;; fix) echo "## Fixes" ;;
refactor) echo "Refactoring" ;; refactor) echo "## Improvements" ;;
perf) echo "Performance" ;; perf) echo "## Performance" ;;
docs) echo "Documentation" ;;
test) echo "Tests" ;;
chore) echo "Chores" ;;
esac esac
} }
# Build output # Build output — only user-facing sections
echo "# KCC ${TAG}" HAS_CONTENT=false
echo ""
for prefix in feat fix refactor perf docs test chore; do for prefix in feat fix refactor perf; do
if [ -s "${TMPDIR_NOTES}/${prefix}" ]; then if [ -s "${TMPDIR_NOTES}/${prefix}" ]; then
echo "## $(section_title "$prefix")" section_title "$prefix"
echo "" echo ""
cat "${TMPDIR_NOTES}/${prefix}" cat "${TMPDIR_NOTES}/${prefix}"
echo "" echo ""
HAS_CONTENT=true
fi fi
done done
if [ -s "${TMPDIR_NOTES}/other" ]; then if [ -s "${TMPDIR_NOTES}/other" ]; then
echo "## Other Changes" echo "## Other"
echo "" echo ""
cat "${TMPDIR_NOTES}/other" cat "${TMPDIR_NOTES}/other"
echo "" echo ""
HAS_CONTENT=true
fi fi
echo "---" if [ "$HAS_CONTENT" = false ]; then
echo "" echo "Bug fixes and improvements."
echo "${COMPARE_TEXT}" fi
+1 -1
View File
@@ -192,7 +192,7 @@ app.whenReady().then(async () => {
if (!cancelled && !updateWin.isDestroyed()) { if (!cancelled && !updateWin.isDestroyed()) {
sendProgress(`Downloading update... ${pct}%`, pct); sendProgress(`Downloading update... ${pct}%`, pct);
} }
}); }, update.sha256);
if (!cancelled) { if (!cancelled) {
sendProgress('Installing update...', 100); sendProgress('Installing update...', 100);
+42 -6
View File
@@ -1,5 +1,6 @@
import { get as httpsGet } from 'https'; import { get as httpsGet } from 'https';
import { createWriteStream, renameSync, unlinkSync, existsSync } from 'fs'; import { createReadStream, createWriteStream, renameSync, unlinkSync, existsSync } from 'fs';
import { createHash } from 'crypto';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { app } from 'electron'; import { app } from 'electron';
import { electronLog } from './logger'; import { electronLog } from './logger';
@@ -8,6 +9,7 @@ export interface UpdateInfo {
version: string; version: string;
downloadUrl: string; downloadUrl: string;
fileSize: number; fileSize: number;
sha256: string;
} }
export type ProgressCallback = (percent: number) => void; export type ProgressCallback = (percent: number) => void;
@@ -15,7 +17,7 @@ export type ProgressCallback = (percent: number) => void;
const UPDATE_CONFIG = { const UPDATE_CONFIG = {
checkUrl: 'https://api.github.com/repos/bigjakk/Krunker-Civilian-Client/releases/latest', checkUrl: 'https://api.github.com/repos/bigjakk/Krunker-Civilian-Client/releases/latest',
assetPattern: /Setup\.exe$/i, assetPattern: /Setup\.exe$/i,
allowedHosts: ['github.com', 'api.github.com', 'objects.githubusercontent.com'], allowedHosts: ['github.com', 'githubusercontent.com'],
}; };
const CHECK_TIMEOUT_MS = 10000; const CHECK_TIMEOUT_MS = 10000;
@@ -104,7 +106,7 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
return; return;
} }
const assets: Array<{ name: string; browser_download_url: string; size: number }> = release.assets || []; const assets: Array<{ name: string; browser_download_url: string; size: number; digest: string }> = release.assets || [];
const setupAsset = assets.find((a) => UPDATE_CONFIG.assetPattern.test(a.name)); const setupAsset = assets.find((a) => UPDATE_CONFIG.assetPattern.test(a.name));
if (!setupAsset) { if (!setupAsset) {
electronLog.error('[KCC-Update] No Setup.exe asset found in release', remoteVersion); electronLog.error('[KCC-Update] No Setup.exe asset found in release', remoteVersion);
@@ -119,11 +121,20 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
return; return;
} }
electronLog.log('[KCC-Update] Update available:', remoteVersion, '| Download:', setupAsset.browser_download_url, '| Size:', setupAsset.size); // Extract SHA-256 digest from GitHub API (format: "sha256:<hex>")
const sha256 = (setupAsset.digest || '').replace(/^sha256:/i, '');
if (!sha256) {
electronLog.error('[KCC-Update] No SHA-256 digest found for asset');
resolve(null);
return;
}
electronLog.log('[KCC-Update] Update available:', remoteVersion, '| SHA-256:', sha256.substring(0, 16) + '...');
resolve({ resolve({
version: remoteVersion, version: remoteVersion,
downloadUrl: setupAsset.browser_download_url, downloadUrl: setupAsset.browser_download_url,
fileSize: setupAsset.size, fileSize: setupAsset.size,
sha256,
}); });
} catch (err) { } catch (err) {
electronLog.error('[KCC-Update] Failed to parse release data:', err); electronLog.error('[KCC-Update] Failed to parse release data:', err);
@@ -149,7 +160,22 @@ export function checkForUpdate(currentVersion: string): Promise<UpdateInfo | nul
}); });
} }
export function downloadUpdate(url: string, destPath: string, onProgress: ProgressCallback): Promise<void> { function verifyChecksum(filePath: string, expectedSha256: string): Promise<boolean> {
return new Promise((resolve, reject) => {
const hash = createHash('sha256');
const stream = createReadStream(filePath);
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => {
const actual = hash.digest('hex');
electronLog.log('[KCC-Update] SHA-256 expected:', expectedSha256);
electronLog.log('[KCC-Update] SHA-256 actual: ', actual);
resolve(actual === expectedSha256);
});
stream.on('error', reject);
});
}
export function downloadUpdate(url: string, destPath: string, onProgress: ProgressCallback, expectedSha256?: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const tmpPath = destPath + '.tmp'; const tmpPath = destPath + '.tmp';
@@ -194,8 +220,18 @@ export function downloadUpdate(url: string, destPath: string, onProgress: Progre
res.pipe(file); res.pipe(file);
file.on('finish', () => { file.on('finish', () => {
file.close(() => { file.close(async () => {
try { try {
if (expectedSha256) {
const valid = await verifyChecksum(tmpPath, expectedSha256);
if (!valid) {
electronLog.error('[KCC-Update] Checksum mismatch — file may be corrupted or tampered');
try { unlinkSync(tmpPath); } catch { /* ignore */ }
reject(new Error('SHA-256 checksum mismatch'));
return;
}
electronLog.log('[KCC-Update] Checksum verified');
}
if (existsSync(destPath)) unlinkSync(destPath); if (existsSync(destPath)) unlinkSync(destPath);
renameSync(tmpPath, destPath); renameSync(tmpPath, destPath);
resolve(); resolve();