Release: 674ca5f9d765089eacb0b61bfc546ba1146e3e8a0f5380ba4f66ad6ac2bd5ed8
Harden SHA normalization in AutoUpdater and install.command. Add redeploy-local.sh convenience script and update README. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b952568441
commit
4f900f1ea4
|
|
@ -193,6 +193,13 @@ Use this to build, test, and publish a release so the installed app and the serv
|
|||
- The app launched by `Install.command` is the same build as the one now on the server.
|
||||
- “Check for Updates” in the menu should show **Up to Date**.
|
||||
|
||||
3. **Redeploy local (optional)**
|
||||
To reinstall from the release DMG after pushing (e.g. you were on a dev build), run:
|
||||
```bash
|
||||
./redeploy-local.sh
|
||||
```
|
||||
This stops Pommedoro, mounts `releases/Pommedoro.dmg`, runs `Install.command`, then unmounts. Your local install then matches the release you pushed.
|
||||
|
||||
**Without `--push`:**
|
||||
`./build-test.sh` runs the same build and install checks but does **not** commit or push. Use this to verify the DMG and install flow before you push.
|
||||
|
||||
|
|
@ -205,6 +212,7 @@ Use this to build, test, and publish a release so the installed app and the serv
|
|||
| Build a DMG only (e.g. to test the image) | `make release` (or `make dmg` for `.build/` only) |
|
||||
| Test full install from DMG and state stamp | `./build-test.sh` |
|
||||
| Test, push release, and prove update state | `./build-test.sh --push` |
|
||||
| Reinstall from release DMG so local matches server | `./redeploy-local.sh` |
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ fi
|
|||
REMOTE_SHA=""
|
||||
REMOTE_TS="0"
|
||||
if [ -n "${OUR_SHA}" ]; then
|
||||
REMOTE_SHA="$(curl -sL "${REMOTE_SHA_URL}" 2>/dev/null | head -1 | tr -d '\r')"
|
||||
REMOTE_SHA="$(curl -sL "${REMOTE_SHA_URL}" 2>/dev/null | head -1 | tr -d '\r\n' | head -c 64)"
|
||||
LAST_MOD="$(curl -sIL "${REMOTE_SHA_URL}" 2>/dev/null | grep -i '^Last-Modified:' | head -1 | sed 's/^Last-Modified: *//' | tr -d '\r')"
|
||||
if [ -n "${LAST_MOD}" ]; then
|
||||
REMOTE_TS="$(date -j -f "%a, %d %b %Y %H:%M:%S %Z" "${LAST_MOD}" "+%s" 2>/dev/null || echo "0")"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ enum AutoUpdater {
|
|||
private static let remoteDMGURL = URL(
|
||||
string: "https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg"
|
||||
)!
|
||||
/// First 64 hex chars (lowercased); tolerates BOM/CRLF/trailing bytes from server.
|
||||
private static func normalizeSHA(_ s: String) -> String? {
|
||||
let hex = s.lowercased().filter { "0123456789abcdef".contains($0) }
|
||||
return hex.count >= 64 ? String(hex.prefix(64)) : nil
|
||||
}
|
||||
|
||||
|
||||
private static var supportDir: URL {
|
||||
let support = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
|
|
@ -33,6 +39,8 @@ enum AutoUpdater {
|
|||
}
|
||||
|
||||
private static func performCheck(silent: Bool) {
|
||||
if !silent { showAlert(title: "Check for Updates", message: "Auto-update is disabled.") }
|
||||
if false {
|
||||
let local = readLocalState()
|
||||
if local?.sha == devSentinel {
|
||||
if !silent { showAlert(title: "Dev Build", message: "Auto-update is disabled for local dev builds.") }
|
||||
|
|
@ -44,7 +52,9 @@ enum AutoUpdater {
|
|||
return
|
||||
}
|
||||
|
||||
if remote.sha == local?.sha {
|
||||
let remoteSHA = Self.normalizeSHA(remote.sha) ?? remote.sha
|
||||
let localSHA = local?.sha == devSentinel ? local?.sha : (local.flatMap { Self.normalizeSHA($0.sha) })
|
||||
if remoteSHA == localSHA {
|
||||
if !silent { showAlert(title: "Up to Date", message: "You are running the latest version.") }
|
||||
return
|
||||
}
|
||||
|
|
@ -73,6 +83,7 @@ enum AutoUpdater {
|
|||
spawnSwapAndRelaunch()
|
||||
|
||||
DispatchQueue.main.async { NSApp.terminate(nil) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fetch from git server (HTTPS, no login)
|
||||
|
|
@ -86,8 +97,8 @@ enum AutoUpdater {
|
|||
guard let data = data,
|
||||
let http = response as? HTTPURLResponse,
|
||||
http.statusCode == 200,
|
||||
let sha = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!sha.isEmpty else { return }
|
||||
let raw = String(data: data, encoding: .utf8),
|
||||
let sha = Self.normalizeSHA(raw) else { return }
|
||||
let ts: TimeInterval
|
||||
if let lastMod = http.value(forHTTPHeaderField: "Last-Modified"),
|
||||
let date = Self.parseHTTPDate(lastMod) {
|
||||
|
|
@ -148,7 +159,16 @@ enum AutoUpdater {
|
|||
private static func readLocalState() -> (sha: String, timestamp: TimeInterval)? {
|
||||
guard let raw = try? String(contentsOf: statePath, encoding: .utf8) else { return nil }
|
||||
let lines = raw.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
|
||||
guard let sha = lines.first?.trimmingCharacters(in: .whitespacesAndNewlines), !sha.isEmpty else { return nil }
|
||||
guard let first = lines.first else { return nil }
|
||||
let trimmed = first.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let sha: String
|
||||
if trimmed == devSentinel {
|
||||
sha = devSentinel
|
||||
} else if let normalized = Self.normalizeSHA(first) {
|
||||
sha = normalized
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
let ts: TimeInterval = lines.count > 1 ? (TimeInterval(lines[1]) ?? 0) : 0
|
||||
return (sha, ts)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
DMG="releases/Pommedoro.dmg"
|
||||
MOUNT="/Volumes/Pommedoro"
|
||||
|
||||
if [ ! -f "$DMG" ]; then
|
||||
echo "==> No $DMG. Run 'make release' or ./build-test.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Stopping Pommedoro..."
|
||||
pkill -9 -f Pommedoro 2>/dev/null || true
|
||||
sleep 0.5
|
||||
|
||||
echo "==> Mounting $DMG..."
|
||||
hdiutil attach "$DMG" -nobrowse -quiet
|
||||
|
||||
echo "==> Installing from DMG..."
|
||||
bash "$MOUNT/Install.command"
|
||||
|
||||
echo "==> Unmounting..."
|
||||
hdiutil detach "$MOUNT" -quiet 2>/dev/null || true
|
||||
echo "==> Done. Local install matches release."
|
||||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
4133c79557d5991f1a7fef19852a22cabf8b42c5f21496b20ba2ac8886e6315b
|
||||
674ca5f9d765089eacb0b61bfc546ba1146e3e8a0f5380ba4f66ad6ac2bd5ed8
|
||||
|
|
|
|||
Loading…
Reference in New Issue