diff --git a/Assemblies/Scripts/Borealis/Remote_Agent_Update_WIN.json b/Assemblies/Scripts/Borealis/Remote_Agent_Update_WIN.json new file mode 100644 index 0000000..bf06513 --- /dev/null +++ b/Assemblies/Scripts/Borealis/Remote_Agent_Update_WIN.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "name": "Remote Agent Update [WIN]", + "description": "Reaches out to the remote Borealis agent and triggers an automatic unattended update from the Github repository.", + "category": "script", + "type": "powershell", + "script": "W0NtZGxldEJpbmRpbmcoKV0KcGFyYW0oCiAgICBbUGFyYW1ldGVyKCldCiAgICBbc3RyaW5nXSRUYXNrTmFtZSA9ICJCb3JlYWxpcyBBZ2VudCIsCgogICAgW1BhcmFtZXRlcigpXQogICAgW3N0cmluZ10kVGFza1BhdGgKKQoKJHRhc2tQYXJhbXMgPSBAeyBUYXNrTmFtZSA9ICRUYXNrTmFtZTsgRXJyb3JBY3Rpb24gPSAnU3RvcCcgfQppZiAoJFRhc2tQYXRoKSB7CiAgICAkdGFza1BhcmFtcy5UYXNrUGF0aCA9ICRUYXNrUGF0aAp9Cgp0cnkgewogICAgJHRhc2sgPSBHZXQtU2NoZWR1bGVkVGFzayBAdGFza1BhcmFtcwp9IGNhdGNoIHsKICAgIHRocm93ICJTY2hlZHVsZWQgdGFzayAnJFRhc2tOYW1lJyB3YXMgbm90IGZvdW5kLiIgCn0KCiRleGVjQWN0aW9uID0gJHRhc2suQWN0aW9ucyB8IFdoZXJlLU9iamVjdCB7ICRfLkNpbUNsYXNzLkNpbUNsYXNzTmFtZSAtZXEgJ01TRlRfVGFza0V4ZWNBY3Rpb24nIH0gfCBTZWxlY3QtT2JqZWN0IC1GaXJzdCAxCmlmICgtbm90ICRleGVjQWN0aW9uKSB7CiAgICB0aHJvdyAiU2NoZWR1bGVkIHRhc2sgJyRUYXNrTmFtZScgZG9lcyBub3QgY29udGFpbiBhbiBleGVjdXRhYmxlIGFjdGlvbi4iCn0KCiR3b3JraW5nRGlyZWN0b3J5ID0gJGV4ZWNBY3Rpb24uV29ya2luZ0RpcmVjdG9yeQppZiAoW3N0cmluZ106OklzTnVsbE9yV2hpdGVTcGFjZSgkd29ya2luZ0RpcmVjdG9yeSkpIHsKICAgICRjYW5kaWRhdGUgPSBTcGxpdC1QYXRoIC1QYXRoICRleGVjQWN0aW9uLkV4ZWN1dGUgLVBhcmVudAogICAgaWYgKFtzdHJpbmddOjpJc051bGxPcldoaXRlU3BhY2UoJGNhbmRpZGF0ZSkpIHsKICAgICAgICB0aHJvdyAiVW5hYmxlIHRvIGRldGVybWluZSB0aGUgd29ya2luZyBkaXJlY3RvcnkgZm9yIHNjaGVkdWxlZCB0YXNrICckVGFza05hbWUnLiIKICAgIH0KICAgICR3b3JraW5nRGlyZWN0b3J5ID0gJGNhbmRpZGF0ZQp9Cgp0cnkgewogICAgJGFnZW50Um9vdCA9IFJlc29sdmUtUGF0aCAtUGF0aCAkd29ya2luZ0RpcmVjdG9yeSAtRXJyb3JBY3Rpb24gU3RvcAp9IGNhdGNoIHsKICAgIHRocm93ICJUaGUgd29ya2luZyBkaXJlY3RvcnkgJyR3b3JraW5nRGlyZWN0b3J5JyBmb3Igc2NoZWR1bGVkIHRhc2sgJyRUYXNrTmFtZScgZG9lcyBub3QgZXhpc3QuIgp9Cgp0cnkgewogICAgJHJlcG9Sb290ID0gUmVzb2x2ZS1QYXRoIC1QYXRoIChKb2luLVBhdGggJGFnZW50Um9vdCAnLi5cLi4nKSAtRXJyb3JBY3Rpb24gU3RvcAp9IGNhdGNoIHsKICAgIHRocm93ICJVbmFibGUgdG8gcmVzb2x2ZSB0aGUgQm9yZWFsaXMgcmVwb3NpdG9yeSByb290IGZyb20gJyRhZ2VudFJvb3QnLiIKfQoKJHVwZGF0ZVNjcmlwdCA9IEpvaW4tUGF0aCAkcmVwb1Jvb3QgJ0JvcmVhbGlzLnBzMScKaWYgKC1ub3QgKFRlc3QtUGF0aCAtUGF0aCAkdXBkYXRlU2NyaXB0IC1QYXRoVHlwZSBMZWFmKSkgewogICAgdGhyb3cgIkJvcmVhbGlzLnBzMSB3YXMgbm90IGZvdW5kIGF0ICckdXBkYXRlU2NyaXB0Jy4iCn0KCldyaXRlLVZlcmJvc2UgIlJlc29sdmVkIHNjaGVkdWxlZCB0YXNrIHdvcmtpbmcgZGlyZWN0b3J5OiAkYWdlbnRSb290IgpXcml0ZS1WZXJib3NlICJCb3JlYWxpcyByZXBvc2l0b3J5IHJvb3Q6ICRyZXBvUm9vdCIKV3JpdGUtVmVyYm9zZSAiSW52b2tpbmcgJyR1cGRhdGVTY3JpcHQgLVNpbGVudFVwZGF0ZSciCgokdXBkYXRlU3VjY2VlZGVkID0gJGZhbHNlCgpQdXNoLUxvY2F0aW9uIC1QYXRoICRyZXBvUm9vdAp0cnkgewogICAgJiAkdXBkYXRlU2NyaXB0IC1TaWxlbnRVcGRhdGUKICAgICR1cGRhdGVTdWNjZWVkZWQgPSAkPwp9IGZpbmFsbHkgewogICAgUG9wLUxvY2F0aW9uCn0KCmlmICgtbm90ICR1cGRhdGVTdWNjZWVkZWQpIHsKICAgIHRocm93ICJCb3JlYWxpcy5wczEgLVNpbGVudFVwZGF0ZSBmYWlsZWQuIgp9CgpXcml0ZS1WZXJib3NlICJCb3JlYWxpcy5wczEgLVNpbGVudFVwZGF0ZSBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5LiI=", + "timeout_seconds": 3600, + "sites": { + "mode": "all", + "values": [] + }, + "variables": [], + "files": [], + "script_encoding": "base64" +} \ No newline at end of file diff --git a/Data/Agent/Roles/role_ScriptExec_SYSTEM.py b/Data/Agent/Roles/role_ScriptExec_SYSTEM.py index 39d0240..c0dbacb 100644 --- a/Data/Agent/Roles/role_ScriptExec_SYSTEM.py +++ b/Data/Agent/Roles/role_ScriptExec_SYSTEM.py @@ -39,54 +39,6 @@ def _find_borealis_root() -> Optional[str]: return None -def _launch_silent_update_task(): - if os.name != 'nt': - raise RuntimeError('Silent update is supported on Windows hosts only.') - - root = _find_borealis_root() - if not root: - raise RuntimeError('Unable to locate Borealis.ps1 on this agent.') - - ps_exe = os.path.expandvars(r"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") - if not os.path.isfile(ps_exe): - ps_exe = 'powershell.exe' - - task_name = f"Borealis Agent - SilentUpdate - {uuid.uuid4().hex}" - task_literal = _ps_literal(task_name) - ps_literal = _ps_literal(ps_exe) - root_literal = _ps_literal(root) - args_literal = _ps_literal('-NoProfile -ExecutionPolicy Bypass -File .\\Borealis.ps1 -SilentUpdate') - - cleanup_script = ( - "Start-Job -ScriptBlock { Start-Sleep -Seconds 600; " - f"try {{ Unregister-ScheduledTask -TaskName {task_literal} -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}} }} | Out-Null" - ) - - ps_script = "\n".join( - [ - "$ErrorActionPreference='Stop'", - f"$task = {task_literal}", - "try { Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue } catch {}", - f"$action = New-ScheduledTaskAction -Execute {ps_literal} -Argument {args_literal} -WorkingDirectory {root_literal}", - "$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DeleteExpiredTaskAfter (New-TimeSpan -Minutes 30)", - "$principal= New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest", - "Register-ScheduledTask -TaskName $task -Action $action -Settings $settings -Principal $principal -Force | Out-Null", - "Start-ScheduledTask -TaskName $task | Out-Null", - cleanup_script, - ] - ) - - flags = 0x08000000 if os.name == 'nt' else 0 - proc = subprocess.run( - [ps_exe, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps_script], - capture_output=True, - text=True, - creationflags=flags, - ) - if proc.returncode != 0: - stderr = proc.stderr or proc.stdout or 'scheduled task registration failed' - raise RuntimeError(stderr.strip()) - def _canonical_env_key(name: str) -> str: cleaned = re.sub(r"[^A-Za-z0-9_]", "_", (name or "").strip()) return cleaned.upper() @@ -328,46 +280,6 @@ class Role: def register_events(self): sio = self.ctx.sio - @sio.on('agent_silent_update') - async def _on_agent_silent_update(payload): - hostname = None - details = payload if isinstance(payload, dict) else {} - try: - import socket - - hostname = socket.gethostname() - target = (details.get('target_hostname') or '').strip().lower() - if target and target != hostname.lower(): - return - - loop = asyncio.get_running_loop() - await loop.run_in_executor(None, _launch_silent_update_task) - - try: - await sio.emit( - 'agent_silent_update_status', - { - 'request_id': details.get('request_id') or '', - 'hostname': hostname, - 'status': 'started', - }, - ) - except Exception: - pass - except Exception as e: - try: - await sio.emit( - 'agent_silent_update_status', - { - 'request_id': details.get('request_id') or '', - 'hostname': hostname or '', - 'status': 'error', - 'error': str(e), - }, - ) - except Exception: - pass - @sio.on('quick_job_run') async def _on_quick_job_run(payload): try: diff --git a/Data/Server/WebUI/src/Devices/Device_List.jsx b/Data/Server/WebUI/src/Devices/Device_List.jsx index a52f612..ba9d4a7 100644 --- a/Data/Server/WebUI/src/Devices/Device_List.jsx +++ b/Data/Server/WebUI/src/Devices/Device_List.jsx @@ -18,9 +18,7 @@ import { MenuItem, Popover, TextField, - Tooltip, - Snackbar, - Alert + Tooltip } from "@mui/material"; import MoreVertIcon from "@mui/icons-material/MoreVert"; import FilterListIcon from "@mui/icons-material/FilterList"; @@ -63,8 +61,6 @@ export default function DeviceList({ onSelectDevice }) { // Track selection by agent id to avoid duplicate hostname collisions const [selectedIds, setSelectedIds] = useState(() => new Set()); const [quickJobOpen, setQuickJobOpen] = useState(false); - const [updateLoading, setUpdateLoading] = useState(false); - const [updateStatus, setUpdateStatus] = useState({ open: false, severity: "success", message: "" }); // Saved custom views (from server) const [views, setViews] = useState([]); // [{id, name, columns:[id], filters:{}}] @@ -426,49 +422,6 @@ export default function DeviceList({ onSelectDevice }) { }); }; - const triggerSilentUpdate = useCallback(async () => { - const hostnames = Array.from( - new Set( - rows - .filter((r) => selectedIds.has(r.id)) - .map((r) => r.hostname) - .filter(Boolean) - ) - ); - if (!hostnames.length) return; - - setUpdateLoading(true); - try { - const resp = await fetch('/api/agents/silent_update', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ hostnames }), - }); - let payload = null; - try { - payload = await resp.json(); - } catch (err) { - payload = null; - } - if (!resp.ok || (payload && payload.error)) { - const msg = payload && payload.error ? payload.error : `HTTP ${resp.status}`; - throw new Error(msg); - } - const count = Array.isArray(payload?.results) ? payload.results.length : hostnames.length; - const requestId = typeof payload?.request_id === 'string' && payload.request_id ? payload.request_id : ''; - setUpdateStatus({ - open: true, - severity: 'success', - message: `Silent update triggered for ${count} device${count === 1 ? '' : 's'}${requestId ? ` (request ${requestId})` : ''}.`, - }); - } catch (err) { - const message = err instanceof Error ? err.message : 'Unknown error'; - setUpdateStatus({ open: true, severity: 'error', message: `Failed to trigger silent update: ${message}` }); - } finally { - setUpdateLoading(false); - } - }, [rows, selectedIds]); - // Column drag handlers const onHeaderDragStart = (colId) => (e) => { dragColId.current = colId; @@ -620,22 +573,6 @@ export default function DeviceList({ onSelectDevice }) { {/* Second row: Quick Job button aligned under header title */} -