#!/usr/bin/env python3 import json import os import subprocess import sys from pathlib import Path from PyQt5 import QtCore, QtWidgets APP_TITLE = "SAIKYO OS — Security Profile Manager" def _run(cmd, check=False): return subprocess.run(cmd, check=check) def _pkexec(cmd): # pkexec will prompt for admin auth via polkit return _run(["/usr/bin/pkexec"] + cmd, check=False) def _profiles_dir() -> Path: return Path("/usr/share/saikyo-security-profile/profiles") def _read_profile(profile: str) -> dict: p = _profiles_dir() / f"{profile}.json" return json.loads(p.read_text(encoding="utf-8")) class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(APP_TITLE) self.setMinimumSize(720, 480) root = QtWidgets.QWidget(self) self.setCentralWidget(root) self.profile_combo = QtWidgets.QComboBox() self.profile_combo.addItems(["standard", "secure"]) self.apply_btn = QtWidgets.QPushButton("Применить") self.export_btn = QtWidgets.QPushButton("Экспорт...") self.import_btn = QtWidgets.QPushButton("Импорт...") self.reload_btn = QtWidgets.QPushButton("Обновить") self.preview = QtWidgets.QPlainTextEdit() self.preview.setReadOnly(True) self.preview.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) top = QtWidgets.QHBoxLayout() top.addWidget(QtWidgets.QLabel("Профиль:")) top.addWidget(self.profile_combo, 1) top.addWidget(self.apply_btn) top.addWidget(self.export_btn) top.addWidget(self.import_btn) top.addWidget(self.reload_btn) layout = QtWidgets.QVBoxLayout(root) layout.addLayout(top) layout.addWidget(self.preview, 1) self.apply_btn.clicked.connect(self.on_apply) self.export_btn.clicked.connect(self.on_export) self.import_btn.clicked.connect(self.on_import) self.reload_btn.clicked.connect(self.refresh_preview) self.profile_combo.currentTextChanged.connect(lambda _: self.refresh_preview()) self.refresh_preview() def current_profile(self) -> str: return self.profile_combo.currentText().strip() def refresh_preview(self): prof_name = self.current_profile() try: data = _read_profile(prof_name) self.preview.setPlainText(json.dumps(data, indent=2, sort_keys=True) + "\n") except Exception as e: self.preview.setPlainText(f"Ошибка чтения профиля '{prof_name}': {e}\n") def _msg(self, title: str, text: str, icon=QtWidgets.QMessageBox.Information): m = QtWidgets.QMessageBox(self) m.setIcon(icon) m.setWindowTitle(title) m.setText(text) m.exec_() def on_apply(self): prof = self.current_profile() rc = _pkexec(["/usr/sbin/saikyo-security-profile", "apply", prof]).returncode if rc == 0: self._msg("Готово", f"Профиль '{prof}' применён.") else: self._msg("Ошибка", f"Не удалось применить профиль '{prof}'. Код: {rc}", QtWidgets.QMessageBox.Critical) def on_export(self): prof = self.current_profile() path, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Экспорт профиля", str(Path.home() / f"{prof}.json"), "JSON (*.json);;All files (*)", ) if not path: return try: # Export does not require root rc = _run(["/usr/sbin/saikyo-security-profile", "export", prof, path], check=False).returncode if rc == 0: self._msg("Готово", f"Профиль '{prof}' экспортирован в:\n{path}") else: self._msg("Ошибка", f"Не удалось экспортировать профиль '{prof}'. Код: {rc}", QtWidgets.QMessageBox.Critical) except Exception as e: self._msg("Ошибка", str(e), QtWidgets.QMessageBox.Critical) def on_import(self): prof = self.current_profile() path, _ = QtWidgets.QFileDialog.getOpenFileName( self, "Импорт профиля", str(Path.home()), "JSON (*.json);;All files (*)", ) if not path: return # Validate JSON before asking for auth try: json.loads(Path(path).read_text(encoding="utf-8")) except Exception as e: self._msg("Ошибка", f"Некорректный JSON: {e}", QtWidgets.QMessageBox.Critical) return rc = _pkexec(["/usr/sbin/saikyo-security-profile", "import", prof, path]).returncode if rc == 0: self._msg("Готово", f"Профиль '{prof}' импортирован из:\n{path}") self.refresh_preview() else: self._msg("Ошибка", f"Не удалось импортировать профиль '{prof}'. Код: {rc}", QtWidgets.QMessageBox.Critical) def main(argv): if not Path("/usr/sbin/saikyo-security-profile").exists(): print("ERROR: /usr/sbin/saikyo-security-profile not found", file=sys.stderr) return 2 if not Path("/usr/bin/pkexec").exists(): print("ERROR: pkexec not found (install policykit-1)", file=sys.stderr) return 2 app = QtWidgets.QApplication(list(argv)) app.setApplicationName("saikyo-security-profile-gui") w = MainWindow() w.show() return app.exec_() if __name__ == "__main__": raise SystemExit(main(sys.argv))