Release 0.0.7: user-local installer + self-update apply
release / release (push) Successful in 22s

- install.sh: no-root user-local install (private venv + ~/.local/bin launchers
  + desktop entry); --ref <tag> to install a specific release, --uninstall to
  remove; auto-installs the python3-venv prerequisite with consent
- packaging/make-run.sh: build a self-extracting .run installer (makeself)
  bundling the wheel + install.sh; release workflow builds and attaches it
- M13 self-update apply: `rigdoctor update` runs an authenticated pip upgrade
  (rigdoctor[gui] @ git+https://oauth2:<token>@...@<tag>), token scrubbed; GUI
  sidebar "Update to v…" button applies it and prompts to restart
- version 0.0.7, CHANGELOG, docs (M9/M13, ROADMAP, README install section)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 18:12:53 +02:00
parent 4e3f6aa94e
commit 46ba53631a
12 changed files with 238 additions and 16 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
"""RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers."""
__version__ = "0.0.6"
__version__ = "0.0.7"
+8 -2
View File
@@ -266,8 +266,14 @@ def cmd_update(args) -> int:
print(f"Update available: {tag} (current v{__version__}).")
if args.check:
return 0
print("Self-update (apply) isn't wired yet — coming with the install script.")
return 0
print(f"Installing {tag}")
rc, out = updates.apply_update(tag)
print(out[-2000:])
if rc == 0:
print(f"\nUpdated to {tag}. Restart RigDoctor to use the new version.")
return 0
print(f"\nUpdate failed (exit {rc}).")
return rc
def cmd_report(args) -> int:
+22
View File
@@ -9,6 +9,8 @@ handles detection and exposes a clear state for the UI.
from __future__ import annotations
import json
import subprocess
import sys
import urllib.error
import urllib.request
@@ -73,3 +75,23 @@ def update_state(timeout: float = 5.0) -> tuple[str, str | None]:
if tag and is_newer(tag):
return (AVAILABLE, tag)
return (UP_TO_DATE, tag)
def apply_update(tag: str) -> tuple[int, str]:
"""Self-update the current (user-local) install to `tag` via authenticated pip.
Installs `rigdoctor[gui] @ git+https://oauth2:<token>@…/rigdoctor.git@<tag>` into
the running environment. Returns (exit_code, output) with the token scrubbed.
"""
token = load_token()
if not token:
return (1, "No update token configured. Run `rigdoctor login`.")
host = GITEA_BASE.split("://", 1)[1]
ref = f"rigdoctor[gui] @ git+https://oauth2:{token}@{host}/{REPO}.git@{tag}"
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", ref]
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
out = (proc.stdout + proc.stderr).replace(token, "***")
return (proc.returncode, out)
except (subprocess.SubprocessError, OSError) as exc:
return (1, str(exc).replace(token, "***"))
+23 -4
View File
@@ -4,8 +4,7 @@ from __future__ import annotations
import threading
from PySide6.QtCore import Qt, QUrl, Signal
from PySide6.QtGui import QDesktopServices
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QButtonGroup,
QFrame,
@@ -34,7 +33,8 @@ _PLACEHOLDERS = {
class MainWindow(QMainWindow):
_update_checked = Signal(object) # latest tag (str) or None
_update_checked = Signal(object) # (state, tag)
_update_applied = Signal(int) # pip exit code
def __init__(self, interval: float = 1.0) -> None:
super().__init__()
@@ -72,7 +72,9 @@ class MainWindow(QMainWindow):
self._worker.start()
# Background update check (M13); result lands in the sidebar.
self._latest_tag = None
self._update_checked.connect(self._show_update_state)
self._update_applied.connect(self._on_update_applied)
threading.Thread(target=self._check_updates, daemon=True).start()
def _build_sidebar(self) -> QFrame:
@@ -117,16 +119,33 @@ class MainWindow(QMainWindow):
self._update_btn = QPushButton()
self._update_btn.setObjectName("PrimaryButton")
self._update_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self._update_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(updates.RELEASES_PAGE)))
self._update_btn.clicked.connect(self._apply_update)
self._update_btn.setVisible(False)
v.addWidget(self._update_btn)
return bar
def _apply_update(self) -> None:
if not self._latest_tag:
return
self._update_btn.setEnabled(False)
self._update_label.setText("updating…")
tag = self._latest_tag
threading.Thread(target=lambda: self._update_applied.emit(updates.apply_update(tag)[0]), daemon=True).start()
def _on_update_applied(self, rc: int) -> None:
if rc == 0:
self._update_label.setText("updated — restart RigDoctor")
self._update_btn.setVisible(False)
else:
self._update_label.setText("update failed")
self._update_btn.setEnabled(True)
def _check_updates(self) -> None:
self._update_checked.emit(updates.update_state())
def _show_update_state(self, result) -> None:
state, tag = result
self._latest_tag = tag
self._update_btn.setVisible(False)
if state == updates.NO_TOKEN:
self._update_label.setText("connect to update server")