NTFS-Dirty-Bit-Reparatur, Release-Skript und Projekthygiene
- GUI: Dirty-Bit/Fast-Startup beim Mounten erkennen, Rueckfrage mit ntfsfix + erzwungenem Schreib-Mount, Button "NTFS reparieren" - release.py: Version hochzaehlen, CHANGELOG pflegen, commit+tag, ISO bauen und als Gitea-Release (tea) mit ISO-Anhang veroeffentlichen - out/ bleibt im Repo (out/.gitignore), ISOs ignoriert - .gitignore ergaenzt, versehentlich getrackte __pycache__ entfernt Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
fd3fe19539
commit
cbd7e74b1c
@@ -0,0 +1,9 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# live-build Arbeitsverzeichnis (falls lokal gebaut)
|
||||||
|
/live/
|
||||||
|
*.iso
|
||||||
@@ -17,3 +17,7 @@ Versionierung nach [SemVer](https://semver.org/lang/de/).
|
|||||||
- NTFS-Dirty-Bit-Erkennung beim Mounten mit Rückfrage und automatischem
|
- NTFS-Dirty-Bit-Erkennung beim Mounten mit Rückfrage und automatischem
|
||||||
`ntfsfix` + erzwungenem Schreib-Mount; zusätzlich eigener Button
|
`ntfsfix` + erzwungenem Schreib-Mount; zusätzlich eigener Button
|
||||||
„NTFS reparieren (Dirty-Bit)".
|
„NTFS reparieren (Dirty-Bit)".
|
||||||
|
- `release.py`: Version automatisch hochzählen (Patch/Minor/Major), CHANGELOG
|
||||||
|
pflegen, committen + taggen, ISO bauen und als Gitea-Release (via `tea`)
|
||||||
|
mit ISO-Anhang veröffentlichen.
|
||||||
|
- `out/` bleibt im Repo (per `out/.gitignore`), die gebauten ISOs werden ignoriert.
|
||||||
|
|||||||
@@ -53,6 +53,23 @@ sudo dd if=out/pc-unlocker.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
|||||||
> Der Container läuft mit `--privileged`, weil `live-build` Loop-Devices und
|
> Der Container läuft mit `--privileged`, weil `live-build` Loop-Devices und
|
||||||
> Mounts benötigt.
|
> Mounts benötigt.
|
||||||
|
|
||||||
|
## Release veröffentlichen
|
||||||
|
|
||||||
|
`release.py` zählt die Version hoch, pflegt das CHANGELOG, committet + taggt,
|
||||||
|
baut die ISO und veröffentlicht sie als **Gitea-Release** mit der ISO als Anhang.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./release.py # Patch: 0.1.0 -> 0.1.1
|
||||||
|
./release.py --minor # 0.1.0 -> 0.2.0
|
||||||
|
./release.py --major # 0.1.0 -> 1.0.0
|
||||||
|
./release.py --no-build # vorhandene out/pc-unlocker.iso anhängen
|
||||||
|
./release.py --no-publish # nur lokal (bump, commit, tag, build)
|
||||||
|
./release.py --dry-run # nur anzeigen
|
||||||
|
```
|
||||||
|
|
||||||
|
Veröffentlichen pusht Release-Commit und Tag nach `origin` und ruft dann
|
||||||
|
`tea release create`. Einmalig nötig: `tea login add` für die Gitea-Instanz.
|
||||||
|
|
||||||
## App direkt testen (ohne ISO)
|
## App direkt testen (ohne ISO)
|
||||||
|
|
||||||
Auf einem Linux-Host mit `python3-tk`, `chntpw`, `ntfs-3g`:
|
Auf einem Linux-Host mit `python3-tk`, `chntpw`, `ntfs-3g`:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
Executable
+181
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Release-Automatik fuer PC-Unlocker.
|
||||||
|
|
||||||
|
Zaehlt die Version hoch, pflegt das CHANGELOG, committet + taggt, baut die ISO
|
||||||
|
und veroeffentlicht sie als Gitea-Release (via `tea`) mit der ISO als Anhang.
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
./release.py # Patch-Release (0.1.0 -> 0.1.1), bauen + publizieren
|
||||||
|
./release.py --minor # 0.1.0 -> 0.2.0
|
||||||
|
./release.py --major # 0.1.0 -> 1.0.0
|
||||||
|
./release.py --no-build # vorhandene out/pc-unlocker.iso verwenden
|
||||||
|
./release.py --no-publish # nur lokal: bump, commit, tag, (build)
|
||||||
|
./release.py --dry-run # nur anzeigen, nichts aendern
|
||||||
|
|
||||||
|
Veroeffentlichen pusht den Release-Commit und das Tag nach origin (zwingend,
|
||||||
|
damit Gitea das Release anlegen kann) und ruft dann `tea release create`.
|
||||||
|
Einmalig noetig: `tea login add` fuer git.hacker-net.de.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime as dt
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
INIT = ROOT / "app" / "puk" / "__init__.py"
|
||||||
|
CHANGELOG = ROOT / "CHANGELOG.md"
|
||||||
|
ISO = ROOT / "out" / "pc-unlocker.iso"
|
||||||
|
|
||||||
|
VERSION_RE = re.compile(r'^__version__\s*=\s*"(\d+)\.(\d+)\.(\d+)"', re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str], *, check: bool = True, capture: bool = False) -> subprocess.CompletedProcess:
|
||||||
|
print(f" $ {' '.join(cmd)}")
|
||||||
|
return subprocess.run(cmd, check=check, text=True,
|
||||||
|
capture_output=capture, cwd=ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
def fail(msg: str) -> None:
|
||||||
|
print(f"\nFEHLER: {msg}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def read_version() -> tuple[int, int, int]:
|
||||||
|
m = VERSION_RE.search(INIT.read_text(encoding="utf-8"))
|
||||||
|
if not m:
|
||||||
|
fail(f"__version__ nicht in {INIT} gefunden")
|
||||||
|
return tuple(int(x) for x in m.groups()) # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
|
def bump(ver: tuple[int, int, int], part: str) -> tuple[int, int, int]:
|
||||||
|
major, minor, patch = ver
|
||||||
|
if part == "major":
|
||||||
|
return major + 1, 0, 0
|
||||||
|
if part == "minor":
|
||||||
|
return major, minor + 1, 0
|
||||||
|
return major, minor, patch + 1
|
||||||
|
|
||||||
|
|
||||||
|
def write_version(ver: tuple[int, int, int]) -> None:
|
||||||
|
new = f'__version__ = "{ver[0]}.{ver[1]}.{ver[2]}"'
|
||||||
|
text = VERSION_RE.sub(new, INIT.read_text(encoding="utf-8"), count=1)
|
||||||
|
INIT.write_text(text, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def update_changelog(ver_str: str) -> str:
|
||||||
|
"""[Unreleased]-Inhalt in einen datierten Versionsabschnitt ueberfuehren.
|
||||||
|
|
||||||
|
Gibt die Notizen (den verschobenen Inhalt) fuer die Release-Beschreibung zurueck.
|
||||||
|
"""
|
||||||
|
text = CHANGELOG.read_text(encoding="utf-8")
|
||||||
|
marker = "## [Unreleased]"
|
||||||
|
idx = text.find(marker)
|
||||||
|
if idx == -1:
|
||||||
|
fail("'## [Unreleased]' nicht im CHANGELOG gefunden")
|
||||||
|
after = idx + len(marker)
|
||||||
|
# Inhalt bis zur naechsten Versionsueberschrift (## [..]) oder Dateiende
|
||||||
|
nxt = text.find("\n## [", after)
|
||||||
|
end = nxt if nxt != -1 else len(text)
|
||||||
|
body = text[after:end].strip("\n")
|
||||||
|
notes = body if body else "Wartungs-Release."
|
||||||
|
|
||||||
|
today = dt.date.today().isoformat()
|
||||||
|
new_block = (
|
||||||
|
f"{marker}\n\n"
|
||||||
|
f"## [{ver_str}] - {today}\n\n"
|
||||||
|
f"{body}\n"
|
||||||
|
)
|
||||||
|
new_text = text[:idx] + new_block + text[end:]
|
||||||
|
CHANGELOG.write_text(new_text, encoding="utf-8")
|
||||||
|
return notes
|
||||||
|
|
||||||
|
|
||||||
|
def git_clean_or_die() -> None:
|
||||||
|
res = run(["git", "status", "--porcelain"], capture=True)
|
||||||
|
dirty = [l for l in res.stdout.splitlines()
|
||||||
|
if l and not l.endswith(("CHANGELOG.md", "__init__.py"))]
|
||||||
|
if dirty:
|
||||||
|
fail("Arbeitsverzeichnis nicht sauber. Bitte erst committen/stashen:\n"
|
||||||
|
+ "\n".join(dirty))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="PC-Unlocker Release")
|
||||||
|
g = ap.add_mutually_exclusive_group()
|
||||||
|
g.add_argument("--major", action="store_const", const="major", dest="part")
|
||||||
|
g.add_argument("--minor", action="store_const", const="minor", dest="part")
|
||||||
|
g.add_argument("--patch", action="store_const", const="patch", dest="part")
|
||||||
|
ap.add_argument("--no-build", action="store_true", help="ISO nicht neu bauen")
|
||||||
|
ap.add_argument("--no-publish", action="store_true",
|
||||||
|
help="nur lokal: kein push, kein Gitea-Release")
|
||||||
|
ap.add_argument("--dry-run", action="store_true", help="nur anzeigen")
|
||||||
|
args = ap.parse_args()
|
||||||
|
part = args.part or "patch"
|
||||||
|
|
||||||
|
old = read_version()
|
||||||
|
new = bump(old, part)
|
||||||
|
old_s = ".".join(map(str, old))
|
||||||
|
new_s = ".".join(map(str, new))
|
||||||
|
tag = f"v{new_s}"
|
||||||
|
print(f"Release: {old_s} -> {new_s} (tag {tag})")
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print("--dry-run: es wird nichts geaendert.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not args.no_publish:
|
||||||
|
git_clean_or_die()
|
||||||
|
if shutil.which("tea") is None:
|
||||||
|
fail("`tea` (Gitea CLI) nicht gefunden. Mit --no-publish nur lokal "
|
||||||
|
"arbeiten oder tea installieren.")
|
||||||
|
|
||||||
|
# 1) Version + CHANGELOG
|
||||||
|
print("\n==> Version und CHANGELOG aktualisieren")
|
||||||
|
write_version(new)
|
||||||
|
notes = update_changelog(new_s)
|
||||||
|
|
||||||
|
# 2) ISO bauen
|
||||||
|
if not args.no_build:
|
||||||
|
print("\n==> ISO bauen")
|
||||||
|
run(["bash", str(ROOT / "build.sh")])
|
||||||
|
if not ISO.exists():
|
||||||
|
fail(f"ISO fehlt: {ISO} (ohne --no-build bauen lassen)")
|
||||||
|
|
||||||
|
# 3) Commit + Tag
|
||||||
|
print("\n==> Commit und Tag")
|
||||||
|
run(["git", "add", str(INIT), str(CHANGELOG)])
|
||||||
|
run(["git", "commit", "-m", f"release: {tag}"])
|
||||||
|
run(["git", "tag", "-a", tag, "-m", f"PC-Unlocker {tag}"])
|
||||||
|
|
||||||
|
if args.no_publish:
|
||||||
|
print(f"\nLokal fertig. Veroeffentlichen spaeter z.B.:\n"
|
||||||
|
f" git push origin HEAD && git push origin {tag}\n"
|
||||||
|
f" tea release create --tag {tag} --title {tag} "
|
||||||
|
f"--asset {ISO}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 4) Push (noetig, damit Gitea das Release kennt)
|
||||||
|
print("\n==> Push nach origin")
|
||||||
|
run(["git", "push", "origin", "HEAD"])
|
||||||
|
run(["git", "push", "origin", tag])
|
||||||
|
|
||||||
|
# 5) Gitea-Release mit ISO-Anhang
|
||||||
|
print("\n==> Gitea-Release anlegen")
|
||||||
|
run(["tea", "release", "create",
|
||||||
|
"--tag", tag,
|
||||||
|
"--title", tag,
|
||||||
|
"--note", notes,
|
||||||
|
"--asset", str(ISO)])
|
||||||
|
|
||||||
|
print(f"\nFertig: Release {tag} veroeffentlicht, ISO angehaengt.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user