🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
pagan-gui-build.py – GUI do lokalnych buildów PaganOS + push na VPS.
(architektura Wayland-friendly: GUI jako użytkownik, root tylko dla pagsync)
Listuje pakiety z receptur (git.paganlinux.eu/recipes.git), pozwala wybrać
kolejkę, buduje SEKWENCYJNIE na PC (pagsync z VPS, rootfs lokalny – hasło
sudo pytane w okienku, bez terminala) i wysyła .pag na VPS (rsync -> podpis
GPG na serwerze -> pagsync --gen-repo).
Uruchomienie: python3 pagan-gui-build.py
Konfiguracja (env): PAGAN_VPS_HOST, PAGAN_SSH_KEY, PAGAN_GPG_KEY,
PAGAN_ROOTFS, PAGAN_RECIPES_URL, PAGAN_JOBS, PAGAN_GUI_WORK
"""
import os
import queue
import re
import shutil
import subprocess
import sys
import threading
import tkinter as tk
from tkinter import simpledialog, ttk
# ── Konfiguracja ──────────────────────────────────────────────────────────
HOME = os.path.expanduser("~")
WORK = os.environ.get("PAGAN_GUI_WORK", os.path.join(HOME, ".pagan-gui"))
RECIPES_URL = os.environ.get("PAGAN_RECIPES_URL", "https://git.paganlinux.eu/recipes.git")
VPS_HOST = os.environ.get("PAGAN_VPS_HOST", "root@paganlinux.eu")
SSH_KEY = os.environ.get("PAGAN_SSH_KEY", os.path.join(HOME, ".ssh", "id_ed25519_pagan"))
GPG_KEY = os.environ.get("PAGAN_GPG_KEY", "A14CBE52BE0D63F5")
REPO_STABLE_VPS = "/var/www/repo.paganlinux.eu/stable"
JOBS = os.environ.get("PAGAN_JOBS", str(os.cpu_count() or 4))
ROOTFS = os.environ.get("PAGAN_ROOTFS") or (
"/mnt/pagan" if os.path.isdir("/mnt/pagan/usr/bin") else "/mnt/pagan-native-rootfs"
)
RECIPES = os.path.join(WORK, "recipes")
TOOLS = os.path.join(WORK, "tools")
OUTPUT = os.path.join(WORK, "output")
REPO = os.path.join(WORK, "repo")
STATE = os.path.join(WORK, "state.json")
DIAG = os.path.join(WORK, "diagnostics")
LOCK = os.path.join(WORK, "pagsync.lock")
GUILOG = os.path.join(WORK, "gui.log")
PAGBUILD = os.path.join(TOOLS, "pagbuild")
PAGSYNC = os.path.join(TOOLS, "pagsync")
SSH_OPTS = ["-i", SSH_KEY, "-o", "BatchMode=yes", "-o", "ConnectTimeout=15",
"-o", "StrictHostKeyChecking=accept-new", "-o", "IdentitiesOnly=yes",
"-o", "UserKnownHostsFile=" + os.path.join(HOME, ".ssh", "known_hosts")]
# ── Sudo przez okienko (bez terminala, Wayland-safe) ─────────────────────
_pw = None
_pw_evt = threading.Event()
_pw_cb = None # ustawiane przez GUI: pokaż okienko na głównym wątku
def _ask_password_ui():
"""Wywoływane na głównym wątku Tk – okienko z hasłem sudo."""
global _pw
d = simpledialog.askstring("Hasło sudo",
"pagbuild/pagsync wymaga root (chroot).\n"
"Podaj hasło sudo:", show="*")
_pw = d
_pw_evt.set()
def _sudo_authed() -> bool:
if subprocess.run(["sudo", "-n", "true"], capture_output=True).returncode == 0:
return True
global _pw
_pw = None
_pw_evt.clear()
if _pw_cb:
_pw_cb()
return _pw_evt.wait(timeout=180) and bool(_pw)
def run_root(cmd, env=None, cwd=None):
"""Polecenie jako root przez `sudo -S` (hasło z okienka). Loguje output."""
if not _sudo_authed():
GUI.log("❌ Brak autoryzacji sudo – przerwano.", "err")
return 1
envpairs = []
if env:
envpairs = [f"{k}={v}" for k, v in env.items()]
full = ["sudo", "-S", "-E", "/usr/bin/env", *envpairs, *cmd]
GUI.log("$ (sudo) " + " ".join(cmd), "cmd")
try:
p = subprocess.Popen(full, cwd=cwd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, errors="replace")
p.stdin.write((_pw or "") + "\n")
p.stdin.flush()
p.stdin.close()
for line in p.stdout:
line = line.rstrip()
if not line:
continue
tag = ""
if any(s in line for s in ("✅", "Sukces", "OK")):
tag = "ok"
elif any(s in line for s in ("❌", "FAIL", "Błąd", "error",
"denied", "permitted", "wrong password")):
tag = "err"
elif any(s in line for s in ("⚠", "WARN")):
tag = "warn"
GUI.log(line, tag)
p.wait()
return p.returncode
except Exception as e:
GUI.log(f"❌ wyjątek: {e}", "err")
return 1
def run(cmd, cwd=None, env=None):
"""Polecenie jako bieżący użytkownik. Loguje output."""
GUI.log("$ " + " ".join(cmd), "cmd")
try:
p = subprocess.Popen(cmd, cwd=cwd, env=env, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, errors="replace")
for line in p.stdout:
line = line.rstrip()
if not line:
continue
tag = ""
if any(s in line for s in ("✅", "Sukces", "OK")):
tag = "ok"
elif any(s in line for s in ("❌", "FAIL", "Błąd", "error")):
tag = "err"
elif any(s in line for s in ("⚠", "WARN")):
tag = "warn"
GUI.log(line, tag)
p.wait()
return p.returncode
except Exception as e:
GUI.log(f"❌ wyjątek: {e}", "err")
return 1
def ssh(cmd):
return run(["ssh", *SSH_OPTS, VPS_HOST] + cmd)
def _writable(p):
"""Czy katalog istnieje i da się w nim pisać (probe file)."""
if not os.path.isdir(p):
return True
try:
t = os.path.join(p, ".wtest")
open(t, "w").close()
os.unlink(t)
return True
except OSError:
return False
def ensure_dirs():
"""Tworzy katalogi robocze i (raz) przejmuje WORK z roota na użytkownika.
Zwraca True gdy wszystko jest zapisywalne."""
for d in (WORK, TOOLS, OUTPUT, DIAG, os.path.join(REPO, "stable")):
try:
os.makedirs(d, exist_ok=True)
except OSError:
pass
dirty = [d for d in (WORK, RECIPES, TOOLS, OUTPUT, DIAG, os.path.join(REPO, "stable"))
if os.path.isdir(d) and not _writable(d)]
if not dirty:
return True
GUI.log("⚠ katalogi robocze należą do roota – przejmuję na użytkownika (sudo)", "warn")
rc = run_root(["chown", "-R", f"{os.getuid()}:{os.getgid()}", WORK])
if rc != 0:
GUI.log("❌ nie udało się przejąć ~/.pagan-gui – sprawdź hasło sudo", "err")
return False
return all(_writable(d) for d in dirty)
def _flog(msg: str):
try:
with open(GUILOG, "a", encoding="utf-8") as fh:
fh.write(msg + "\n")
except OSError:
pass
# ── Akcje ─────────────────────────────────────────────────────────────────
def sync_recipes():
if not ensure_dirs():
return
GUI.log("── Sync receptur ──", "hdr")
if os.path.isdir(os.path.join(RECIPES, ".git")):
run(["git", "-C", RECIPES, "pull", "--ff-only", "origin", "main"])
else:
run(["git", "clone", RECIPES_URL, RECIPES])
if not os.path.exists(PAGBUILD) or not os.path.exists(PAGSYNC):
GUI.log("── Pobieram pagbuild/pagsync z VPS ──", "hdr")
for dst, src in ((PAGBUILD, "/opt/pagan-web-v2/pagbuild"),
(PAGSYNC, "/usr/bin/pagsync")):
run(["scp", *SSH_OPTS, f"{VPS_HOST}:{src}", dst])
os.chmod(dst, 0o755)
GUI.log("✅ Sync gotowy", "ok")
def recipe_list():
out = []
if not os.path.isdir(RECIPES):
return out
for root, _dirs, files in os.walk(RECIPES):
for fn in ("PAGBUILD.yaml", "package.yml", "recipe.yaml"):
if fn in files:
fp = os.path.join(root, fn)
try:
txt = open(fp, encoding="utf-8", errors="replace").read()
except OSError:
continue
m = re.search(r"(?m)^pkgname:\s*['\"]?([\w+.-]+)", txt)
v = re.search(r"(?m)^pkgver:\s*['\"]?([\w.+~-]+)", txt)
d = re.search(r"(?m)^pkgdesc:\s*['\"]?([^\n'\"]+)", txt)
name = m.group(1) if m else os.path.basename(root)
cat = os.path.relpath(root, RECIPES).split(os.sep)[0]
out.append((name, v.group(1) if v else "?", cat,
(d.group(1).strip() if d else ""), fp))
break
out.sort(key=lambda x: x[0].lower())
return out
def _run_capture(prefix, cmd, env=None, cwd=None):
"""Wykonuje polecenie, loguje linie i zwraca (rc, lista_linii).
prefix: lista (np. ['sudo','-S','-E','/usr/bin/env', ...] albo [])."""
full = prefix + cmd
GUI.log("$ " + " ".join(full), "cmd")
lines = []
try:
p = subprocess.Popen(full, cwd=cwd, env=env, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, errors="replace")
if prefix and prefix[0] == "sudo":
p.stdin.write((_pw or "") + "\n")
p.stdin.flush()
p.stdin.close()
for ln in p.stdout:
ln = ln.rstrip()
lines.append(ln)
if not ln:
continue
tag = ""
if any(s in ln for s in ("✅", "Sukces", "OK")):
tag = "ok"
elif any(s in ln for s in ("❌", "FAIL", "Błąd", "error", "denied", "permitted")):
tag = "err"
elif any(s in ln for s in ("⚠", "WARN")):
tag = "warn"
GUI.log(ln, tag)
p.wait()
return p.returncode, lines
except Exception as e:
GUI.log(f"❌ wyjątek: {e}", "err")
return 1, lines
def _missing_names_from_log(lines):
"""Wyciąga nazwy brakujących pakietów z logu pagsync (auto-deps)."""
out = []
for ln in lines:
m = re.search(r"brak receptury i brak w repo: (.+)$", ln)
if not m:
m = re.search(r"brak receptury dostarczającej: (.+)$", ln)
if m:
for x in re.split(r"[,\s]+", m.group(1).strip()):
if x and x not in out:
out.append(x)
return out[:6]
def _fill_missing_from_repo(names):
"""Dla brakujących pakietów: pag search -> pobierz .pag z repo VPS ->
pagsync --install (do rootfs buildera). Zwraca listę doinstalowanych."""
filled = []
for name in names:
GUI.log(f"── Brak {name}: pag search … ──", "hdr")
rc, out = _run_capture([], ["pag", "search", name])
found = rc == 0 and any(re.match(rf"^\s*{re.escape(name)}\b", ln) for ln in out)
if not found:
GUI.log(f"⤼ {name}: brak też w repo (pag search) – pomijam", "warn")
continue
# pobierz nazwę pliku z repo.json (VPS)
try:
import json as _json
import urllib.request as _ur
req = _ur.Request("https://repo.paganlinux.eu/stable/repo.json",
headers={"User-Agent": "pag-gui"})
data = _json.load(_ur.urlopen(req, timeout=30))
except Exception as e:
GUI.log(f"⤼ {name}: nie mogę pobrać repo.json: {e}", "warn")
continue
entry = next((p for p in data.get("packages", []) if p.get("name") == name), None)
if not entry or not entry.get("filename"):
GUI.log(f"⤼ {name}: brak wpisu w repo.json", "warn")
continue
fname = entry["filename"]
stable = os.path.join(REPO, "stable")
os.makedirs(stable, exist_ok=True)
dst = os.path.join(stable, fname)
GUI.log(f"↓ pobieram {fname}")
try:
import urllib.request as _ur
_ur.urlretrieve(f"https://repo.paganlinux.eu/stable/{fname}", dst)
except Exception as e:
GUI.log(f"⤼ {name}: pobieranie nieudane: {e}", "warn")
continue
GUI.log(f"📦 {name}: instaluję do rootfs (pagsync --install)")
env = {"PAGAN_RECIPES": RECIPES, "PAGAN_REPO": REPO,
"PAGAN_PAGBUILD": PAGBUILD, "PAGAN_ROOTFS": ROOTFS,
"PAGAN_STATE": STATE, "PAGAN_BUILD_OUT": OUTPUT,
"PAGAN_DIAG": DIAG, "PAGAN_LOCK": LOCK, "PAGAN_DO_SIGN": ""}
if run_root([PAGSYNC, "--once", "--install", name, "--rootfs", ROOTFS], env=env) == 0:
filled.append(name)
return filled
def _sudo_prefix():
return ["sudo", "-S", "-E"] if os.geteuid() != 0 else []
def _run_build(pkg, env):
"""pagsync --build jako root; zwraca (rc, linie_logu)."""
if not _sudo_authed():
return 1, []
return _run_capture(_sudo_prefix(),
[PAGSYNC, "--once", "--build", pkg, "--rootfs", ROOTFS],
env=env)
def check_updates(pkg=None, apply=False):
"""Heurystyka aktualizacji receptur (pagsync --check-updates).
pkg=None -> skan wszystkich; pkg=nazwa -> tylko ten pakiet.
apply=True -> podbija pkgver/pkgrel (zmiany w recipes + commit)."""
if not ensure_dirs():
return
GUI.log("── Sync receptur (git pull) ──", "hdr")
if os.path.isdir(os.path.join(RECIPES, ".git")):
run(["git", "-C", RECIPES, "pull", "--ff-only", "origin", "main"])
GUI.log("── " + ("Podbijam (--apply)" if apply else "Sprawdzam nowsze wersje") + " ──", "hdr")
env = {"PAGAN_RECIPES": RECIPES, "PAGAN_STATE": STATE,
"PAGAN_LOCK": LOCK, "PATH": os.environ.get("PATH", "")}
cmd = [PAGSYNC, "--once", "--check-updates"]
if pkg:
cmd += ["--update-pkg", pkg]
if apply:
cmd += ["--apply"]
rc = run(cmd, env=env)
if rc != 0:
GUI.log("⚠ check-updates zakończone z błędem", "warn")
else:
GUI.log("✅ Sprawdzone" + (" – pkgver podbite (commit lokalny)" if apply else ""), "ok")
if apply:
GUI.log("ℹ Odśwież listę pakietów (zmieniły się wersje)", "warn")
GUI.instance.pkgs = recipe_list()
GUI.instance.root.after(0, GUI.instance.render_pkgs)
def build_queue(names, auto_push):
if not ensure_dirs():
return
# git safe.directory dla roota (pagsync robi pull w recipes jako root)
safe_env = {"GIT_CONFIG_COUNT": "1",
"GIT_CONFIG_KEY_0": "safe.directory",
"GIT_CONFIG_VALUE_0": RECIPES}
for pkg in names:
GUI.log(f"── [{pkg}] build ──", "hdr")
env = dict(safe_env)
env.update({
"PAGAN_RECIPES": RECIPES, "PAGAN_REPO": REPO,
"PAGAN_PAGBUILD": PAGBUILD, "PAGAN_ROOTFS": ROOTFS,
"PAGAN_STATE": STATE, "PAGAN_BUILD_OUT": OUTPUT,
"PAGAN_DIAG": DIAG, "PAGAN_LOCK": LOCK,
"PAGAN_DO_SIGN": "", "MAKEFLAGS": f"-j{JOBS}",
})
rc, loglines = _run_build(pkg, env)
if rc != 0:
missing = _missing_names_from_log(loglines)
if missing:
GUI.log(f"ℹ {pkg}: brakujące do doinstalowania: {', '.join(missing)}", "warn")
filled = _fill_missing_from_repo(missing)
if filled:
GUI.log(f"🔁 {pkg}: ponawiam build po doinstalowaniu: {', '.join(filled)}")
rc, _log2 = _run_build(pkg, env)
if rc != 0:
GUI.log(f"⚠ {pkg}: build FAIL (rc={rc})", "warn")
GUI.log("🏁 Kolejka buildów zakończona", "hdr")
if auto_push:
push_to_vps()
def push_to_vps():
if not ensure_dirs():
return
stable = os.path.join(REPO, "stable")
GUI.log("── rsync .pag na VPS ──", "hdr")
ssh_cmd = " ".join(["ssh"] + SSH_OPTS)
if run(["rsync", "-avz", "--update", "--mkpath", "-e", ssh_cmd,
stable + "/", f"{VPS_HOST}:{REPO_STABLE_VPS}/"]):
GUI.log("❌ rsync nieudany", "err")
return
GUI.log("── Podpis .pag na VPS (klucz zostaje na serwerze) ──", "hdr")
ssh(["bash", "-c",
f"cd {REPO_STABLE_VPS} && for f in *.pag; do [ -f \"$f.asc\" ] || "
f"gpg --detach-sign --armor --batch --no-tty --local-user {GPG_KEY} \"$f\" "
f"2>/dev/null && echo ' 🔏 $f.asc'; done; true"])
GUI.log("── repo.json (VPS) ──", "hdr")
ssh(["bash", "-c",
f"PAGAN_DO_SIGN=1 PAGAN_GPG_KEY={GPG_KEY} pagsync --gen-repo 2>&1 | tail -3"])
GUI.log("✅ Push zakończony", "ok")
# ── GUI ───────────────────────────────────────────────────────────────────
class GUI:
log_queue = queue.Queue()
instance = None
@staticmethod
def log(msg, tag=""):
GUI.log_queue.put((msg, tag))
_flog(msg)
def __init__(self, root):
GUI.instance = self
self.root = root
self.pkgs = []
self.busy = False
root.title("PaganOS – lokalny build + push na VPS")
root.geometry("1120x720")
top = ttk.Frame(root, padding=6)
top.pack(fill="x")
for text, fn in (
("🔄 Pobierz receptury", self.cmd_sync),
("➕ Dodaj do kolejki", self.cmd_add),
("🗑 Usuń z kolejki", self.cmd_remove),
("🔨 Zbuduj kolejkę", lambda: self.cmd_build(False)),
("🔨+📤 Zbuduj i wyślij", lambda: self.cmd_build(True)),
("📤 Wyślij na VPS", self.cmd_push),
("🧹 Wyczyść kolejki", self.cmd_clear),
):
ttk.Button(top, text=text, command=fn).pack(side="left", padx=2)
top2 = ttk.Frame(root, padding=(6, 0, 6, 4))
top2.pack(fill="x")
for text, fn in (
("📈 Sprawdź nowsze wersje (heurystyka)", lambda: self._submit(check_updates, None, False)),
("⬆ Podbij zaznaczony pakiet (--apply)", self.cmd_bump),
):
ttk.Button(top2, text=text, command=fn).pack(side="left", padx=2)
ttk.Label(top2, text="zaznacz 1 pakiet na liście → podbija pkgver + commit",
foreground="#666").pack(side="left", padx=6)
self.status = tk.Label(root, text="init…", anchor="w", bg="#111", fg="#8cf", padx=8)
self.status.pack(fill="x")
mid = ttk.Frame(root, padding=6)
mid.pack(fill="both", expand=True)
left = ttk.LabelFrame(mid, text="Pakiety (recipes.git) – Ctrl/Shift: wiele")
left.pack(side="left", fill="both", expand=True, padx=(0, 4))
self.filter_var = tk.StringVar()
ttk.Entry(left, textvariable=self.filter_var).pack(fill="x", padx=4, pady=(4, 2))
self.filter_var.trace_add("write", lambda *a: self.render_pkgs())
self.listbox = tk.Listbox(left, selectmode="extended", exportselection=False,
font=("monospace", 9))
self.listbox.pack(fill="both", expand=True, padx=4, pady=4)
self.count_lbl = ttk.Label(left, text="")
self.count_lbl.pack(anchor="w", padx=4)
qf = ttk.LabelFrame(mid, text="Kolejka (kernel najpierw!)")
qf.pack(side="left", fill="y", padx=4)
self.queue_lb = tk.Listbox(qf, selectmode="extended", width=34, font=("monospace", 9))
self.queue_lb.pack(fill="both", expand=True, padx=4, pady=4)
cons = ttk.LabelFrame(root, text="Konsola")
cons.pack(fill="both", expand=True, padx=6, pady=(0, 6))
self.txt = tk.Text(cons, height=14, state="disabled", bg="#0d1117", fg="#ddd",
font=("monospace", 9), wrap="none")
self.txt.pack(fill="both", expand=True, padx=4, pady=4)
self.txt.tag_configure("ok", foreground="#3fb950")
self.txt.tag_configure("err", foreground="#f85149")
self.txt.tag_configure("warn", foreground="#d29922")
self.txt.tag_configure("hdr", foreground="#58a6ff")
self.txt.tag_configure("cmd", foreground="#888")
self._taskq = queue.Queue()
self.worker = threading.Thread(target=self._worker, daemon=True)
self.worker.start()
self.root.after(80, self._drain_log)
self.cmd_sync()
# --- wątek roboczy / log ---
def _worker(self):
while True:
task, args = self._taskq.get()
self.busy = True
self.root.after(0, lambda: self.status.config(text="⏳ pracuję…"))
try:
task(*args)
except Exception as e:
GUI.log(f"❌ {e}", "err")
finally:
self.busy = False
self._taskq.task_done()
self.root.after(0, lambda: self.status.config(text="gotowy"))
def _submit(self, fn, *args):
if self.busy:
GUI.log("⏳ Poczekaj – trwa inna operacja", "warn")
return
self._taskq.put((fn, args))
def _drain_log(self):
try:
while True:
msg, tag = GUI.log_queue.get_nowait()
self.txt.config(state="normal")
self.txt.insert("end", msg + "\n", tag)
self.txt.see("end")
self.txt.config(state="disabled")
except queue.Empty:
pass
self.root.after(80, self._drain_log)
# --- akcje ---
def cmd_sync(self):
self._submit(self._do_sync)
def _do_sync(self):
sync_recipes()
self.pkgs = recipe_list()
self.root.after(0, self.render_pkgs)
def cmd_clear(self):
self.queue_lb.delete(0, "end")
def cmd_add(self):
existing = set(self.queue_lb.get(0, "end"))
for i in self.listbox.curselection():
name = self.pkgs[i][0]
if name not in existing:
self.queue_lb.insert("end", name)
def cmd_remove(self):
for i in reversed(self.queue_lb.curselection()):
self.queue_lb.delete(i)
def cmd_build(self, auto_push):
names = list(self.queue_lb.get(0, "end"))
if not names:
GUI.log("Brak pakietów w kolejce", "warn")
return
self._submit(build_queue, names, auto_push)
def cmd_push(self):
self._submit(push_to_vps)
def cmd_bump(self):
sel = self.listbox.curselection()
if len(sel) != 1:
GUI.log("Zaznacz DOKŁADNIE jeden pakiet na liście, żeby podbić wersję", "warn")
return
name = self.pkgs[sel[0]][0]
GUI.log(f"⬆ Podbijam {name} (pkgver + pkgrel=1, sha=SKIP)", "hdr")
self._submit(check_updates, name, True)
def render_pkgs(self):
f = self.filter_var.get().lower()
self.listbox.delete(0, "end")
shown = 0
for p in self.pkgs:
name, ver, cat = p[0], p[1], p[2]
if f and f not in name.lower() and f not in cat.lower():
continue
self.listbox.insert("end", f"{name:<32} {ver:<12} {cat}")
shown += 1
self.count_lbl.config(text=f"pokazano {shown} z {len(self.pkgs)}")
def main():
global _pw_cb
_flog("\n===== start pagan-gui-build " + __import__("datetime").datetime.now().isoformat() + " =====")
def _hook(et, ev, tb):
import traceback as _tb
msg = "".join(_tb.format_exception(et, ev, tb))
_flog("❌ NIEZŁAPANY WYJĄTEK:\n" + msg)
print(msg, file=sys.stderr)
sys.excepthook = _hook
if not os.path.isdir(ROOTFS):
print(f"⚠ Uwaga: rootfs nie istnieje: {ROOTFS}\n"
f" Ustaw PAGAN_ROOTFS=/ścieżka/do/rootfs")
root = tk.Tk()
_pw_cb = lambda: root.after(0, _ask_password_ui)
gui = GUI(root)
root.mainloop()
if __name__ == "__main__":
main()