Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Arenas 70a9d4ad51 implements reconnection in ogagent for oglive
ogagent/pipeline/head This commit looks good Details
2026-04-30 13:42:02 +02:00
20 changed files with 258 additions and 770 deletions

View File

@ -5,48 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [8.5.10] - 2026-09-30 ## [8.5.3] - 2026-04-30
### Fixed
- **ogLive**: Resuelta una condición de carrera que provocaba fallos de inicialización del servidor gráfico X al recibir peticiones concurrentes o ultra-rápidas de carga de navegador desde ogCore. Se ha implementado un semáforo/control de estado del proceso del navegador para ignorar peticiones duplicadas redundantes.
## [8.5.9] - 2026-07-23
### Fixed
- Preserve original exit code in Windows PowerShell script wrapper when redirecting streams, ensuring ogcore receives failure status (e.g., CommandNotFoundException).
- Prevent raw PowerShell CLIXML output from leaking into stderr on Windows when a command fails non-interactively.
- Removed the `START_SCRIPT` webhook entirely from the script launching process for both admin (`client=false`) and user-session (`client=true`) paths. This eliminates the race condition that caused duplicate traces in `ogcore`, as `ogcore` now marks traces as `IN_PROGRESS` synchronously.
## [8.5.6] - 2026-07-21
### Added
- Notify `ogcore` via webhook when natively executed scripts finish, bringing `ogagent` in line with `ogLive` functionality (resolves stuck traces in "Enviada" state).
## [8.5.5] - 2026-05-13
### Changed
- Promote all ogLive action handlers (`process_CrearImagen`, `process_RestaurarImagen`, `process_EjecutarScript`, etc.) from `DEBUG` to `INFO` so that the action trace is visible without enabling verbose logging. Polling operations (`process_status`, `process_Sondeo`) remain at `DEBUG`.
- Promote job completion log in `oglive_worker.mon()` from `DEBUG` to `INFO`, now including the job result.
- When `interfaceAdmin()` fails, log the script stdout at `WARNING` in addition to the existing `ERROR` for stderr, providing full diagnostic context without requiring a debug-level rerun.
## [8.5.5] - 2026-05-20
### Changed
- On machines with multiple network interfaces, the agent now selects the interface whose IP the OS routing table would use to reach ogcore, instead of always picking the first interface returned by the OS. This ensures the agent registers with the correct IP on multi-homed machines. If the routing query fails (network not ready yet) the detection loop keeps retrying rather than accepting a wrong interface. Falls back to the first available interface only when the routing query succeeds but no interface matches (e.g. NAT or VPN).
## [8.5.4] - 2026-05-11
### Fixed ### Fixed
- Network interface detection no longer blocks activation with a hard 5-minute timeout. `onActivation()` now returns immediately and launches a background thread (`_setup_network`) that retries indefinitely until a network interface is available, then registers with ogcore. This fixes a race condition on Windows machines where the network stack was not ready when the service started; the workaround of configuring a delayed service start is no longer needed. The same improvement applies to Linux and macOS where the service could also start before DHCP had assigned an address. - Reconnect to ogcore after a connection loss in oglive: the monitoring thread now detects when contact is restored and re-sends `InclusionCliente` so ogcore marks the client as OG_LIVE and refreshes its token
- Guard against `AttributeError` in `onDeactivation()`, `onLogin()` and `onLogout()` if the service is stopped or a session event fires before the network interface has been detected.
## [8.5.3] - 2026-05-07
### Added
- Report `busy` and `st` fields in the periodic heartbeat ping to ogCore, so ogCore can update the client status without waiting for a command response. `busy` is `true` while a job is running; `st` indicates the base OS type (`OGL`, `LNX`, `WIN`, `OSX`)
## [8.5.2] - 2026-04-28 ## [8.5.2] - 2026-04-28
@ -590,4 +553,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Initial release for OpenGnsys Agent - Initial release for OpenGnsys Agent

View File

@ -67,20 +67,6 @@ pipeline {
} }
} }
} }
stage('Generate Changelog (Devel)') {
when {
branch 'devel'
}
steps {
script {
def devName = params.DEV_NAME ? params.DEV_NAME : env.DEFAULT_DEV_NAME
def devEmail = params.DEV_EMAIL ? params.DEV_EMAIL : env.DEFAULT_DEV_EMAIL
generateDebianChangelog(env.BUILD_DIR, devName, devEmail, "devel")
sh "mkdir -p ${env.BUILD_DIR}/debian/"
sh "cp ${env.BUILD_DIR}/debian/changelog ${env.BUILD_DIR}/linux/debian/"
}
}
}
stage('Build') { stage('Build') {
steps { steps {
dir ("${env.BUILD_DIR}") { dir ("${env.BUILD_DIR}") {
@ -126,32 +112,8 @@ pipeline {
} }
} }
} }
stage ('Publish to Debian Repository (Devel)') {
when {
branch 'devel'
}
agent { label 'debian-repo' }
steps {
script {
def versionPattern = "-${env.BUILD_NUMBER}~devel"
publicarEnAptly('/var/tmp/opengnsys/debian-repo/ogagent', 'devel', versionPattern)
}
}
}
} }
post { post {
success {
script {
if (env.BRANCH_NAME == 'devel') {
build job: 'Aptly publish develop repository',
wait: false,
parameters: [
string(name: 'TRIGGERED_BY', value: "${env.JOB_NAME}-${env.BUILD_NUMBER}")
]
}
}
}
always { always {
notifyBuildStatus('narenas@qindel.com') notifyBuildStatus('narenas@qindel.com')
} }

View File

@ -38,7 +38,6 @@ install-ogagent:
cp $(SOURCEDIR)/OGAgentUser.py $(LIBDIR) cp $(SOURCEDIR)/OGAgentUser.py $(LIBDIR)
# QT Dialogs & resources # QT Dialogs & resources
cd $(SOURCEDIR) && python3 update.py
cp $(SOURCEDIR)/*_ui.py $(LIBDIR) cp $(SOURCEDIR)/*_ui.py $(LIBDIR)
# Version file # Version file
cp $(SOURCEDIR)/VERSION $(LIBDIR) cp $(SOURCEDIR)/VERSION $(LIBDIR)

View File

@ -1,111 +1,3 @@
ogagent (8.5.9-1) stable; urgency=medium
* Removed START_SCRIPT webhook to fix race conditions causing duplicate traces
* Added script completion webhook to resolve stuck traces in Enviada state
-- OpenGnsys developers <info@opengnsys.es> Wed, 29 Jul 2026 13:58:00 +0200
ogagent (8.5.5-1) stable; urgency=medium
* Remove obsolete dependencies (six, prctl, libxss1) to support modern Ubuntu releases (like 26.04)
* Update policykit-1 to polkitd
-- OpenGnsys developers <info@opengnsys.es> Thu, 09 Jul 2026 12:00:00 +0200
ogagent (8.5.4-1) stable; urgency=medium
* Fix network detection race condition on slow-booting machines: activation
now launches a background thread that retries indefinitely until a network
interface is available, removing the hard 5-minute timeout that caused the
agent to fail permanently on Windows (and potentially Linux/macOS) when the
network stack was not ready at service start time
* Guard onDeactivation(), onLogin() and onLogout() against AttributeError
when the service is stopped before the network interface has been detected
-- OpenGnsys developers <info@opengnsys.es> Mon, 11 May 2026 00:00:00 +0200
ogagent (8.5.3-1) stable; urgency=medium
* Report busy state and OS type in the periodic heartbeat ping to ogcore
-- OpenGnsys developers <info@opengnsys.es> Thu, 07 May 2026 00:00:00 +0200
ogagent (8.5.2-1) stable; urgency=medium
* Reconnect to ogcore after a connection loss and re-send ogagent/started
* Fix crash in REST client when a network-level error occurs and e.response is None
-- OpenGnsys developers <info@opengnsys.es> Tue, 28 Apr 2026 00:00:00 +0200
ogagent (8.5.1-1) stable; urgency=medium
* Start the HTTP server only after all modules complete activation
-- OpenGnsys developers <info@opengnsys.es> Tue, 28 Apr 2026 00:00:00 +0200
ogagent (8.5.0-1) stable; urgency=medium
* Write the authentication token to /tmp/ogagent.token on oglive startup only
-- OpenGnsys developers <info@opengnsys.es> Mon, 23 Mar 2026 00:00:00 +0100
ogagent (8.4.3-1) stable; urgency=medium
* Fix script execution on Windows when C:\tmp does not exist: use
tempfile.gettempdir() instead of hardcoded /tmp for the EjecutarScript
job_id handoff file
-- OpenGnsys developers <info@opengnsys.es> Mon, 23 Mar 2026 00:00:00 +0100
ogagent (8.4.2-1) stable; urgency=medium
* Log the fail condition for network detection
* Refresh the menu after calling getConfiguration
-- OpenGnsys developers <info@opengnsys.es> Thu, 16 Oct 2025 00:00:00 +0200
ogagent (8.4.1-1) stable; urgency=medium
* Don't return HTTP 500 on POST requests with an empty body
-- OpenGnsys developers <info@opengnsys.es> Wed, 15 Oct 2025 00:00:00 +0200
ogagent (8.4.0-1) stable; urgency=medium
* Show a user-friendly error if the agent fails to start
-- OpenGnsys developers <info@opengnsys.es> Mon, 29 Sep 2025 00:00:00 +0200
ogagent (8.3.3-1) stable; urgency=medium
* Exit from the infinite loop
-- OpenGnsys developers <info@opengnsys.es> Tue, 23 Sep 2025 00:00:00 +0200
ogagent (8.3.2-1) stable; urgency=medium
* Wait for children within a loop, not just once
-- OpenGnsys developers <info@opengnsys.es> Thu, 18 Sep 2025 00:00:00 +0200
ogagent (8.3.1-1) stable; urgency=medium
* Fix EOF bug in interfaceAdm()
-- OpenGnsys developers <info@opengnsys.es> Thu, 11 Sep 2025 00:00:00 +0200
ogagent (8.3.0-1) stable; urgency=medium
* Put InventarioHardware to work
-- OpenGnsys developers <info@opengnsys.es> Fri, 29 Aug 2025 00:00:00 +0200
ogagent (8.2.0-1) stable; urgency=medium
* Git operations support
-- OpenGnsys developers <info@opengnsys.es> Fri, 29 Aug 2025 00:00:00 +0200
ogagent (8.1.2-1) stable; urgency=medium ogagent (8.1.2-1) stable; urgency=medium
* Parameter fixes * Parameter fixes

View File

@ -2,7 +2,7 @@ Source: ogagent
Section: admin Section: admin
Priority: optional Priority: optional
Maintainer: OpenGnsys developers <info@opengnsys.es> Maintainer: OpenGnsys developers <info@opengnsys.es>
Build-Depends: debhelper (>= 7), po-debconf, pyqt6-dev-tools Build-Depends: debhelper (>= 7), po-debconf
Standards-Version: 3.9.2 Standards-Version: 3.9.2
Homepage: https://opengnsys.es/ Homepage: https://opengnsys.es/
@ -11,7 +11,7 @@ Section: admin
Priority: optional Priority: optional
Architecture: all Architecture: all
Depends: Depends:
polkitd | policykit-1 (>= 0.100), python3 (>=3.4), python3-pyqt6, python3-requests, policykit-1 (>= 0.100), python3 (>=3.4) | python (>= 3.4), python3-pyqt6, python3-requests,
python3-distro, zenity, ${misc:Depends} python3-six, python3-prctl, python3-distro, libxss1, zenity, ${misc:Depends}
Description: OpenGnsys Agent for Operating Systems Description: OpenGnsys Agent for Operating Systems
This package provides the required components to allow this machine to work on an environment managed by OpenGnsys. This package provides the required components to allow this machine to work on an environment managed by OpenGnsys.

View File

@ -1,238 +0,0 @@
# We need http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin
# Copy inside the two x86_xxxxx folders inside nsis plugins folder
Name "OpenGnsys Agent"
# OpenGnsys Agent version
!define /file OGA_VERSION "src\VERSION"
!define /file OGA_WINVERSION "windows\VERSION"
# General Symbol Definitions
!define REGKEY "SOFTWARE\OGAgent"
!if ${OGA_VERSION} == ${OGA_WINVERSION}
!define VERSION ${OGA_WINVERSION}.0
!else
!define VERSION ${OGA_WINVERSION}.1
!endif
!define COMPANY "OpenGnsys Project"
!define URL https://opengnsys.es
!define CONFIGFILE "cfg\ogagent.cfg"
# MultiUser Symbol Definitions
!define MULTIUSER_EXECUTIONLEVEL Admin
!define MULTIUSER_INSTALLMODE_COMMANDLINE
!define MULTIUSER_INSTALLMODE_INSTDIR OGAgent
!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "${REGKEY}"
!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUE "Path"
# MUI Symbol Definitions
!define MUI_ICON "src\img\oga.ico"
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_UNICON "src\img\oga.ico"
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
!define MUI_LANGDLL_REGISTRY_ROOT HKLM
!define MUI_LANGDLL_REGISTRY_KEY ${REGKEY}
!define MUI_LANGDLL_REGISTRY_VALUENAME InstallerLanguage
# Included files
!include MultiUser.nsh
!include Sections.nsh
!include MUI2.nsh
!include nsDialogs.nsh
!include LogicLib.nsh
!include Filefunc.nsh
# Reserved Files
!insertmacro MUI_RESERVEFILE_LANGDLL
# Variables
Var StartMenuGroup
Var SERVERIP
Var SERVERIP_VALUE
# Installer pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE src\license.txt
Page custom "ParamsPage" "ParamsPageLeave"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
# Installer languages
!insertmacro MUI_LANGUAGE English
!insertmacro MUI_LANGUAGE Spanish
!insertmacro MUI_LANGUAGE French
!insertmacro MUI_LANGUAGE German
# Installer attributes
BrandingText "OpenGnsys"
OutFile OGAgentSetup-${OGA_VERSION}.exe
InstallDir OGAgent
CRCCheck on
XPStyle on
ShowInstDetails hide
VIProductVersion "${VERSION}.0.0"
VIAddVersionKey /LANG=${LANG_ENGLISH} ProductName "OGAgent"
VIAddVersionKey /LANG=${LANG_ENGLISH} ProductVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ENGLISH} CompanyName "${COMPANY}"
VIAddVersionKey /LANG=${LANG_ENGLISH} CompanyWebsite "${URL}"
VIAddVersionKey /LANG=${LANG_ENGLISH} FileVersion "${VERSION}"
VIAddVersionKey /LANG=${LANG_ENGLISH} FileDescription "OpenGnsys Agent installer"
VIAddVersionKey /LANG=${LANG_ENGLISH} LegalCopyright "(c) 2015 Virtual Cable S.L.U."
InstallDirRegKey HKLM "${REGKEY}" Path
ShowUninstDetails show
# Installer sections
Section -Main SEC0000
SetShellVarContext all
SetOutPath $INSTDIR
SetOverwrite on
File /r src\dist\OGAgent\*.*
File windows\VC_redist.x64.exe
File src\VERSION
WriteRegStr HKLM "${REGKEY}\Components" Main 1
SectionEnd
Section -post SEC0001
WriteIniStr $INSTDIR\${CONFIGFILE} "opengnsys" "remote" "https://$SERVERIP_VALUE/opengnsys/rest"
SetShellVarContext current
WriteRegStr HKLM "${REGKEY}" Path $INSTDIR
SetOutPath $INSTDIR
WriteUninstaller $INSTDIR\OGAgentUninstaller.exe
SetOutPath $SMPROGRAMS\$StartMenuGroup
CreateShortcut "$SMPROGRAMS\$StartMenuGroup\$(^UninstallLink).lnk" $INSTDIR\OGAgentUninstaller.exe
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayName "$(^Name)"
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayVersion "${VERSION}"
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" Publisher "${COMPANY}"
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" URLInfoAbout "${URL}"
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" DisplayIcon $INSTDIR\OGAgentUninstaller.exe
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" UninstallString $INSTDIR\OGAgentUninstaller.exe
WriteRegStr HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" OGAgentTool $INSTDIR\OGAgentUser.exe
WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoModify 1
WriteRegDWORD HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" NoRepair 1
ExecWait '"$INSTDIR\VC_redist.x64.exe" /passive /norestart'
# Add the application to the firewall exception list - All Networks - All IP Version - Enabled
# SimpleFC::AddApplication "OpenGnsys Agent Service" "$INSTDIR\OGAgentService.exe" 0 2 "" 1
# SimpleFC::AdvAddRule [name] [description] [protocol] [direction]
# [status] [profile] [action] [application] [service_name] [icmp_types_and_codes]
# [group] [local_ports] [remote_ports] [local_address] [remote_address]
#
SimpleFC::AdvAddRule "OpenGnsys Agent Firewall rules" "Firewall rules for OpenGnsys Agent interaction with broker." "6" "1" \
"1" "7" "1" "$INSTDIR\OGAgentService.exe" "" "" \
"" "" "" "" ""
Pop $0 ; return error(1)/success(0)
# Disable fast boot on Windows 10, if registry key exists.
ReadRegDWORD $0 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Power" HiberbootEnabled
IfErrors 0 +2
WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Power" HiberbootEnabled 0
# Install service
nsExec::Exec /OEM "$INSTDIR\OGAgentService.exe --startup auto install" # Add service after installation
Exec "net start ogagent"
Exec "$INSTDIR\OGAgentUser.exe"
SectionEnd
# Macro for selecting uninstaller sections
!macro SELECT_UNSECTION SECTION_NAME UNSECTION_ID
Push $R0
ReadRegStr $R0 HKLM "${REGKEY}\Components" "${SECTION_NAME}"
StrCmp $R0 1 0 next${UNSECTION_ID}
!insertmacro SelectSection "${UNSECTION_ID}"
GoTo done${UNSECTION_ID}
next${UNSECTION_ID}:
!insertmacro UnselectSection "${UNSECTION_ID}"
done${UNSECTION_ID}:
Pop $R0
!macroend
# Uninstaller sections
Section /o -un.Main UNSEC0000
nsExec::Exec "taskkill /F /IM OGAgentUser.exe /T"
nsExec::Exec /OEM "$INSTDIR\OGAgentService.exe stop" # Stops the service prior uninstall
nsExec::Exec /OEM "$INSTDIR\OGAgentService.exe remove" # Removes the service prior uninstall
nsExec::Exec "taskkill /F /IM OGAgentService.exe /T"
Delete /REBOOTOK "$INSTDIR\*.*"
DeleteRegValue HKLM "${REGKEY}\Components" Main
DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" OGAgentTool
SectionEnd
Section -un.post UNSEC0001
# Remove application from the firewall exception list
# SimpleFC::RemoveApplication "$INSTDIR\OGAgentService.exe"
SimpleFC::AdvRemoveRule "OpenGnsys Agent Firewall rules"
Pop $0 ; return error(1)/success(0)
SetShellVarContext current
StrCpy $StartMenuGroup "OpenGnsys Agent"
DeleteRegKey HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)"
Delete /REBOOTOK "$SMPROGRAMS\$StartMenuGroup\$(^UninstallLink).lnk"
Delete /REBOOTOK $INSTDIR\OGAgentUninstaller.exe
DeleteRegValue HKLM "${REGKEY}" Path
DeleteRegKey /IfEmpty HKLM "${REGKEY}\Components"
DeleteRegKey /IfEmpty HKLM "${REGKEY}"
RmDir /REBOOTOK $SMPROGRAMS\$StartMenuGroup
SetShellVarContext all
RmDir /r /REBOOTOK $INSTDIR
SectionEnd
# Installer functions
Function .onInit
InitPluginsDir
Call GetParameters
StrCpy $StartMenuGroup "OpenGnsys Agent"
!insertmacro MUI_LANGDLL_DISPLAY
!insertmacro MULTIUSER_INIT
FunctionEnd
# Uninstaller functions
Function un.onInit
StrCpy $StartMenuGroup "OpenGnsys Agent"
!insertmacro MUI_UNGETLANGUAGE
!insertmacro MULTIUSER_UNINIT
!insertmacro SELECT_UNSECTION Main ${UNSEC0000}
FunctionEnd
# Parameters dialog box
Function ParamsPage
!insertmacro MUI_HEADER_TEXT "$(PARAMS_TITLE)" ""
nsDialogs::Create /NOUNLOAD 1018
Pop $0
${If} $0 == error
Abort
${EndIf}
${NSD_CreateLabel} 0 0 100% 12u "$(SERVER_LABEL):"
Pop $0
${NSD_CreateText} 10% 20u 80% 12u "$SERVERIP_VALUE"
Pop $SERVERIP
nsDialogs::Show
FunctionEnd
# Get parameters from the dialog
Function ParamsPageLeave
${NSD_GetText} $SERVERIP $SERVERIP_VALUE
FunctionEnd
# Assign input parameters or default values to variables
Function GetParameters
${GetOptions} $CMDLINE "/server" $SERVERIP_VALUE
${If} $SERVERIP_VALUE == ""
StrCpy $SERVERIP_VALUE "192.168.2.1:8443"
${EndIf}
FunctionEnd
# Installer Language Strings
LangString PARAMS_TITLE ${LANG_ENGLISH} "Setup parameters"
LangString PARAMS_TITLE ${LANG_SPANISH} "Parametros de configuracion"
LangString PARAMS_TITLE ${LANG_FRENCH} "Parametres de configuration"
LangString PARAMS_TITLE ${LANG_GERMAN} "Setup-Parameter"
LangString SERVER_LABEL ${LANG_ENGLISH} "OpenGnsys Server IP Address and port (eg. 192.168.98.99:8443)"
LangString SERVER_LABEL ${LANG_SPANISH} "Direccion IP y puerto del Servidor OpenGnsys (p. ej. 192.168.98.99:8443)"
LangString SERVER_LABEL ${LANG_FRENCH} "Adresse IP et port du Serveur OpenGnsys (ex. 192.168.98.99:8443)"
LangString SERVER_LABEL ${LANG_GERMAN} "OpenGnsys Server IP-Adresse und Port (z. B. 192.168.98.99:8443)"
LangString ^UninstallLink ${LANG_ENGLISH} "Uninstall $(^Name)"
LangString ^UninstallLink ${LANG_SPANISH} "Desinstalar $(^Name)"
LangString ^UninstallLink ${LANG_FRENCH} "D<>sinstaller $(^Name)"
LangString ^UninstallLink ${LANG_GERMAN} "deinstallieren $(^Name)"

View File

@ -1 +1 @@
8.5.9 8.3.1

View File

@ -65,7 +65,7 @@
<item> <item>
<widget class="QLabel" name="VersionLabel"> <widget class="QLabel" name="VersionLabel">
<property name="text"> <property name="text">
<string>Version 8.5.6</string> <string>Version 0</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -31,10 +31,13 @@
""" """
# On centos, old six release does not includes byte2int, nor six.PY2
import six
from . import modules from . import modules
from .RESTApi import REST, RESTError from .RESTApi import REST, RESTError
VERSION='8.5.6' VERSION='0'
__title__ = 'OpenGnsys Agent' __title__ = 'OpenGnsys Agent'
__version__ = VERSION __version__ = VERSION
@ -43,4 +46,11 @@ __author__ = 'Adolfo Gómez'
__license__ = "BSD 3-clause" __license__ = "BSD 3-clause"
__copyright__ = "Copyright VirtualCable S.L.U." __copyright__ = "Copyright VirtualCable S.L.U."
if not hasattr(six, 'byte2int'):
if six.PY3:
import operator
six.byte2int = operator.itemgetter(0)
else:
def _byte2int(bs):
return ord(bs[0])
six.byte2int = _byte2int

View File

@ -34,9 +34,10 @@ import os
import json import json
import ssl import ssl
import threading import threading
from socketserver import ThreadingMixIn from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport
from http.server import BaseHTTPRequestHandler, HTTPServer from six.moves.BaseHTTPServer import BaseHTTPRequestHandler # @UnresolvedImport
from urllib.parse import unquote from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport
from six.moves.urllib.parse import unquote # @UnresolvedImport
from .utils import exceptionToMessage from .utils import exceptionToMessage
from .certs import createSelfSignedCert from .certs import createSelfSignedCert

View File

@ -25,7 +25,7 @@ class JobMgr():
ts = now.strftime ('%Y-%m-%d %H:%M:%S.%f%z') ## '%s' doesn't work on windows ts = now.strftime ('%Y-%m-%d %H:%M:%S.%f%z') ## '%s' doesn't work on windows
job_id = hashlib.sha256 (now.isoformat().encode('UTF-8') + script.encode ('UTF-8')).hexdigest()[0:12] job_id = hashlib.sha256 (now.isoformat().encode('UTF-8') + script.encode ('UTF-8')).hexdigest()[0:12]
p = subprocess.Popen (args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen (args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.jobs[job_id] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '', 'notified': False } self.jobs[job_id] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '' }
self.jobs[job_id]['t1'] = threading.Thread (target=job_readstdout, args=(self.jobs[job_id],)) self.jobs[job_id]['t1'] = threading.Thread (target=job_readstdout, args=(self.jobs[job_id],))
self.jobs[job_id]['t2'] = threading.Thread (target=job_readstderr, args=(self.jobs[job_id],)) self.jobs[job_id]['t2'] = threading.Thread (target=job_readstderr, args=(self.jobs[job_id],))
self.jobs[job_id]['t1'].start() self.jobs[job_id]['t1'].start()

View File

@ -41,6 +41,7 @@ import ctypes.util
import subprocess import subprocess
import struct import struct
import array import array
import six
import distro import distro
from opengnsys import utils from opengnsys import utils
@ -52,12 +53,12 @@ def _getMacAddr(ifname):
''' '''
if isinstance(ifname, list): if isinstance(ifname, list):
return dict([(name, _getMacAddr(name)) for name in ifname]) return dict([(name, _getMacAddr(name)) for name in ifname])
if isinstance(ifname, str): if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15]))) info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15])))
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1]) return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
except Exception: except Exception:
return None return None
@ -69,11 +70,11 @@ def _getIpAddr(ifname):
''' '''
if isinstance(ifname, list): if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname]) return dict([(name, _getIpAddr(name)) for name in ifname])
if isinstance(ifname, str): if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7) ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return str(socket.inet_ntoa(fcntl.ioctl( return six.text_type(socket.inet_ntoa(fcntl.ioctl(
s.fileno(), s.fileno(),
0x8915, # SIOCGIFADDR 0x8915, # SIOCGIFADDR
struct.pack(str('256s'), ifname[:15]) struct.pack(str('256s'), ifname[:15])
@ -137,6 +138,11 @@ def reboot(flags=0):
''' '''
Simple reboot using os command Simple reboot using os command
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Check for OpenGnsys Client or GNU/Linux distribution. # Check for OpenGnsys Client or GNU/Linux distribution.
if os.path.exists('/scripts/oginit'): if os.path.exists('/scripts/oginit'):
subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/reboot', shell=True) subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/reboot', shell=True)
@ -148,6 +154,11 @@ def poweroff(flags=0):
''' '''
Simple poweroff using os command Simple poweroff using os command
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Check for OpenGnsys Client or GNU/Linux distribution. # Check for OpenGnsys Client or GNU/Linux distribution.
if os.path.exists('/scripts/oginit'): if os.path.exists('/scripts/oginit'):
subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/poweroff', shell=True) subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/poweroff', shell=True)
@ -160,6 +171,11 @@ def logoff():
Kills all curent user processes, which must send a logogof Kills all curent user processes, which must send a logogof
caveat: If the user has other sessions, will also disconnect from them caveat: If the user has other sessions, will also disconnect from them
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']]) subprocess.call(['/usr/bin/pkill', '-u', os.environ['USER']])

View File

@ -41,6 +41,7 @@ import ctypes.util
import subprocess import subprocess
import struct import struct
import array import array
import six
import json import json
from opengnsys import utils from opengnsys import utils
@ -133,6 +134,11 @@ def reboot(flags=0):
''' '''
Simple reboot command Simple reboot command
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Exec reboot command # Exec reboot command
subprocess.call('/sbin/shutdown -r now', shell=True) subprocess.call('/sbin/shutdown -r now', shell=True)
@ -141,6 +147,11 @@ def poweroff(flags=0):
''' '''
Simple poweroff command Simple poweroff command
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Exec shutdown command # Exec shutdown command
subprocess.call('/sbin/shutdown -h now', shell=True) subprocess.call('/sbin/shutdown -h now', shell=True)
@ -149,6 +160,11 @@ def logoff():
''' '''
Simple logout using AppleScript Simple logout using AppleScript
''' '''
# Workaround for dummy thread
if six.PY3 is False:
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Exec logout using AppleScript # Exec logout using AppleScript
subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True) subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True)

View File

@ -53,35 +53,6 @@ class OpenGnSysWorker(ClientWorker):
job_id = self.jobmgr.launch_job (script, True) job_id = self.jobmgr.launch_job (script, True)
self.sendServerMessage('script_launched', {'op': 'launched', 'job_id': job_id}) self.sendServerMessage('script_launched', {'op': 'launched', 'job_id': job_id})
# Monitor the job in the background and notify the server when done
def monitor_job():
import time
import threading
job = self.jobmgr.jobs.get(job_id)
if not job:
return
p = job.get('p')
if not p:
return
p.wait()
# Wait for stdout/stderr threads to finish
if job.get('t1'): job['t1'].join()
if job.get('t2'): job['t2'].join()
logger.debug(f'Client job {job_id} finished, notifying server.')
self.sendServerMessage('script_finished', {
'job_id': job_id,
'rc': p.returncode,
'stderr': job.get('stderr', ''),
'stdout': job.get('stdout', '')
})
import threading
threading.Thread(target=monitor_job, daemon=True).start()
def process_terminatescript(self, json_params): def process_terminatescript(self, json_params):
job_id = json_params['job_id'] job_id = json_params['job_id']
logger.debug('Processing terminatescript request, job_id "{}"'.format (job_id)) logger.debug('Processing terminatescript request, job_id "{}"'.format (job_id))

View File

@ -34,9 +34,7 @@ import base64
import os import os
import random import random
import shutil import shutil
import socket
import string import string
import sys
import threading import threading
import time import time
import urllib.error import urllib.error
@ -103,7 +101,6 @@ class OpenGnSysWorker(ServerWorker):
exec_level = None # Execution level (permitted operations) exec_level = None # Execution level (permitted operations)
jobmgr = JobMgr() jobmgr = JobMgr()
_registered = False # Whether ogcore has acknowledged our ogagent/started _registered = False # Whether ogcore has acknowledged our ogagent/started
client_jobs = set() # Track active client job IDs for busy status
def _send_started(self): def _send_started(self):
"""Send ogagent/started to ogcore and mark as registered on success.""" """Send ogagent/started to ogcore and mark as registered on success."""
@ -127,40 +124,10 @@ class OpenGnSysWorker(ServerWorker):
while True: while True:
time.sleep(1) time.sleep(1)
n += 1 n += 1
# Check for finished jobs to send webhook
for k, elem in list(self.jobmgr.jobs.items()):
if elem.get('p') and elem['p'].poll() is not None:
if not elem.get('notified', False):
if not elem.get('t1').is_alive() and not elem.get('t2').is_alive():
elem['notified'] = True
elem['status'] = 'finished'
elem['rc'] = elem['p'].returncode
stdout = elem.get('stdout', '')
stderr = elem.get('stderr', '')
der = stdout + ('\n' if stdout and stderr else '') + stderr
webhook_body = {
"nfn": "RESPUESTA_EjecutarScript",
"job_id": k,
"res": 1 if elem['rc'] == 0 else 0,
"der": der
}
logger.info(f"Job {k} finished with return code {elem['rc']}. Sending webhook to ogcore.")
try:
self.REST.sendMessage('clients/status/webhook', webhook_body)
except Exception as e:
logger.error(f"Failed to send webhook for job {k}: {e}")
if not n % 10: if not n % 10:
_st_map = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'}
is_busy = any(j['p'].poll() is None for j in self.jobmgr.jobs.values()) or len(self.client_jobs) > 0
body = { body = {
"iph": self.interface.ip, "iph": self.interface.ip,
"timestamp": int(time.time()), "timestamp": int(time.time()),
"busy": is_busy,
"st": _st_map.get(operations.os_type.lower(), 'UNK'),
"loggedin": len(self.user) > 0,
} }
result = self.REST.sendMessage('clients/status/webhook', body) result = self.REST.sendMessage('clients/status/webhook', body)
if result is None: if result is None:
@ -171,63 +138,57 @@ class OpenGnSysWorker(ServerWorker):
logger.info('Connection to ogcore restored, re-registering') logger.info('Connection to ogcore restored, re-registering')
self._send_started() self._send_started()
def _best_interface(self, nets): def onActivation(self):
""" """
Return the interface whose IP the OS routing table would use to reach Sends OGAgent activation notification to OpenGnsys server
ogcore, by opening a no-op UDP socket (no packets sent) and reading """
the local address assigned by the kernel. if os.path.exists ('/scripts/oginit'):
## estamos en oglive, este modulo no debe cargarse
## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar
raise Exception ('Refusing to load within an ogLive image')
Returns None if the routing query itself fails (network not ready yet), e = None # Error info
so the caller keeps retrying. Falls back to nets[0] only when the query t = 0 # Count of time
succeeds but no interface matches the routing IP (e.g. NAT or VPN). # Generate random secret to send on activation
""" self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length))
# Ensure cfg has required configuration variables or an exception will be thrown
try: try:
host = urllib.parse.urlparse(self.REST.endpoint).hostname url = self.service.config.get(self.name, 'remote')
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: ca_file = self.service.config.get(self.name, 'ca')
s.connect((host, 80)) crt_file = self.service.config.get(self.name, 'crt')
local_ip = s.getsockname()[0] key_file = self.service.config.get(self.name, 'key')
except Exception as e: except NoOptionError as e:
logger.debug('Routing query to ogcore failed (network not ready?): {}'.format(e)) logger.error("Configuration error: {}".format(e))
return None raise e
self.REST = REST (url, ca_file=ca_file, crt_file=crt_file, key_file=key_file)
for iface in nets: # Execution level ('full' by default)
if iface.ip == local_ip: try:
logger.debug('Selected interface {} ({}) for ogcore at {}'.format( self.exec_level = self.service.config.get(self.name, 'level')
iface.name, local_ip, host)) except NoOptionError:
return iface self.exec_level = 'full'
# Get network interfaces until they are active or timeout (5 minutes)
# Routing succeeded but no interface matched (NAT/VPN): best effort for t in range(0, 300):
logger.debug('No interface matched routing IP {}, falling back to {}'.format(
local_ip, nets[0].ip))
return nets[0]
def _setup_network(self, ca_file, crt_file, key_file):
"""
Background thread: waits indefinitely for a network interface, then
registers with ogcore. This avoids blocking onActivation() and allows
the agent to recover automatically when the network comes up late
(e.g. on slow Windows boots without a delayed-start service setting).
"""
# COM must be initialised per-thread on Windows; WMI (used by
# getNetworkInfo) raises CO_E_NOTINITIALIZED otherwise.
if sys.platform == 'win32':
import pythoncom
pythoncom.CoInitialize()
t = 0
while not self.interface:
try: try:
nets = list(operations.getNetworkInfo()) # Get the first network interface
if nets: nets = list (operations.getNetworkInfo())
self.interface = self._best_interface(nets) if 0 == len (nets):
if self.interface and t > 0: logger.error ('No network interfaces found')
logger.debug("Network interface found after {} seconds".format(t)) raise Exception ('No network interfaces found')
else: self.interface = nets[0]
logger.error('No network interfaces found')
except Exception as e: except Exception as e:
logger.warn(e) # Wait 1 sec. and retry
if not self.interface: logger.warn (e)
time.sleep(1) time.sleep(1)
t += 1 finally:
# Exit loop if interface is active
if self.interface:
if t > 0:
logger.debug("Fetch connection data after {} tries".format(t))
break
# Raise error after timeout
if not self.interface:
## UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
raise e
# Loop to send initialization message # Loop to send initialization message
init_retries = 100 init_retries = 100
@ -240,7 +201,7 @@ class OpenGnSysWorker(ServerWorker):
'agent_version': VERSION}) 'agent_version': VERSION})
break break
except Exception as e: except Exception as e:
logger.warn(str(e)) logger.warn (str (e))
# Trying to initialize on alternative server, if defined # Trying to initialize on alternative server, if defined
# (used in "exam mode" from the University of Seville) # (used in "exam mode" from the University of Seville)
self.REST = REST(self.service.config.get(self.name, 'altremote'), ca_file=ca_file, crt_file=crt_file, key_file=key_file) self.REST = REST(self.service.config.get(self.name, 'altremote'), ca_file=ca_file, crt_file=crt_file, key_file=key_file)
@ -250,14 +211,13 @@ class OpenGnSysWorker(ServerWorker):
'agent_version': VERSION}) 'agent_version': VERSION})
break break
except Exception as e: except Exception as e:
logger.warn(str(e)) logger.warn (str (e))
time.sleep(3) time.sleep(3)
# Raise error after timeout
if t < init_retries - 1: if t < init_retries-1:
logger.debug('Successful connection after {} tries'.format(t)) logger.debug('Successful connection after {} tries'.format(t))
else: elif t == init_retries-1:
# ogcore unreachable after all retries; mon() will re-register when it comes back raise Exception('Initialization error: Cannot connect to remote server')
logger.error('Cannot connect to remote server after {} retries, will retry via monitoring'.format(init_retries))
# Delete marking files # Delete marking files
for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']:
@ -272,46 +232,10 @@ class OpenGnSysWorker(ServerWorker):
if os.path.isfile(new_hosts_file): if os.path.isfile(new_hosts_file):
shutil.copyfile(new_hosts_file, hosts_file) shutil.copyfile(new_hosts_file, hosts_file)
self._registered = t < init_retries - 1 self._registered = True
threading.Thread(name='monitoring_thread', target=self.mon, daemon=True).start() threading.Thread(name='monitoring_thread', target=self.mon, daemon=True).start()
logger.debug('_setup_network complete')
def onActivation(self): logger.debug('onActivation ok')
"""
Sends OGAgent activation notification to OpenGnsys server
"""
if os.path.exists('/scripts/oginit'):
## estamos en oglive, este modulo no debe cargarse
## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar
raise Exception('Refusing to load within an ogLive image')
# Generate random secret to send on activation
self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length))
# Ensure cfg has required configuration variables or an exception will be thrown
try:
url = self.service.config.get(self.name, 'remote')
ca_file = self.service.config.get(self.name, 'ca')
crt_file = self.service.config.get(self.name, 'crt')
key_file = self.service.config.get(self.name, 'key')
except NoOptionError as e:
logger.error("Configuration error: {}".format(e))
raise e
self.REST = REST(url, ca_file=ca_file, crt_file=crt_file, key_file=key_file)
# Execution level ('full' by default)
try:
self.exec_level = self.service.config.get(self.name, 'level')
except NoOptionError:
self.exec_level = 'full'
# Network detection and ogcore registration run in a background thread so
# that a slow network (boot race condition) does not prevent activation.
threading.Thread(
name='setup_thread',
target=self._setup_network,
args=(ca_file, crt_file, key_file),
daemon=True,
).start()
logger.debug('onActivation launched setup thread')
def onDeactivation(self): def onDeactivation(self):
""" """
@ -322,9 +246,8 @@ class OpenGnSysWorker(ServerWorker):
sess_len = now - elem['login_ts'] sess_len = now - elem['login_ts']
logger.debug ('Session of logged in user {} took {} seconds'.format (elem['username'], int (sess_len))) logger.debug ('Session of logged in user {} took {} seconds'.format (elem['username'], int (sess_len)))
logger.debug('onDeactivation') logger.debug('onDeactivation')
if self.interface: self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, 'ostype': operations.os_type, 'osversion': operations.os_version})
'ostype': operations.os_type, 'osversion': operations.os_version})
def onLogin(self, data): def onLogin(self, data):
""" """
Sends session login notification to OpenGnsys server Sends session login notification to OpenGnsys server
@ -332,10 +255,9 @@ class OpenGnSysWorker(ServerWorker):
user, language, self.session_type = tuple(data.split(',')) user, language, self.session_type = tuple(data.split(','))
logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type)) logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type))
self.user.append ({'username': user, 'login_ts': time.time() }) self.user.append ({'username': user, 'login_ts': time.time() })
if self.interface: self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, 'session': self.session_type,
'session': self.session_type, 'ostype': operations.os_type, 'osversion': operations.os_version})
'ostype': operations.os_type, 'osversion': operations.os_version})
def onLogout(self, user): def onLogout(self, user):
""" """
@ -350,8 +272,7 @@ class OpenGnSysWorker(ServerWorker):
self.user.pop() self.user.pop()
except IndexError: except IndexError:
pass pass
if self.interface: self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
def process_ogclient(self, path, get_params, post_params, server): def process_ogclient(self, path, get_params, post_params, server):
""" """
@ -500,38 +421,11 @@ class OpenGnSysWorker(ServerWorker):
def process_client_script_launched(self, data): def process_client_script_launched(self, data):
import tempfile import tempfile
job_id = data['job_id']
job_id_path = os.path.join(tempfile.gettempdir(), 'EjecutarScript-jobid') job_id_path = os.path.join(tempfile.gettempdir(), 'EjecutarScript-jobid')
with open (job_id_path, 'w') as fd: with open (job_id_path, 'w') as fd:
fd.write (job_id) fd.write (data['job_id'])
self.client_jobs.add(job_id)
return True return True
def process_client_script_finished(self, data):
job_id = data['job_id']
rc = data['rc']
stdout = data.get('stdout', '')
stderr = data.get('stderr', '')
der = stdout + ('\n' if stdout and stderr else '') + stderr
if job_id in self.client_jobs:
self.client_jobs.remove(job_id)
logger.info(f"Client job {job_id} finished with return code {rc}. Sending webhook to ogcore.")
webhook_body = {
"nfn": "RESPUESTA_EjecutarScript",
"job_id": job_id,
"res": 1 if rc == 0 else 0,
"der": der
}
try:
self.REST.sendMessage('clients/status/webhook', webhook_body)
except Exception as e:
logger.error(f"Failed to send webhook for client job {job_id}: {e}")
return True
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_terminatescript(self, path, get_params, post_params, server): def process_terminatescript(self, path, get_params, post_params, server):

View File

@ -149,6 +149,13 @@ class ogAdmClientWorker (ogLiveWorker):
logger.error ('Ha ocurrido algún problema al procesar la trama recibida') logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
break break
def _on_reconnect(self):
logger.info('Re-registering with ogcore after reconnection')
if self.inclusionCliente():
self._registered = True
else:
logger.warning('Re-registration failed, will retry on next ping')
def inclusionCliente (self): def inclusionCliente (self):
cfg = self.LeeConfiguracion() cfg = self.LeeConfiguracion()
if not cfg: if not cfg:
@ -889,7 +896,7 @@ class ogAdmClientWorker (ogLiveWorker):
@check_secret @check_secret
def process_popup (self, path, get_params, post_params, server): def process_popup (self, path, get_params, post_params, server):
logger.info ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>" ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
## type(post_params) "<class 'dict'>" ## type(post_params) "<class 'dict'>"
@ -898,7 +905,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_Actualizar (self, path, get_params, post_params, server): def process_Actualizar (self, path, get_params, post_params, server):
logger.info ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -907,7 +914,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_Purgar (self, path, get_params, post_params, server): def process_Purgar (self, path, get_params, post_params, server):
logger.info ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
os.kill (os.getpid(), signal.SIGTERM) os.kill (os.getpid(), signal.SIGTERM)
return {} return {}
#exit (0) ## ogAdmClient.c:905 #exit (0) ## ogAdmClient.c:905
@ -915,7 +922,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_Comando (self, path, get_params, post_params, server): def process_Comando (self, path, get_params, post_params, server):
logger.info ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -931,7 +938,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_ConsolaRemota (self, path, get_params, post_params, server): def process_ConsolaRemota (self, path, get_params, post_params, server):
logger.info ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -940,7 +947,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_Arrancar (self, path, get_params, post_params, server): def process_Arrancar (self, path, get_params, post_params, server):
logger.info ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -961,7 +968,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('halt') @execution_level('halt')
@check_secret @check_secret
def process_Apagar (self, path, get_params, post_params, server): def process_Apagar (self, path, get_params, post_params, server):
logger.info ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -970,7 +977,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('halt') @execution_level('halt')
@check_secret @check_secret
def process_Reiniciar (self, path, get_params, post_params, server): def process_Reiniciar (self, path, get_params, post_params, server):
logger.info ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -979,7 +986,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_IniciarSesion (self, path, get_params, post_params, server): def process_IniciarSesion (self, path, get_params, post_params, server):
logger.info ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -988,7 +995,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_EjecutarScript (self, path, get_params, post_params, server): def process_EjecutarScript (self, path, get_params, post_params, server):
logger.info ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -997,7 +1004,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_EjecutaComandosPendientes (self, path, get_params, post_params, server): def process_EjecutaComandosPendientes (self, path, get_params, post_params, server):
logger.info ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -1006,7 +1013,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_CrearImagen (self, path, get_params, post_params, server): def process_CrearImagen (self, path, get_params, post_params, server):
logger.info ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1014,7 +1021,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,)) return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))
def process_CrearImagenGit (self, path, get_params, post_params, server): def process_CrearImagenGit (self, path, get_params, post_params, server):
logger.info ('in process_CrearImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_CrearImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1022,7 +1029,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, args=(post_params,)) return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, args=(post_params,))
def process_ModificarImagenGit (self, path, get_params, post_params, server): def process_ModificarImagenGit (self, path, get_params, post_params, server):
logger.info ('in process_ModificarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_ModificarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1030,7 +1037,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('ModificarImagenGit', self.do_ModificarImagenGit, args=(post_params,)) return self._long_running_job ('ModificarImagenGit', self.do_ModificarImagenGit, args=(post_params,))
def process_RestaurarImagenGit (self, path, get_params, post_params, server): def process_RestaurarImagenGit (self, path, get_params, post_params, server):
logger.info ('in process_RestaurarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_RestaurarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1040,7 +1047,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_RestaurarImagen (self, path, get_params, post_params, server): def process_RestaurarImagen (self, path, get_params, post_params, server):
logger.info ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1050,7 +1057,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_Configurar (self, path, get_params, post_params, server): def process_Configurar (self, path, get_params, post_params, server):
logger.info ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -1065,7 +1072,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_InventarioHardware (self, path, get_params, post_params, server): def process_InventarioHardware (self, path, get_params, post_params, server):
logger.info ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -1074,7 +1081,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_InventarioSoftware (self, path, get_params, post_params, server): def process_InventarioSoftware (self, path, get_params, post_params, server):
logger.info ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -1083,7 +1090,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_KillJob (self, path, get_params, post_params, server): def process_KillJob (self, path, get_params, post_params, server):
logger.info ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}
@ -1097,7 +1104,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full') @execution_level('full')
@check_secret @check_secret
def process_GetGitData (self, path, get_params, post_params, server): def process_GetGitData (self, path, get_params, post_params, server):
logger.info ('in process_GetGitData, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) logger.debug ('in process_GetGitData, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None: if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary') logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {} post_params = {}

View File

@ -32,6 +32,7 @@
import sys import sys
import six
if sys.platform == 'win32': if sys.platform == 'win32':
_fromEncoding = 'windows-1250' _fromEncoding = 'windows-1250'
@ -41,15 +42,15 @@ else:
def toUnicode(msg): def toUnicode(msg):
try: try:
if not isinstance(msg, str): if not isinstance(msg, six.text_type):
if isinstance(msg, bytes): if isinstance(msg, six.binary_type):
return msg.decode(_fromEncoding, 'ignore') return msg.decode(_fromEncoding, 'ignore')
return str(msg) return six.text_type(msg)
else: else:
return msg return msg
except Exception: except Exception:
try: try:
return str(msg) return six.text_type(msg)
except Exception: except Exception:
return '' return ''

View File

@ -151,8 +151,6 @@ def get_etc_path():
def build_popen_args(script): def build_popen_args(script):
## turn the script into utf16le, then to b64 again, and feed the blob to powershell ## turn the script into utf16le, then to b64 again, and feed the blob to powershell
## wrap the script to redirect stderr and output as a simple string to avoid CLIXML wrappers in non-interactive errors
script = f"$ProgressPreference = 'SilentlyContinue'; try {{ $errBefore = $Error.Count; $output = & {{ {script} }} 2>&1; $errAfter = $Error.Count; $exitcode = $LASTEXITCODE; $output | Out-String; if ($exitcode) {{ exit $exitcode }} elseif ($errAfter -gt $errBefore) {{ exit 1 }} }} catch {{ $_ | Out-String; exit 1 }}"
u16 = script.encode ('utf-16le') ## utf16 u16 = script.encode ('utf-16le') ## utf16
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8)) b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
return ['powershell', '-WindowStyle', 'Hidden', '-EncodedCommand', b64] return ['powershell', '-WindowStyle', 'Hidden', '-EncodedCommand', b64]

View File

@ -67,85 +67,90 @@ class ThreadWithResult (threading.Thread):
class ogLiveWorker(ServerWorker): class ogLiveWorker(ServerWorker):
thread_list = {} thread_list = {}
thread_lock = threading.Lock() thread_lock = threading.Lock()
_registered = False # Whether ogcore has acknowledged our registration
tbErroresScripts = [ tbErroresScripts = [
"Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0 "Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0
"001-Formato de ejecución incorrecto.", "001-Formato de ejecución incorrecto.",
"002-Fichero o dispositivo no encontrado", "002-Fichero o dispositivo no encontrado",
"003-Error en partición de disco", "003-Error en partición de disco",
"004-Partición o fichero bloqueado", "004-Partición o fichero bloqueado",
"005-Error al crear o restaurar una imagen", "005-Error al crear o restaurar una imagen",
"006-Sin sistema operativo", "006-Sin sistema operativo",
"007-Programa o función BOOLEAN no ejecutable", "007-Programa o función BOOLEAN no ejecutable",
"008-Error en la creación del archivo de eco para consola remota", "008-Error en la creación del archivo de eco para consola remota",
"009-Error en la lectura del archivo temporal de intercambio", "009-Error en la lectura del archivo temporal de intercambio",
"010-Error al ejecutar la llamada a la interface de administración", "010-Error al ejecutar la llamada a la interface de administración",
"011-La información retornada por la interface de administración excede de la longitud permitida", "011-La información retornada por la interface de administración excede de la longitud permitida",
"012-Error en el envío de fichero por la red", "012-Error en el envío de fichero por la red",
"013-Error en la creación del proceso hijo", "013-Error en la creación del proceso hijo",
"014-Error de escritura en destino", "014-Error de escritura en destino",
"015-Sin Cache en el Cliente", "015-Sin Cache en el Cliente",
"016-No hay espacio en la cache para almacenar fichero-imagen", "016-No hay espacio en la cache para almacenar fichero-imagen",
"017-Error al Reducir el Sistema Archivos", "017-Error al Reducir el Sistema Archivos",
"018-Error al Expandir el Sistema Archivos", "018-Error al Expandir el Sistema Archivos",
"019-Valor fuera de rango o no válido.", "019-Valor fuera de rango o no válido.",
"020-Sistema de archivos desconocido o no se puede montar", "020-Sistema de archivos desconocido o no se puede montar",
"021-Error en partición de caché local", "021-Error en partición de caché local",
"022-El disco indicado no contiene una particion GPT", "022-El disco indicado no contiene una particion GPT",
"023-Error no definido", "023-Error no definido",
"024-Error no definido", "024-Error no definido",
"025-Error no definido", "025-Error no definido",
"026-Error no definido", "026-Error no definido",
"027-Error no definido", "027-Error no definido",
"028-Error no definido", "028-Error no definido",
"029-Error no definido", "029-Error no definido",
"030-Error al restaurar imagen - Imagen mas grande que particion", "030-Error al restaurar imagen - Imagen mas grande que particion",
"031-Error al realizar el comando updateCache", "031-Error al realizar el comando updateCache",
"032-Error al formatear", "032-Error al formatear",
"033-Archivo de imagen corrupto o de otra versión de partclone", "033-Archivo de imagen corrupto o de otra versión de partclone",
"034-Error no definido", "034-Error no definido",
"035-Error no definido", "035-Error no definido",
"036-Error no definido", "036-Error no definido",
"037-Error no definido", "037-Error no definido",
"038-Error no definido", "038-Error no definido",
"039-Error no definido", "039-Error no definido",
"040-Error imprevisto no definido", "040-Error imprevisto no definido",
"041-Error no definido", "041-Error no definido",
"042-Error no definido", "042-Error no definido",
"043-Error no definido", "043-Error no definido",
"044-Error no definido", "044-Error no definido",
"045-Error no definido", "045-Error no definido",
"046-Error no definido", "046-Error no definido",
"047-Error no definido", "047-Error no definido",
"048-Error no definido", "048-Error no definido",
"049-Error no definido", "049-Error no definido",
"050-Error en la generación de sintaxis de transferenica unicast", "050-Error en la generación de sintaxis de transferenica unicast",
"051-Error en envio UNICAST de una particion", "051-Error en envio UNICAST de una particion",
"052-Error en envio UNICAST de un fichero", "052-Error en envio UNICAST de un fichero",
"053-Error en la recepcion UNICAST de una particion", "053-Error en la recepcion UNICAST de una particion",
"054-Error en la recepcion UNICAST de un fichero", "054-Error en la recepcion UNICAST de un fichero",
"055-Error en la generacion de sintaxis de transferenica Multicast", "055-Error en la generacion de sintaxis de transferenica Multicast",
"056-Error en envio MULTICAST de un fichero", "056-Error en envio MULTICAST de un fichero",
"057-Error en la recepcion MULTICAST de un fichero", "057-Error en la recepcion MULTICAST de un fichero",
"058-Error en envio MULTICAST de una particion", "058-Error en envio MULTICAST de una particion",
"059-Error en la recepcion MULTICAST de una particion", "059-Error en la recepcion MULTICAST de una particion",
"060-Error en la conexion de una sesion UNICAST|MULTICAST con el MASTER", "060-Error en la conexion de una sesion UNICAST|MULTICAST con el MASTER",
"061-Error no definido", "061-Error no definido",
"062-Error no definido", "062-Error no definido",
"063-Error no definido", "063-Error no definido",
"064-Error no definido", "064-Error no definido",
"065-Error no definido", "065-Error no definido",
"066-Error no definido", "066-Error no definido",
"067-Error no definido", "067-Error no definido",
"068-Error no definido", "068-Error no definido",
"069-Error no definido", "069-Error no definido",
"070-Error al montar una imagen sincronizada.", "070-Error al montar una imagen sincronizada.",
"071-Imagen no sincronizable (es monolitica).", "071-Imagen no sincronizable (es monolitica).",
"072-Error al desmontar la imagen.", "072-Error al desmontar la imagen.",
"073-No se detectan diferencias entre la imagen basica y la particion.", "073-No se detectan diferencias entre la imagen basica y la particion.",
"074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.", "074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.",
"Error desconocido", "Error desconocido",
] ]
def _on_reconnect(self):
"""Hook called by mon() when connection to ogcore is restored. Override in subclasses."""
pass
def notifier (self, job_id, result): def notifier (self, job_id, result):
result['job_id'] = job_id result['job_id'] = job_id
@ -239,10 +244,10 @@ class ogLiveWorker(ServerWorker):
elem['thread'].join (0.05) elem['thread'].join (0.05)
if not elem['thread'].is_alive(): if not elem['thread'].is_alive():
logger.debug (f'is no longer alive, k ({k}) thread ({elem["thread"]})')
elem['running'] = False elem['running'] = False
elem['result'] = elem['thread'].result elem['result'] = elem['thread'].result
del elem['thread'] del elem['thread']
logger.info (f'job finished, job_id ({k}) result ({elem["result"]})')
self.notifier (k, elem['result']) self.notifier (k, elem['result'])
time.sleep (1) time.sleep (1)
@ -260,11 +265,15 @@ class ogLiveWorker(ServerWorker):
body = { body = {
'iph': self.IPlocal, 'iph': self.IPlocal,
'timestamp': int (time.time()), 'timestamp': int (time.time()),
'busy': any(elem['running'] for elem in self.thread_list.values()),
'st': 'OGL',
} }
#logger.debug (f'about to send ping ({body})') result = self.REST.sendMessage('clients/status/webhook', body)
self.REST.sendMessage ('clients/status/webhook', body) if result is None:
if self._registered:
logger.debug('Lost connection to ogcore')
self._registered = False
elif not self._registered:
logger.info('Connection to ogcore restored, re-registering')
self._on_reconnect()
def interfaceAdmin (self, method, parametros=[]): def interfaceAdmin (self, method, parametros=[]):
if method not in ['ConsolaRemota', 'procesaCache']: if method not in ['ConsolaRemota', 'procesaCache']:
@ -363,9 +372,6 @@ class ogLiveWorker(ServerWorker):
## /DEBUG ## /DEBUG
if 0 != p.returncode: if 0 != p.returncode:
cmd_txt = ' '.join (proc) cmd_txt = ' '.join (proc)
logger.warning (f'command ({cmd_txt}) failed, stdout follows:')
for l in sout.splitlines():
logger.warning (f' {l}')
logger.error (f'command ({cmd_txt}) failed, stderr follows:') logger.error (f'command ({cmd_txt}) failed, stderr follows:')
for l in serr.splitlines(): for l in serr.splitlines():
logger.error (f' {l}') logger.error (f' {l}')
@ -427,33 +433,23 @@ class ogLiveWorker(ServerWorker):
dbus_address = os.environ.get ('DBUS_SESSION_BUS_ADDRESS') dbus_address = os.environ.get ('DBUS_SESSION_BUS_ADDRESS')
if not dbus_address: logger.warning ('env var DBUS_SESSION_BUS_ADDRESS not set, cargaPaginaWeb() will likely not work') if not dbus_address: logger.warning ('env var DBUS_SESSION_BUS_ADDRESS not set, cargaPaginaWeb() will likely not work')
b = dbus.SystemBus()
dest = 'es.opengnsys.OGBrowser.browser'
path = '/'
interface = None
method = 'setURL'
signature = 's'
try: try:
b = dbus.SystemBus()
dest = 'es.opengnsys.OGBrowser.browser'
path = '/'
interface = None
method = 'setURL'
b.call_blocking (dest, path, interface, method, 's', [url]) b.call_blocking (dest, path, interface, method, 's', [url])
except Exception as e: except Exception as e:
# Capturamos ServiceUnknown y también NameHasNoOwner que salta a veces por condiciones de carrera if 'ServiceUnknown' in str(e):
if 'ServiceUnknown' in str(e) or 'NameHasNoOwner' in str(e): logger.warning ('browser is not running, launching a new one')
if getattr(self, 'browser_process', None) is None or self.browser_process.poll() is not None: browser_log_fd = open ('/var/log/launch_browser.log', 'a')
logger.warning (f'browser is not running ({e}), launching a new one') self.browser_process = subprocess.Popen (['/usr/bin/launch_browser', url], stdout=browser_log_fd, stderr=subprocess.STDOUT)
browser_log_fd = open ('/var/log/launch_browser.log', 'a') browser_log_fd.close()
self.browser_process = subprocess.Popen (['/usr/bin/launch_browser', url], stdout=browser_log_fd, stderr=subprocess.STDOUT)
browser_log_fd.close()
else:
logger.warning ('browser is already launching, ignoring duplicate launch request')
else: else:
logger.error (f'Error al cambiar URL: ({e})') logger.error (f'Error al cambiar URL: ({e})')
# Como medida de contingencia, si falla por cualquier otro error de DBus y no hay proceso, lo lanzamos return False
if getattr(self, 'browser_process', None) is None or self.browser_process.poll() is not None:
logger.warning ('Fallback: launching browser despite unknown DBus error')
browser_log_fd = open ('/var/log/launch_browser.log', 'a')
self.browser_process = subprocess.Popen (['/usr/bin/launch_browser', url], stdout=browser_log_fd, stderr=subprocess.STDOUT)
browser_log_fd.close()
else:
return False
return True return True
@ -552,7 +548,8 @@ class ogLiveWorker(ServerWorker):
logger.warning ('onActivation failed') logger.warning ('onActivation failed')
return return
threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start() self._registered = True
threading.Thread(name='monitoring_thread', target=self.mon, daemon=True).start()
def _long_running_job (self, name, f, args): def _long_running_job (self, name, f, args):
any_job_running = False any_job_running = False

View File

@ -1,6 +1,6 @@
pywin32 pywin32
pyqt6 pyqt6
requests requests
six
pycryptodome pycryptodome
pyinstaller pyinstaller