Commit d99e366f by Szeberényi Imre

win_exe + *.ps1

parent fe93383e
......@@ -7,6 +7,8 @@ version.txt
.coverage
*~
*.log
build
dist
build/
dist/
*.spec
.venv/
.venv64/
\ No newline at end of file
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
......@@ -3,20 +3,31 @@
# Should be in autostart and run by the user logged in
import logging
import sys
from os.path import join
from os import environ
from utils import setup_logging
from windows.winutils import update_component, create_autostart_task
from notify import run_client, get_temp_dir
logger = logging.getLogger()
logfile = join(get_temp_dir(), "agent-client.log")
fh = logging.FileHandler(logfile)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
workdir = r"C:\circle"
comp_name = "circle-notify"
if getattr(sys, "frozen", False):
file = os.path.join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
logger.info("Update check: %s in %s", comp_name + ".exe", workdir)
update = update_component(comp_name, workdir, service_fn=create_autostart_task)
logger.info("Update status: %s", update)
if update == "exit":
sys.exit(1)
if __name__ == '__main__':
run_client()
#
import logging
from logging.handlers import NTEventLogHandler
from time import sleep
......@@ -7,38 +8,45 @@ import servicemanager
import socket
import sys
import winerror
import win32api
import win32event
import win32service
import win32serviceutil
from utils import setup_logging
from windows.winutils import getRegistryVal, get_windows_version, update_component
from windows.winutils import(
getRegistryVal, get_windows_version,
update_component, update_service_binpath
)
workdir = r"C:\circle"
workdir = r"C:\circle"
comp_name = "circle-watchdog"
if getattr(sys, "frozen", False):
logger = setup_logging(logfile=workdir + r"\watchdog.log")
file = join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = setup_logging()
fh = NTEventLogHandler("CIRCLE Watchdog")
fh = NTEventLogHandler(comp_name)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = getRegistryVal(
r"SYSTEM\\CurrentControlSet\\Services\\CIRCLE-Watchdog\\Parameters",
fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel",
"INFO"
)
logger.setLevel(level)
logger.info("%s loaded", __file__)
logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-watchdog"
_svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Watchdog"
_svc_description_ = "Watchdog for CIRCLE Agent"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
......@@ -50,7 +58,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
def check_service(checked_service):
return win32serviceutil.QueryServiceStatus(checked_service)[1] == 4
def start_service():
def start_service(checked_service):
win32serviceutil.StartService(checked_service)
timo_base = 20
......@@ -61,7 +69,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
if not check_service(checked_service):
logger.info("Service %s is not running.", checked_service)
try:
start_service()
start_service(checked_service)
timo = timo_base
logger.info("Service %s restarted.", checked_service)
except Exception:
......@@ -80,11 +88,12 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
logger.info("%s starting", __file__)
working_dir = r"C:\circle"
exe = "circle-watchdog.exe"
exe_path = join(working_dir, exe)
logger.debug("Update check: %s %s", self._svc_name_, exe_path)
update = update_component(self._svc_name_ + ".state", workdir)
u = win32api.GetUserName()
logger.info("Running as user: %s", u)
logger.info("Update check: %s in %s", comp_name + ".exe", workdir)
update = update_component(comp_name, workdir, service_fn=update_service_binpath)
logger.info("Update status: %s", update)
if update == "exit":
# Service updated, Restart needed
......
......@@ -9,24 +9,37 @@ import win32service
import win32serviceutil
import logging
from logging.handlers import NTEventLogHandler
from twisted.internet import reactor
#import agent, reactor
from agent import main as agent_main
from twisted.internet import reactor
from windows.winutils import getRegistryVal
workdir = r"C:\circle"
comp_name = "circle-agent"
#
# angent inported and it initilized the logger
logger = logging.getLogger(__name__)
logger = logging.getLogger()
fh = NTEventLogHandler("CIRCLE Agent")
fh = NTEventLogHandler(comp_name)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = getRegistryVal(
fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel",
"INFO"
)
#logger.propagate = False
#logger.setLevel('INFO')
logger.info("%s loaded", __file__)
logger.setLevel(level)
logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-agent"
_svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Agent"
_svc_description_ = "CIRCLE cloud contextualization agent"
......
......@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from os import environ, chdir
from os.path import join
import logging
import platform
import subprocess
......@@ -12,11 +13,15 @@ from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from utils import setup_logging
workdir = r"C:\circle"
comp_name = "circle-agent"
if getattr(sys, "frozen", False):
logger = setup_logging(logfile=r"C:\Circle\agent.log")
file = join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = setup_logging()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
......@@ -53,7 +58,7 @@ if win:
)
logger.setLevel(level)
system = get_windows_version()
system = "DEBUG"
# system = "DEBUG"
from context import get_context, get_serial # noqa
......
# circle-agent-install.ps1 (v1.1)
# Installs and starts CIRCLE agent + watchdog, and registers circle-notify.exe as logon task
# Run from elevated PowerShell as cloud user
# ASCII-only comments
param(
[switch]$WhatIf
)
$ErrorActionPreference = "Stop"
function Say($msg="") { Write-Host $msg }
function Fail($msg) {
Write-Host "ERROR: $msg" -ForegroundColor Red
exit 1
}
function Require-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Fail "Run this script as Administrator."
}
}
function Run-Step($desc, [ScriptBlock]$action) {
if ($WhatIf) {
Say ("[WHATIF] {0}" -f $desc)
return
}
Say ("[DO] {0}" -f $desc)
try {
& $action
} catch {
Fail ("{0} failed: {1}" -f $desc, $_.Exception.Message)
}
}
function Check-File($path) {
if (-not (Test-Path $path)) { Fail "Missing file: $path" }
}
function Check-Dir($path) {
if (-not (Test-Path $path)) { Fail "Directory not found: $path" }
}
function Check-ServiceRunning($name) {
$s = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $s) { Fail "Service not found: $name" }
if ($s.Status -ne "Running") {
Fail "Service $name is not running (state=$($s.Status))"
}
}
Require-Admin
$baseDir = "C:\circle"
$agentExe = Join-Path $baseDir "circle-agent.exe"
$watchdogExe = Join-Path $baseDir "circle-watchdog.exe"
$notifyExe = Join-Path $baseDir "circle-notify.exe"
$taskName = "CIRCLE circle-notify (Logon)"
Say "=== CIRCLE Agent install ==="
Say ("User: {0}" -f $env:USERNAME)
Say ("Working dir: {0}" -f $baseDir)
Say ("Mode: {0}" -f $(if ($WhatIf) { "WhatIf (dry-run)" } else { "Apply" }))
Say ""
# --- Preconditions ---
Check-Dir $baseDir
Check-File $agentExe
Check-File $watchdogExe
Check-File $notifyExe
# Show what would run (useful in WhatIf)
Say "Plan:"
Say (" - cd {0}" -f $baseDir)
Say (" - {0} --startup auto install" -f $agentExe)
Say (" - {0} --startup auto install" -f $watchdogExe)
Say (" - {0} start" -f $agentExe)
Say (" - {0} start" -f $watchdogExe)
Say (" - Register Scheduled Task: {0} -> {1}" -f $taskName, $notifyExe)
Say ""
Run-Step "Set location to $baseDir" { Set-Location $baseDir }
# --- Install services ---
Run-Step "Install circle-agent service (startup auto)" {
& $agentExe --startup auto install | Out-Null
}
Run-Step "Install circle-watchdog service (startup auto)" {
& $watchdogExe --startup auto install | Out-Null
}
# --- Start services ---
Run-Step "Start circle-agent service" {
& $agentExe start | Out-Null
}
Run-Step "Start circle-watchdog service" {
& $watchdogExe start | Out-Null
}
# --- Verify services (skip on WhatIf) ---
if (-not $WhatIf) {
Check-ServiceRunning "circle-agent"
Check-ServiceRunning "circle-watchdog"
} else {
Say "[WHATIF] Skip service running checks (no changes applied)."
}
# --- Register circle-notify as logon task ---
Run-Step "Register Scheduled Task '$taskName' for circle-notify.exe" {
$action = New-ScheduledTaskAction -Execute $notifyExe
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Run only when user is logged on (no password prompt)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 3)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
# --- Verify task (skip on WhatIf) ---
if (-not $WhatIf) {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if (-not $task) { Fail "Scheduled Task '$taskName' not found after registration" }
} else {
Say "[WHATIF] Skip task verification (no changes applied)."
}
Say ""
if ($WhatIf) {
Say "DRY-RUN OK: Preconditions passed; no changes were made."
} else {
Say "SUCCESS:"
Say " - circle-agent service installed and running"
Say " - circle-watchdog service installed and running"
Say " - circle-notify scheduled task registered (logon, restart-on-failure)"
Say ""
Say "NOTE: User logoff/logon required for circle-notify.exe to start."
}
......@@ -299,7 +299,7 @@ if win:
message="This VM expiring soon",
yes_label="Renew",
no_label="Cancel",
timeout_seconds=60, )
timeout_seconds=120, )
if ans == "yes":
ret = accept(line)
prompt_yes_no(
......@@ -307,7 +307,7 @@ if win:
message=ret['msg'],
yes_label="OK",
no_label="",
timeout_seconds=10 if ret['ret'] else 60)
timeout_seconds=30 if ret['ret'] else 60)
class SubFactory(protocol.ReconnectingClientFactory):
......
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --exclude-module tkinter --exclude-module _tkinter -F agent-wdog-winservice.py
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --exclude-module tkinter --exclude-module _tkinter -F agent-winservice.py
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil -F agent-notify.pyw
\ No newline at end of file
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --hidden-import winotify -F agent-notify.pyw
\ No newline at end of file
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
......@@ -19,9 +19,8 @@ from twisted.internet import reactor
from .network import change_ip_windows
from context import BaseContext
from windows.winutils import (
is_frozen_exe, copy_running_exe,
update_service_binpath, servicePostUpdate,
start_process_async
is_frozen_exe, update_service_binpath,
update_component, start_process_async
)
try:
......@@ -34,15 +33,15 @@ logger = logging.getLogger(__name__)
class Context(BaseContext):
service_name = "CIRCLE-agent"
comp_name = "circle-agent"
workdir = r"C:\circle"
update_cmd = "update.ps1"
exe = "circle-agent.exe"
@staticmethod
def postUpdate():
exe_path = join(Context.workdir, Context.exe)
return servicePostUpdate(Context.service_name, exe_path)
ret = update_component(Context.comp_name, Context.workdir, service_fn=update_service_binpath)
logger.debug("update:component: %s", ret)
return ret == "exit"
@staticmethod
def change_password(password):
......@@ -133,13 +132,14 @@ class Context(BaseContext):
start_process_async(executable, workdir=Context.workdir)
else:
if executable.startswith("0_"):
old_exe = update_service_binpath("CIRCLE-agent", join(Context.workdir, executable))
logger.info('%s Updated', old_exe)
ret = update_component(Context.comp_name, Context.workdir, img_pendig=executable, service_fn=update_service_binpath)
logger.info("Update: %s", ret)
if exists(join(Context.workdir, Context.update_cmd)):
logger.debug("starting %s in %s", Context.update_cmd, Context.workdir)
start_process_async(Context.update_cmd, workdir=Context.workdir, delay_seconds=60)
Context.exit_code = 1
reactor.callLater(0, reactor.stop)
if ret == "exit":
Context.exit_code = 1
reactor.callLater(0, reactor.stop)
@staticmethod
def ipaddresses():
......
......@@ -37,6 +37,73 @@ def update_service_binpath(service_name: str, exe_path: str) -> str:
return old_executable
import subprocess
def create_autostart_task(
task_name: str,
exe_path: str,
workdir: str = r"c:\circle",
username: str = "cloud",
restart_interval_minutes: int = 1,
restart_count: int = 5
):
"""
Create/Update a Scheduled Task for a local user (default: .\\cloud) that:
- runs at user logon
- restarts on failure (bounded by restart_count to avoid infinite crash loops)
- can start with a logon delay
Safe Mode handling should be done inside the target program (exit 0 if in safe mode).
"""
logger.debug("autostar:update: %s %s", task_name, exe_path)
# Force local account form for reliability
# if "\\" not in username:
# username = f".\\{username}"
ps_script = f"""
$ErrorActionPreference = "Stop"
$action = New-ScheduledTaskAction `
-Execute "{exe_path}" `
-WorkingDirectory "{workdir}"
$trigger = New-ScheduledTaskTrigger `
-AtLogOn `
-User "{username}"
$settings = New-ScheduledTaskSettingsSet `
-RestartCount {int(restart_count)} `
-RestartInterval (New-TimeSpan -Minutes {int(restart_interval_minutes)}) `
-MultipleInstances IgnoreNew `
-StartWhenAvailable `
-ExecutionTimeLimit ([TimeSpan]::Zero)
$principal = New-ScheduledTaskPrincipal `
-UserId "{username}" `
-LogonType Interactive `
-RunLevel Highest
$task = New-ScheduledTask `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal
Register-ScheduledTask `
-TaskName "{task_name}" `
-InputObject $task `
-Force | Out-Null
"""
res = subprocess.run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps_script],
capture_output=True,
text=True
)
logger.error("PS rc=%s stdout=%s stderr=%s", res.returncode, res.stdout, res.stderr)
res.check_returncode()
return None
def copy_running_exe(dest: str) -> bool:
"""
Startup helper:
......@@ -56,15 +123,6 @@ def copy_running_exe(dest: str) -> bool:
copy2(current_exe, dest)
return True
def servicePostUpdate(service_name, exe_path):
logger.debug("Running exe %s", sys.executable)
if is_frozen_exe() and copy_running_exe(exe_path):
logger.debug("The running agent copyed to %s", exe_path)
old_exe = update_service_binpath(service_name, exe_path)
logger.debug("%s service binpath updated %s -> %s", service_name, old_exe, exe_path)
return True
return False
def getRegistryVal(reg_path: str, name: str, default=None):
"""
Read HKLM\\<reg_path>\\<name> and return its value.
......@@ -135,19 +193,21 @@ def start_process_async(path, args=None, workdir=None, delay_seconds=0):
except Exception:
logger.exception(f"Execution failed for: {path} (workdir={workdir})")
def file_is_newer(file_a, file_b):
"""
Returns True if file_a is newer than file_b.
Raises FileNotFoundError if any file does not exist.
Returns True if file_a is newer than file_b of file_b is not exist
Retirns False otherwise or file_a does not exist
"""
if not os.path.exists(file_a):
raise FileNotFoundError(file_a)
return False
if not os.path.exists(file_b):
raise FileNotFoundError(file_b)
return True
return os.path.getmtime(file_a) > os.path.getmtime(file_b)
def start_delayed_process(command, delay_seconds, workdir=None):
"""
Starts a delayed process asynchronously using cmd.exe (no threads).
......@@ -185,6 +245,7 @@ def load_json(path, default=None):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def save_json(path, data):
"""
Saves dict to JSON file atomically.
......@@ -216,7 +277,7 @@ def save_json(path, data):
pass
def update_component(state_file: str, base_dir: str) -> str:
def update_component(name: str, base_dir: str, img_pending=None, service_fn=None) -> str:
"""
Component self-update state handler.
Uses a per-component JSON state file to coordinate a two-phase update:
......@@ -229,27 +290,24 @@ def update_component(state_file: str, base_dir: str) -> str:
and user-mode processes. The running image is identified by comparing
sys.executable with the configured image names.
"""
if not is_frozen_exe():
return "Not frozen"
state_file = name + ".state"
state_file = os.path.join(base_dir, state_file)
img_pending = name + "_.exe" if img_pending is None else img_pending
try:
state = load_json(state_file)
except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
logger.error("Cannot load state: %s", e)
return "State file error"
state["last_checked"] = datetime.now(timezone.utc).isoformat() + "Z"
img = state.get("img", name + ".exe")
img_pending = state.get("img_pending", img_pending)
status = state.get("status", "idle").lower()
save_json(state_file, state)
img = state.get("img")
img_pending = state.get("img_pending")
status = (state.get("status") or "idle").lower()
service = state.get("service")
if not img or not img_pending:
return "Miisng basic update info"
# raise ValueError("status json must contain 'img' and 'img_pending'")
img_path = os.path.join(base_dir, img)
img_pending_path = os.path.join(base_dir, img_pending)
......@@ -268,16 +326,17 @@ def update_component(state_file: str, base_dir: str) -> str:
if newer:
logger.debug("newer: %s", status);
if status == "idle" and is_self_img_pending:
status = "pending" # img_pending started first
if is_self_img_pending and status != "pending":
status = "pending" # img_pending started first
if status == "idle":
state["status"] = "pending"
save_json(state_file, state)
if service:
# set pending image as service image
update_service_binpath(service, img_pending_path)
if service_fn:
# set pending image as serving image
old_exe = service_fn(name, img_pending_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_pending_path)
return "exit"
else:
# start pending image after 60 sec, then the caller should exit
......@@ -288,21 +347,27 @@ def update_component(state_file: str, base_dir: str) -> str:
if status == "pending" and is_self_img_pending:
# we are running as img_pending -> safe to overwrite img (img is not running)
copy2(img_pending_path, img_path)
logger.debug("Copy: %s ---> %s", img_pending_path, img_path)
logger.info("Copy: %s ---> %s", img_pending_path, img_path)
state["status"] = "copied"
save_json(state_file, state)
if service:
# set standard image as service image
update_service_binpath(service, img_path)
if service_fn:
# set standard image as serving image
old_exe = service_fn(name, img_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_path)
return "exit"
else:
# start standard image after 60 sec
logger.debug("Start porcess: %s", imp)
start_delayed_process(img, workdir=base_dir, delay_seconds=60)
return "copied_img_pending_to_img_and_started_img"
if status == "copyed" and is_self_img:
state["status"] = "idle"
save_json(state_file, state)
return "reset_to_idle1"
return "newer_no_action"
else:
if status != "idle":
......@@ -310,7 +375,9 @@ def update_component(state_file: str, base_dir: str) -> str:
save_json(state_file, state)
return "reset_to_idle"
return "idle_no_change"
if is_self_img_pending:
return "this_cannot_happen"
return "idle_no_change2"
def get_windows_version():
......@@ -324,21 +391,21 @@ def get_windows_version():
# Windows 7
if major == 6 and minor == 1:
return "Windows_7"
return "Win_7"
# Windows 8 / 8.1
if major == 6 and minor in (2, 3):
return "Windows_8"
return "Win_8"
# Windows 10 / 11
if major == 10:
# Windows 11 starts at build 22000
if build >= 22000:
return "Windows_11"
return "Win_11"
else:
return "Windows_10"
return "Win_10"
return f"Windows_{major}_{minor}_{build})"
return f"Win_{major}_{minor}_{build})"
if __name__ == '__main__':
logging.basicConfig(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment