feat: real release notes, restart button, reliable .run installer (0.0.10)
release / release (push) Successful in 14s

- feat(ci): set each Gitea release body from the matching CHANGELOG section
  (was hardcoded "Automated release for…")
- feat(updater): show "What's new" — release notes dialog before applying (GUI)
  and in `rigdoctor update` (CLI); fetch_latest/update_state now return notes
- feat(gui): "Restart now" button relaunches the app after an update is applied
- fix(packaging): build the self-extracting .run with a pure-Python extractor
  (packaging/make_run.py) instead of makeself, so it attaches to every release
  (it was silently skipped before)
- chore: adopt Conventional Commits + git-cliff (cliff.toml, packaging/
  changelog.sh) for changelog generation going forward (D20)
- chore(gui): drop internal module refs (M4, M5, …) from Setup descriptions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 18:31:28 +02:00
parent ca4bc4c64f
commit f3021c4ddb
15 changed files with 240 additions and 68 deletions
+26 -4
View File
@@ -28,9 +28,7 @@ jobs:
python -m build python -m build
- name: Build self-extracting installer (.run) - name: Build self-extracting installer (.run)
run: | run: python packaging/make_run.py
(apt-get update && apt-get install -y makeself && sh packaging/make-run.sh) \
|| echo "makeself unavailable — skipping .run"
- name: Read version - name: Read version
id: ver id: ver
@@ -38,6 +36,30 @@ jobs:
V=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") V=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
echo "version=$V" >> "$GITHUB_OUTPUT" echo "version=$V" >> "$GITHUB_OUTPUT"
- name: Build release notes
run: |
python - <<'PY'
import json
version = "${{ steps.ver.outputs.version }}"
tag = f"v{version}"
out, capturing = [], False
try:
for line in open("CHANGELOG.md", encoding="utf-8").read().splitlines():
if line.startswith("## "):
if capturing:
break
capturing = line.startswith(f"## [{version}]")
continue
if capturing:
out.append(line)
except OSError:
pass
body = "\n".join(out).strip() or f"Release {tag}."
payload = {"tag_name": tag, "target_commitish": "${{ github.sha }}", "name": tag, "body": body}
open("/tmp/release.json", "w", encoding="utf-8").write(json.dumps(payload))
print(f"release notes: {len(body)} chars")
PY
- name: Publish Gitea release - name: Publish Gitea release
env: env:
TOKEN: ${{ secrets.GITHUB_TOKEN }} TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -57,7 +79,7 @@ jobs:
rid=$(curl -sS -X POST \ rid=$(curl -sS -X POST \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${{ github.sha }}\",\"name\":\"${TAG}\",\"body\":\"Automated release for ${TAG}. See CHANGELOG.md.\"}" \ -d @/tmp/release.json \
"${API}/releases" | python -c "import sys, json; print(json.load(sys.stdin)['id'])") "${API}/releases" | python -c "import sys, json; print(json.load(sys.stdin)['id'])")
for f in dist/*; do for f in dist/*; do
+17
View File
@@ -5,6 +5,23 @@ All notable changes to RigDoctor are recorded here. Format follows
(`MAJOR.MINOR.PATCH`, pre-1.0). `__version__` and `pyproject.toml` must match the git (`MAJOR.MINOR.PATCH`, pre-1.0). `__version__` and `pyproject.toml` must match the git
release tag (so the auto-updater, D18, can compare versions). release tag (so the auto-updater, D18, can compare versions).
## [0.0.10] - 2026-05-21
### Added
- **"Restart now" button** after a successful in-app update — relaunches RigDoctor for you
instead of asking you to restart manually.
- **Real release notes**: CI now sets each Gitea release's body from the matching CHANGELOG
section (instead of "Automated release for…"), and the updater shows **"What's new"** — a
notes dialog before applying (GUI) and in `rigdoctor update` (CLI).
### Changed
- Setup page / `rigdoctor install`: dropped internal module references (M4, M5, …) from the
component descriptions — end users don't need them.
- Adopting **Conventional Commits** + **git-cliff** (`cliff.toml`, `packaging/changelog.sh`)
to generate CHANGELOG entries from commit history going forward (D20).
### Fixed
- The self-extracting **`.run` installer** is now built **without makeself** (a pure-Python
self-extractor, `packaging/make_run.py`), so it reliably attaches to every release — it was
silently skipped before because the CI runner couldn't install makeself.
## [0.0.8] - 2026-05-21 ## [0.0.8] - 2026-05-21
### Added ### Added
- **Periodic update checks**: the GUI now re-checks for new releases while running (every - **Periodic update checks**: the GUI now re-checks for new releases while running (every
+37
View File
@@ -0,0 +1,37 @@
# git-cliff configuration — generate CHANGELOG.md from Conventional Commits (D20).
# Run via packaging/changelog.sh.
[changelog]
header = """
# Changelog
All notable changes to RigDoctor are recorded here. Format follows
[Keep a Changelog](https://keepachangelog.com/); versioning is SemVer-style
(`MAJOR.MINOR.PATCH`, pre-1.0). `__version__` and `pyproject.toml` must match the git
release tag (so the auto-updater, D18, can compare versions).
"""
body = """
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}\
- {{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = false
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^docs", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Changed" },
{ message = "^chore\\(release\\)", skip = true },
{ message = "^chore|^build|^ci|^style|^test", group = "Internal" },
{ message = ".*", group = "Other" },
]
tag_pattern = "v[0-9]*"
sort_commits = "oldest"
+14 -2
View File
@@ -191,12 +191,24 @@ desirable — access control is delegated to Gitea.
PATCH for ordinary changes, MINOR for larger milestones). `__version__` PATCH for ordinary changes, MINOR for larger milestones). `__version__`
(`rigdoctor/__init__.py`) and `pyproject.toml` are the single source of truth and **must match (`rigdoctor/__init__.py`) and `pyproject.toml` are the single source of truth and **must match
the git release tag** so the auto-updater (D18) can compare versions. Every change updates the git release tag** so the auto-updater (D18) can compare versions. Every change updates
`CHANGELOG.md` (Keep a Changelog style). *Note:* an early placeholder `0.1.0` was corrected to `CHANGELOG.md` — now generated from **Conventional Commits** via git-cliff (see D20). *Note:* an early placeholder `0.1.0` was corrected to
follow the released **0.0.x** line — first release was **V0.0.1**; current is **0.0.2**. follow the released **0.0.x** line — first release was **V0.0.1**; current is **0.0.2**.
### D20 — Automated changelog & release notes — *DECIDED 2026-05-21*
**Release notes are generated from our changes, surfaced in the auto-updater.**
- *Release body:* CI sets each Gitea release's `body` from the matching `CHANGELOG.md`
section (was a hardcoded "Automated release for…"). The updater fetches the release `body`
and shows **"What's new"** — a dialog before applying (GUI) and in `rigdoctor update` (CLI).
- *Generation:* adopt **Conventional Commits** (`feat:`/`fix:`/`docs:`/`chore:` …) and
**git-cliff** (`cliff.toml`, `packaging/changelog.sh`) to generate `CHANGELOG.md` from
commit history. Refines D19's "hand-write CHANGELOG" to "generate it from conventional
commits"; `__version__`/`pyproject.toml`/tag still the source of truth for the version.
- *CI does not auto-commit the changelog* (avoids push loops) — it's regenerated by the dev
via the script when cutting a version; CI only reads the section for the release body.
## Open ## Open
None currently — all tracked decisions (D1D19) are resolved. New questions will be added None currently — all tracked decisions (D1D20) are resolved. New questions will be added
here as they arise. Remaining detail to flesh out during build: the tray's supporting-action here as they arise. Remaining detail to flesh out during build: the tray's supporting-action
set (D13), per-module apt package names, M12's tunnel/token specifics, and M13's set (D13), per-module apt package names, M12's tunnel/token specifics, and M13's
update mechanism (APT repo vs. self-installed `.deb`). update mechanism (APT repo vs. self-installed `.deb`).
+2 -1
View File
@@ -59,7 +59,8 @@ Status: ⬜ not started · 🟦 designing · 🟨 in progress · ✅ done
and dependency install via pkexec/sudo — `rigdoctor install [--check] [-y]` + GUI Setup tab. and dependency install via pkexec/sudo — `rigdoctor install [--check] [-y]` + GUI Setup tab.
The **user-local app install** is `install.sh` (private venv + `~/.local/bin` launchers + The **user-local app install** is `install.sh` (private venv + `~/.local/bin` launchers +
desktop entry, no root; handles the `python3-venv` prerequisite) plus a self-extracting desktop entry, no root; handles the `python3-venv` prerequisite) plus a self-extracting
**`.run`** (makeself, built by CI). *Pending:* config/module selection + `systemd --user` **`.run`** (pure-Python self-extractor, `packaging/make_run.py`, built by CI). *Pending:*
config/module selection + `systemd --user`
service enable. service enable.
- **M12 Session sharing / remote assist** (D16) — let a helper inspect a user's machine, in - **M12 Session sharing / remote assist** (D16) — let a helper inspect a user's machine, in
an escalating ladder: (1) **diagnostic bundle export** (inventory + recent log + report, an escalating ladder: (1) **diagnostic bundle export** (inventory + recent log + report,
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env sh
# Regenerate CHANGELOG.md from Conventional Commits using git-cliff (D20).
# Install once: pip install git-cliff (ships prebuilt binaries)
# Usage: packaging/changelog.sh [--tag vX.Y.Z]
set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT"
command -v git-cliff >/dev/null 2>&1 || {
echo "git-cliff not found. Install it: pip install git-cliff"
exit 1
}
if [ "${1:-}" = "--tag" ] && [ -n "${2:-}" ]; then
git-cliff --tag "$2" -o CHANGELOG.md
else
git-cliff -o CHANGELOG.md
fi
echo "Wrote CHANGELOG.md"
+2 -32
View File
@@ -1,33 +1,3 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Build a self-extracting .run installer: bundles the wheel + install.sh so a user # Build the self-extracting .run installer (delegates to make_run.py — no makeself).
# can download one file, run it, and get a no-root user-local install. exec python3 "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/make_run.py" "$@"
#
# Requires `makeself` (apt install makeself). Run from the repo root.
set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT"
command -v makeself >/dev/null 2>&1 || {
echo "makeself not found. Install it: sudo apt install makeself"
exit 1
}
VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
mkdir -p dist
# Build the wheel if it isn't already in dist/.
if ! ls dist/rigdoctor-"$VERSION"-py3-none-any.whl >/dev/null 2>&1; then
python3 -m build --wheel
fi
STAGE=$(mktemp -d)
cp dist/rigdoctor-"$VERSION"-py3-none-any.whl "$STAGE"/
cp install.sh "$STAGE"/install.sh
chmod +x "$STAGE/install.sh"
OUT="dist/rigdoctor-$VERSION-installer.run"
makeself --notemp-suffix "$STAGE" "$OUT" "RigDoctor $VERSION installer" ./install.sh
rm -rf "$STAGE"
echo "Built $OUT"
echo "Run it with: chmod +x $OUT && ./$OUT"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Build a dependency-free self-extracting .run installer (no makeself).
Produces dist/rigdoctor-<version>-installer.run: a POSIX shell stub with an appended
tar.gz of the wheel + install.sh. Running it extracts to a temp dir and runs install.sh.
"""
from __future__ import annotations
import io
import os
import subprocess
import sys
import tarfile
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
MARKER = "__RIGDOCTOR_ARCHIVE__"
STUB = f"""#!/bin/sh
# RigDoctor self-extracting installer. Extracts the embedded archive and runs install.sh.
set -eu
SKIP=$(awk '/^{MARKER}$/ {{ print NR + 1; exit 0 }}' "$0")
TMP=$(mktemp -d)
tail -n +"$SKIP" "$0" | tar -xz -C "$TMP"
sh "$TMP/install.sh" "$@"
RET=$?
rm -rf "$TMP"
exit $RET
{MARKER}
"""
def main() -> int:
version = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["version"]
dist = ROOT / "dist"
dist.mkdir(exist_ok=True)
wheel = dist / f"rigdoctor-{version}-py3-none-any.whl"
if not wheel.exists():
subprocess.run([sys.executable, "-m", "build", "--wheel"], cwd=ROOT, check=True)
if not wheel.exists():
print(f"wheel not found: {wheel}", file=sys.stderr)
return 1
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
tar.add(wheel, arcname=wheel.name)
tar.add(ROOT / "install.sh", arcname="install.sh")
out = dist / f"rigdoctor-{version}-installer.run"
with open(out, "wb") as f:
f.write(STUB.encode())
f.write(buf.getvalue())
os.chmod(out, 0o755)
print(f"Built {out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "rigdoctor" name = "rigdoctor"
version = "0.0.8" version = "0.0.10"
description = "Modular hardware monitoring & crash diagnostics for Linux gamers." description = "Modular hardware monitoring & crash diagnostics for Linux gamers."
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
+1 -1
View File
@@ -1,3 +1,3 @@
"""RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers.""" """RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers."""
__version__ = "0.0.8" __version__ = "0.0.10"
+4 -2
View File
@@ -228,7 +228,7 @@ def cmd_login(args) -> int:
print("No token provided.") print("No token provided.")
return 1 return 1
config.save_token(token) config.save_token(token)
state, tag = updates.update_state() state, tag, _notes = updates.update_state()
if state == updates.AUTH: if state == updates.AUTH:
print("Token saved, but the server rejected it (check scope/permissions).") print("Token saved, but the server rejected it (check scope/permissions).")
return 1 return 1
@@ -248,7 +248,7 @@ def cmd_logout(args) -> int:
def cmd_update(args) -> int: def cmd_update(args) -> int:
from .core import updates from .core import updates
state, tag = updates.update_state() state, tag, notes = updates.update_state()
if state == updates.NO_TOKEN: if state == updates.NO_TOKEN:
print("No update token. Run `rigdoctor login` after creating one at:") print("No update token. Run `rigdoctor login` after creating one at:")
print(f" {updates.TOKEN_PAGE}") print(f" {updates.TOKEN_PAGE}")
@@ -264,6 +264,8 @@ def cmd_update(args) -> int:
return 0 return 0
# AVAILABLE # AVAILABLE
print(f"Update available: {tag} (current v{__version__}).") print(f"Update available: {tag} (current v{__version__}).")
if notes:
print("\nWhat's new:\n" + "\n".join(" " + ln for ln in notes.splitlines()) + "\n")
if args.check: if args.check:
return 0 return 0
print(f"Installing {tag}") print(f"Installing {tag}")
+4 -4
View File
@@ -23,7 +23,7 @@ class Component:
COMPONENTS: tuple[Component, ...] = ( COMPONENTS: tuple[Component, ...] = (
Component( Component(
"smartmontools", "SMART disk health", "Diagnostics", "smartmontools", "SMART disk health", "Diagnostics",
"Disk health (SMART) in the health report (M4)", ("smartmontools",), "smartctl", "Disk health (SMART) in the health report", ("smartmontools",), "smartctl",
), ),
Component( Component(
"lm-sensors", "lm-sensors", "Diagnostics", "lm-sensors", "lm-sensors", "Diagnostics",
@@ -31,7 +31,7 @@ COMPONENTS: tuple[Component, ...] = (
), ),
Component( Component(
"dmidecode", "dmidecode", "Diagnostics", "dmidecode", "dmidecode", "Diagnostics",
"Motherboard / BIOS / RAM details for system inventory (M5)", ("dmidecode",), "dmidecode", "Motherboard / BIOS / RAM details for system inventory", ("dmidecode",), "dmidecode",
), ),
Component( Component(
"pciutils", "pciutils", "Diagnostics", "pciutils", "pciutils", "Diagnostics",
@@ -39,10 +39,10 @@ COMPONENTS: tuple[Component, ...] = (
), ),
Component( Component(
"libnotify", "Desktop notifications", "Monitoring", "libnotify", "Desktop notifications", "Monitoring",
"Desktop alert notifications (M8)", ("libnotify-bin",), "notify-send", "Desktop alert notifications", ("libnotify-bin",), "notify-send",
), ),
Component( Component(
"libsecret", "Encrypted token storage", "Updates", "libsecret", "Encrypted token storage", "Updates",
"Store the update token in the OS keyring, encrypted (M13)", ("libsecret-tools",), "secret-tool", "Store the update token in the OS keyring, encrypted", ("libsecret-tools",), "secret-tool",
), ),
) )
+17 -17
View File
@@ -1,9 +1,9 @@
"""Update check (M13): ask the Gitea releases API for the latest version. """Update check (M13): ask the Gitea releases API for the latest version + notes.
Stdlib-only (urllib). The Gitea instance requires sign-in, so updates are gated to Stdlib-only (urllib). The Gitea instance requires sign-in, so updates are gated to
account holders via a Personal Access Token (D18): set $RIGDOCTOR_TOKEN or save one account holders via a Personal Access Token (D18): set $RIGDOCTOR_TOKEN or save one
with `rigdoctor login`. Self-update (apply) is built on top of this; this module with `rigdoctor login`. Returns the latest tag, its release notes (body), and a clear
handles detection and exposes a clear state for the UI. state for the UI; `apply_update` performs the no-root self-update.
""" """
from __future__ import annotations from __future__ import annotations
@@ -42,11 +42,11 @@ def is_newer(latest: str, current: str = __version__) -> bool:
return False return False
def fetch_latest(timeout: float = 5.0) -> tuple[str | None, str | None]: def fetch_latest(timeout: float = 5.0) -> tuple[str | None, str, str | None]:
"""Return (tag, error). error is one of NO_TOKEN / AUTH / NETWORK, or None on success.""" """Return (tag, notes, error). error is NO_TOKEN/AUTH/NETWORK, or None on success."""
token = load_token() token = load_token()
if not token: if not token:
return (None, NO_TOKEN) return (None, "", NO_TOKEN)
req = urllib.request.Request( req = urllib.request.Request(
LATEST_API, LATEST_API,
headers={"Accept": "application/json", "Authorization": f"token {token}"}, headers={"Accept": "application/json", "Authorization": f"token {token}"},
@@ -54,27 +54,27 @@ def fetch_latest(timeout: float = 5.0) -> tuple[str | None, str | None]:
try: try:
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310 (https) with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310 (https)
data = json.load(resp) data = json.load(resp)
return (data.get("tag_name") or None, None) return (data.get("tag_name") or None, (data.get("body") or "").strip(), None)
except urllib.error.HTTPError as exc: except urllib.error.HTTPError as exc:
return (None, AUTH if exc.code in (401, 403) else NETWORK) return (None, "", AUTH if exc.code in (401, 403) else NETWORK)
except Exception: except Exception:
return (None, NETWORK) return (None, "", NETWORK)
def check_latest(timeout: float = 5.0) -> str | None: def check_latest(timeout: float = 5.0) -> str | None:
"""Convenience: latest tag or None (ignores error reason).""" """Convenience: latest tag or None (ignores notes/error)."""
tag, _ = fetch_latest(timeout) tag, _notes, _error = fetch_latest(timeout)
return tag return tag
def update_state(timeout: float = 5.0) -> tuple[str, str | None]: def update_state(timeout: float = 5.0) -> tuple[str, str | None, str]:
"""Return (state, tag). state in NO_TOKEN/AUTH/NETWORK/UP_TO_DATE/AVAILABLE.""" """Return (state, tag, notes). state in NO_TOKEN/AUTH/NETWORK/UP_TO_DATE/AVAILABLE."""
tag, error = fetch_latest(timeout) tag, notes, error = fetch_latest(timeout)
if error: if error:
return (error, None) return (error, None, "")
if tag and is_newer(tag): if tag and is_newer(tag):
return (AVAILABLE, tag) return (AVAILABLE, tag, notes)
return (UP_TO_DATE, tag) return (UP_TO_DATE, tag, notes)
def apply_update(tag: str) -> tuple[int, str]: def apply_update(tag: str) -> tuple[int, str]:
+32 -3
View File
@@ -2,15 +2,19 @@
from __future__ import annotations from __future__ import annotations
import os
import sys
import threading import threading
from PySide6.QtCore import Qt, QTimer, Signal from PySide6.QtCore import Qt, QProcess, QTimer, Signal
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication,
QButtonGroup, QButtonGroup,
QFrame, QFrame,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QMainWindow, QMainWindow,
QMessageBox,
QPushButton, QPushButton,
QStackedWidget, QStackedWidget,
QVBoxLayout, QVBoxLayout,
@@ -75,6 +79,7 @@ class MainWindow(QMainWindow):
# Update check (M13): once at launch, then periodically so a newly published # Update check (M13): once at launch, then periodically so a newly published
# release is detected without restarting (interval from config; 0 disables). # release is detected without restarting (interval from config; 0 disables).
self._latest_tag = None self._latest_tag = None
self._latest_notes = ""
self._applied = False self._applied = False
self._update_checked.connect(self._show_update_state) self._update_checked.connect(self._show_update_state)
self._update_applied.connect(self._on_update_applied) self._update_applied.connect(self._on_update_applied)
@@ -131,11 +136,33 @@ class MainWindow(QMainWindow):
self._update_btn.clicked.connect(self._apply_update) self._update_btn.clicked.connect(self._apply_update)
self._update_btn.setVisible(False) self._update_btn.setVisible(False)
v.addWidget(self._update_btn) v.addWidget(self._update_btn)
self._restart_btn = QPushButton("Restart now")
self._restart_btn.setObjectName("PrimaryButton")
self._restart_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self._restart_btn.clicked.connect(self._restart)
self._restart_btn.setVisible(False)
v.addWidget(self._restart_btn)
return bar return bar
def _restart(self) -> None:
gui = os.path.join(os.path.dirname(sys.executable), "rigdoctor-gui")
if os.path.exists(gui):
QProcess.startDetached(gui)
else: # dev / not installed next to python
QProcess.startDetached(sys.executable, sys.argv)
QApplication.instance().quit()
def _apply_update(self) -> None: def _apply_update(self) -> None:
if not self._latest_tag: if not self._latest_tag:
return return
box = QMessageBox(self)
box.setWindowTitle(f"Update to {self._latest_tag}")
box.setText(f"Update RigDoctor to {self._latest_tag}?")
box.setInformativeText(self._latest_notes or "(no release notes)")
box.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)
box.button(QMessageBox.StandardButton.Ok).setText("Update")
if box.exec() != QMessageBox.StandardButton.Ok:
return
self._update_btn.setEnabled(False) self._update_btn.setEnabled(False)
self._update_label.setText("updating…") self._update_label.setText("updating…")
tag = self._latest_tag tag = self._latest_tag
@@ -144,8 +171,9 @@ class MainWindow(QMainWindow):
def _on_update_applied(self, rc: int) -> None: def _on_update_applied(self, rc: int) -> None:
if rc == 0: if rc == 0:
self._applied = True self._applied = True
self._update_label.setText("updated — restart RigDoctor") self._update_label.setText("update installed")
self._update_btn.setVisible(False) self._update_btn.setVisible(False)
self._restart_btn.setVisible(True)
if hasattr(self, "_update_timer"): if hasattr(self, "_update_timer"):
self._update_timer.stop() self._update_timer.stop()
else: else:
@@ -161,8 +189,9 @@ class MainWindow(QMainWindow):
def _show_update_state(self, result) -> None: def _show_update_state(self, result) -> None:
if self._applied: # an update was applied this session; awaiting restart if self._applied: # an update was applied this session; awaiting restart
return return
state, tag = result state, tag, notes = result
self._latest_tag = tag self._latest_tag = tag
self._latest_notes = notes
self._update_btn.setVisible(False) self._update_btn.setVisible(False)
if state == updates.NO_TOKEN: if state == updates.NO_TOKEN:
self._update_label.setText("connect to update server") self._update_label.setText("connect to update server")
+1 -1
View File
@@ -185,7 +185,7 @@ class SetupPage(QWidget):
self._upd_state.emit((config.token_backend(), updates.update_state())) self._upd_state.emit((config.token_backend(), updates.update_state()))
def _on_upd_state(self, result) -> None: def _on_upd_state(self, result) -> None:
backend, (state, tag) = result backend, (state, tag, _notes) = result
msg = { msg = {
updates.NO_TOKEN: "paste a token below to enable updates", updates.NO_TOKEN: "paste a token below to enable updates",
updates.AUTH: "token rejected — check its scope/permissions", updates.AUTH: "token rejected — check its scope/permissions",