Skip to content

Commit b9330e9

Browse files
author
Z User
committed
Merge branch 'feature/mobile-installer-and-appimage-freshness' — P0+P1 complete
2 parents cc10bc3 + 90ab847 commit b9330e9

6 files changed

Lines changed: 1389 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: AppImage Freshness Check
2+
3+
# ══════════════════════════════════════════════════════════════════
4+
# WARNING-ONLY job: detects when the AppImage needs rebuilding because
5+
# the 4 scanner_fixer modules (deskew, crop, normalize, dedup) or the
6+
# desktop GUI entry (medical_doc_gui_final.py) changed since the last
7+
# successful build recorded in packages/desktop/.last_build_commit.
8+
#
9+
# This job NEVER fails the workflow — it only surfaces a warning
10+
# annotation on the commit/PR so maintainers know a rebuild is needed.
11+
#
12+
# Triggers:
13+
# - push to main or feature/* (paths-filtered to the tracked files)
14+
# - pull_request targeting main (paths-filtered)
15+
# - manual workflow_dispatch
16+
#
17+
# Exit codes from check_appimage_freshness.py:
18+
# 0 = fresh (no rebuild needed)
19+
# 1 = stale (rebuild needed — surfaces warning annotation)
20+
# 2 = no_build (no previous .last_build_commit — informational only)
21+
# 3 = error (config problem — surfaces error annotation, but still
22+
# doesn't fail the job)
23+
# ══════════════════════════════════════════════════════════════════
24+
25+
on:
26+
push:
27+
branches:
28+
- main
29+
- 'feature/**'
30+
- 'feat/**'
31+
- 'fix/**'
32+
paths:
33+
# Only run when the tracked surface changes (or the script itself)
34+
- 'packages/scanner_fixer/src/scanner_fixer/deskew.py'
35+
- 'packages/scanner_fixer/src/scanner_fixer/crop.py'
36+
- 'packages/scanner_fixer/src/scanner_fixer/normalize.py'
37+
- 'packages/scanner_fixer/src/scanner_fixer/dedup.py'
38+
- 'packages/desktop/medical_doc_gui_final.py'
39+
- 'packages/desktop/build_appimage.sh'
40+
- 'packages/desktop/.last_build_commit'
41+
- 'scripts/check_appimage_freshness.py'
42+
- '.github/workflows/appimage-freshness.yml'
43+
pull_request:
44+
branches:
45+
- main
46+
paths:
47+
- 'packages/scanner_fixer/src/scanner_fixer/deskew.py'
48+
- 'packages/scanner_fixer/src/scanner_fixer/crop.py'
49+
- 'packages/scanner_fixer/src/scanner_fixer/normalize.py'
50+
- 'packages/scanner_fixer/src/scanner_fixer/dedup.py'
51+
- 'packages/desktop/medical_doc_gui_final.py'
52+
- 'packages/desktop/build_appimage.sh'
53+
- 'scripts/check_appimage_freshness.py'
54+
- '.github/workflows/appimage-freshness.yml'
55+
workflow_dispatch:
56+
57+
env:
58+
PYTHON_VERSION: "3.11"
59+
60+
jobs:
61+
freshness-check:
62+
name: Check AppImage Freshness (warning-only)
63+
runs-on: ubuntu-22.04
64+
timeout-minutes: 5
65+
66+
# Always allow this job to complete — even if some upstream step
67+
# sets a failure, we want the warning annotation, not a red X.
68+
continue-on-error: true
69+
70+
steps:
71+
- name: Checkout repo
72+
uses: actions/checkout@v4
73+
with:
74+
fetch-depth: 0 # full history so git log can find tracked-file commits
75+
76+
- name: Set up Python ${{ env.PYTHON_VERSION }}
77+
uses: actions/setup-python@v5
78+
with:
79+
python-version: ${{ env.PYTHON_VERSION }}
80+
81+
- name: Verify git + tracked files
82+
id: sanity
83+
run: |
84+
set -e
85+
echo "git version: $(git --version)"
86+
echo "HEAD: $(git rev-parse HEAD)"
87+
echo ""
88+
echo "Tracked files presence:"
89+
for f in \
90+
packages/scanner_fixer/src/scanner_fixer/deskew.py \
91+
packages/scanner_fixer/src/scanner_fixer/crop.py \
92+
packages/scanner_fixer/src/scanner_fixer/normalize.py \
93+
packages/scanner_fixer/src/scanner_fixer/dedup.py \
94+
packages/desktop/medical_doc_gui_final.py; do
95+
if [ -f "$f" ]; then
96+
echo " ✓ $f"
97+
else
98+
echo " ✗ $f MISSING"
99+
exit 3
100+
fi
101+
done
102+
103+
- name: Run freshness check
104+
id: check
105+
# Capture exit code without failing the step
106+
continue-on-error: true
107+
run: |
108+
set +e
109+
python3 scripts/check_appimage_freshness.py --json > /tmp/freshness.json 2>&1
110+
EXIT_CODE=$?
111+
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
112+
echo "── Freshness check output (exit $EXIT_CODE) ──"
113+
cat /tmp/freshness.json
114+
echo ""
115+
exit 0 # never fail this step
116+
117+
- name: Annotate result
118+
if: always()
119+
env:
120+
EXIT_CODE: ${{ steps.check.outputs.exit_code }}
121+
run: |
122+
set -e
123+
# Parse JSON output (jq is pre-installed on ubuntu-22.04 runners)
124+
STATUS=$(jq -r '.status' /tmp/freshness.json 2>/dev/null || echo "unknown")
125+
MESSAGE=$(jq -r '.message' /tmp/freshness.json 2>/dev/null || echo "(no message)")
126+
HINT=$(jq -r '.hint // ""' /tmp/freshness.json 2>/dev/null || echo "")
127+
LAST_BUILD=$(jq -r '.last_build_commit_short // "(none)"' /tmp/freshness.json 2>/dev/null)
128+
LATEST=$(jq -r '.latest_tracked_commit_short // "(none)"' /tmp/freshness.json 2>/dev/null)
129+
130+
echo "## AppImage Freshness: ${STATUS^^}" >> "$GITHUB_STEP_SUMMARY"
131+
echo "" >> "$GITHUB_STEP_SUMMARY"
132+
echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY"
133+
echo "|---|---|" >> "$GITHUB_STEP_SUMMARY"
134+
echo "| Status | \`${STATUS}\` |" >> "$GITHUB_STEP_SUMMARY"
135+
echo "| Last build commit | \`${LAST_BUILD}\` |" >> "$GITHUB_STEP_SUMMARY"
136+
echo "| Latest tracked commit | \`${LATEST}\` |" >> "$GITHUB_STEP_SUMMARY"
137+
echo "| Message | ${MESSAGE} |" >> "$GITHUB_STEP_SUMMARY"
138+
if [ -n "$HINT" ]; then
139+
echo "| Hint | ${HINT} |" >> "$GITHUB_STEP_SUMMARY"
140+
fi
141+
echo "" >> "$GITHUB_STEP_SUMMARY"
142+
echo "Exit code: \`${EXIT_CODE}\` (0=fresh, 1=stale, 2=no build, 3=error)" >> "$GITHUB_STEP_SUMMARY"
143+
144+
# Emit a GitHub Actions annotation visible on the commit/PR.
145+
# warning = yellow ⚠ ; error = red ✗ ; notice = blue ℹ
146+
case "$STATUS" in
147+
fresh)
148+
echo "::notice title=AppImage Freshness::${MESSAGE}"
149+
;;
150+
stale)
151+
echo "::warning title=AppImage is stale (rebuild needed)::${MESSAGE} ${HINT}"
152+
;;
153+
no_build)
154+
echo "::notice title=AppImage Freshness::${MESSAGE} ${HINT}"
155+
;;
156+
error)
157+
echo "::warning title=AppImage Freshness check error::${MESSAGE}"
158+
;;
159+
*)
160+
echo "::notice title=AppImage Freshness::Unknown status: ${STATUS}"
161+
;;
162+
esac
163+
164+
- name: Final result
165+
if: always()
166+
env:
167+
EXIT_CODE: ${{ steps.check.outputs.exit_code }}
168+
run: |
169+
echo "Freshness check completed with exit code: ${EXIT_CODE}"
170+
# Per workflow policy: NEVER fail the job.
171+
# Even exit_code=3 (config error) is surfaced as a warning,
172+
# not a failure — the AppImage build job (appimage-build.yml)
173+
# is the source of truth for hard failures.
174+
exit 0

‎.gitignore‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,11 @@ data/corrections.db
9999
data/active_learning.db
100100
data/backups/
101101
data/mobile_images/
102+
103+
# AppImage build artifacts (local per-machine state — not committed)
104+
# .last_build_commit is written by packages/desktop/build_appimage.sh
105+
# and read by scripts/check_appimage_freshness.py to detect stale builds.
106+
packages/desktop/.last_build_commit
107+
packages/desktop/AppDir/
108+
packages/desktop/*.AppImage
109+
packages/desktop/*.AppImage.sha256

‎packages/desktop/build_appimage.sh‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
# - Manjaro-specific appimagetool install via yay
1616
# - Optional signing (APPIMAGETOOL_SIGN_KEY env)
1717
#
18+
# Freshness tracking (v1.1.1):
19+
# - Writes .last_build_commit after successful build (Step 10b)
20+
# - Read by scripts/check_appimage_freshness.py to detect stale builds
21+
# - Tracked files: scanner_fixer/{deskew,crop,normalize,dedup}.py
22+
# + packages/desktop/medical_doc_gui_final.py
23+
#
1824
# Requirements:
1925
# - Python 3.10+
2026
# - PyInstaller (`pip install pyinstaller`)
@@ -377,6 +383,40 @@ echo "🔐 Generating SHA256 checksum..."
377383
sha256sum "${OUTPUT_NAME}" > "${OUTPUT_NAME}.sha256"
378384
echo " Checksum: $(cat "${OUTPUT_NAME}.sha256")"
379385

386+
# ── 10b. Record build commit (for freshness checking) ────────────
387+
# Write the current HEAD commit hash to .last_build_commit so that
388+
# scripts/check_appimage_freshness.py can detect when a rebuild is
389+
# needed. We record the *full* hash (40 chars) but the freshness
390+
# script handles short hashes too.
391+
#
392+
# Format:
393+
# <40-char hash> <ISO-8601 build date>
394+
# # built by build_appimage.sh v${VERSION}
395+
#
396+
# Only write after the AppImage + checksum are confirmed to exist on
397+
# disk, so a failed build never updates the freshness marker.
398+
LAST_BUILD_COMMIT_FILE="${SCRIPT_DIR}/.last_build_commit"
399+
if [ -f "${OUTPUT_NAME}" ] && [ -f "${OUTPUT_NAME}.sha256" ]; then
400+
if command -v git &>/dev/null && [ -d "${REPO_ROOT}/.git" ]; then
401+
FULL_COMMIT="$(cd "$REPO_ROOT" && git rev-parse HEAD 2>/dev/null || echo "")"
402+
if [ -n "$FULL_COMMIT" ]; then
403+
{
404+
echo "${FULL_COMMIT} ${BUILD_DATE}"
405+
echo "# built by build_appimage.sh v${VERSION}"
406+
echo "# AppImage: ${OUTPUT_NAME}"
407+
} > "$LAST_BUILD_COMMIT_FILE"
408+
echo "📌 Recorded build commit: ${FULL_COMMIT:0:7} → ${LAST_BUILD_COMMIT_FILE}"
409+
echo " Next freshness check: python3 scripts/check_appimage_freshness.py"
410+
else
411+
echo "⚠️ Could not resolve HEAD commit — .last_build_commit not updated"
412+
fi
413+
else
414+
echo "⚠️ git not available or not a repo — .last_build_commit not updated"
415+
fi
416+
else
417+
echo "⚠️ AppImage or checksum missing — .last_build_commit not updated"
418+
fi
419+
380420
# ── 11. Optional smoke test ───────────────────────────────────────
381421
if $RUN_SMOKE_TEST; then
382422
echo ""

0 commit comments

Comments
 (0)