mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-09-11 06:48:43 -06:00
First Basic Implementation of Remote Script Execution Functionality
This commit is contained in:
147
Data/Agent/tray_launcher.py
Normal file
147
Data/Agent/tray_launcher.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import signal
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
|
||||
|
||||
def project_paths():
|
||||
# Expected layout when running from venv: <Root>\Agent\Borealis
|
||||
borealis_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
agent_dir = os.path.abspath(os.path.join(borealis_dir, os.pardir))
|
||||
venv_scripts = os.path.join(agent_dir, 'Scripts')
|
||||
pyw = os.path.join(venv_scripts, 'pythonw.exe')
|
||||
py = os.path.join(venv_scripts, 'python.exe')
|
||||
icon_path = os.path.join(borealis_dir, 'Borealis.ico')
|
||||
agent_script = os.path.join(borealis_dir, 'borealis-agent.py')
|
||||
return {
|
||||
'borealis_dir': borealis_dir,
|
||||
'venv_scripts': venv_scripts,
|
||||
'pythonw': pyw if os.path.isfile(pyw) else sys.executable,
|
||||
'python': py if os.path.isfile(py) else sys.executable,
|
||||
'agent_script': agent_script,
|
||||
'icon': icon_path if os.path.isfile(icon_path) else None,
|
||||
}
|
||||
|
||||
|
||||
class TrayApp(QtWidgets.QSystemTrayIcon):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
paths = project_paths()
|
||||
self.paths = paths
|
||||
icon = QtGui.QIcon(paths['icon']) if paths['icon'] else app.style().standardIcon(QtWidgets.QStyle.SP_ComputerIcon)
|
||||
super().__init__(icon)
|
||||
self.setToolTip('Borealis Agent')
|
||||
self.menu = QtWidgets.QMenu()
|
||||
|
||||
self.action_show_console = self.menu.addAction('Switch to Foreground Mode')
|
||||
self.action_hide_console = self.menu.addAction('Switch to Background Mode')
|
||||
self.action_restart = self.menu.addAction('Restart Agent')
|
||||
self.action_restart_service = self.menu.addAction('Restart Script Execution Service')
|
||||
self.menu.addSeparator()
|
||||
self.action_quit = self.menu.addAction('Quit Agent and Tray')
|
||||
|
||||
self.action_show_console.triggered.connect(self.switch_to_console)
|
||||
self.action_hide_console.triggered.connect(self.switch_to_background)
|
||||
self.action_restart.triggered.connect(self.restart_agent)
|
||||
self.action_restart_service.triggered.connect(self.restart_script_service)
|
||||
self.action_quit.triggered.connect(self.quit_all)
|
||||
self.setContextMenu(self.menu)
|
||||
|
||||
self.proc = None
|
||||
self.console_mode = False
|
||||
# Start in background mode by default
|
||||
self.switch_to_background()
|
||||
self.show()
|
||||
|
||||
def _start_agent(self, console=False):
|
||||
self._stop_agent()
|
||||
exe = self.paths['python'] if console else self.paths['pythonw']
|
||||
args = [exe, '-W', 'ignore::SyntaxWarning', self.paths['agent_script']]
|
||||
creationflags = 0
|
||||
if not console and os.name == 'nt':
|
||||
# CREATE_NO_WINDOW
|
||||
creationflags = 0x08000000
|
||||
try:
|
||||
self.proc = subprocess.Popen(args, cwd=self.paths['borealis_dir'], creationflags=creationflags)
|
||||
self.console_mode = console
|
||||
self._update_actions(console)
|
||||
except Exception:
|
||||
self.proc = None
|
||||
|
||||
def _stop_agent(self):
|
||||
if self.proc is not None:
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
self.proc.send_signal(signal.SIGTERM)
|
||||
else:
|
||||
self.proc.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self.proc.wait(timeout=3)
|
||||
except Exception:
|
||||
try:
|
||||
self.proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self.proc = None
|
||||
|
||||
def _update_actions(self, console):
|
||||
self.action_show_console.setEnabled(not console)
|
||||
self.action_hide_console.setEnabled(console)
|
||||
|
||||
def switch_to_console(self):
|
||||
self._start_agent(console=True)
|
||||
|
||||
def switch_to_background(self):
|
||||
self._start_agent(console=False)
|
||||
|
||||
def restart_agent(self):
|
||||
# Restart using current mode
|
||||
self._start_agent(console=self.console_mode)
|
||||
|
||||
def restart_script_service(self):
|
||||
# Try direct stop/start; if fails (likely due to permissions), attempt elevation via PowerShell
|
||||
service_name = 'BorealisScriptService'
|
||||
try:
|
||||
# Stop service
|
||||
subprocess.run(["sc.exe", "stop", service_name], check=False, capture_output=True)
|
||||
# Start service
|
||||
subprocess.run(["sc.exe", "start", service_name], check=False, capture_output=True)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: elevate via PowerShell
|
||||
try:
|
||||
script = (
|
||||
f"$ErrorActionPreference='Continue'; "
|
||||
f"try {{ Stop-Service -Name '{service_name}' -Force -ErrorAction SilentlyContinue }} catch {{}}; "
|
||||
f"Start-Sleep -Seconds 1; "
|
||||
f"try {{ Start-Service -Name '{service_name}' }} catch {{}};"
|
||||
)
|
||||
# Start-Process PowerShell -Verb RunAs to elevate
|
||||
ps_cmd = [
|
||||
'powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
||||
'-Command',
|
||||
"Start-Process PowerShell -Verb RunAs -ArgumentList '-NoProfile -ExecutionPolicy Bypass -Command \"" + script.replace("\"", "\\\"") + "\"'"
|
||||
]
|
||||
subprocess.Popen(ps_cmd)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def quit_all(self):
|
||||
self._stop_agent()
|
||||
self.hide()
|
||||
self.app.quit()
|
||||
|
||||
|
||||
def main():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
tray = TrayApp(app)
|
||||
return app.exec_()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
Reference in New Issue
Block a user