Compare commits

...

34 Commits

Author SHA1 Message Date
Juan Manuel Bardallo 7b2f2e685f Merge branch 'devel' into main
ogagent/pipeline/head This commit looks good Details
ogagent/pipeline/tag This commit looks good Details
2026-10-01 12:51:20 +02:00
Juan Manuel Bardallo 2ce098f10e docs(changelog): fix formatting and missing special characters in 8.5.10 notes
ogagent/pipeline/head This commit looks good Details
2026-09-30 15:11:25 +02:00
Juan Manuel Bardallo e62d217fa2 Merge branch 'devel'
ogagent/pipeline/head There was a failure building this commit Details
ogagent/pipeline/tag There was a failure building this commit Details
2026-09-30 14:22:04 +02:00
Juan Manuel Bardallo dc9d888da1 fix(oglive): resolve race condition in browser launch to prevent Xorg crashes
ogagent/pipeline/head There was a failure building this commit Details
2026-09-30 14:21:45 +02:00
Juan Manuel Bardallo ae9012753e Merge pull request 'devel 8.5.9' (#76) from devel into main
ogagent/pipeline/head There was a failure building this commit Details
ogagent/pipeline/tag There was a failure building this commit Details
Reviewed-on: #76
2026-07-31 10:39:27 +02:00
Juan Manuel Bardallo ce9490b46e Docs(build): bump debian version to 8.5.9-1 and include cross-platform fixes
ogagent/pipeline/head This commit looks good Details
2026-07-29 13:56:34 +02:00
Juan Manuel Bardallo ada201fa85 Revert: Fix(build): add -us -uc to debuild to prevent GPG signing errors
ogagent/pipeline/head This commit looks good Details
2026-07-29 13:49:08 +02:00
Juan Manuel Bardallo b880f29efe Fix(build): add -us -uc to debuild to prevent GPG signing errors
ogagent/pipeline/head This commit looks good Details
2026-07-29 13:43:36 +02:00
Juan Manuel Bardallo d35e63e60b Docs: update CHANGELOG for START_SCRIPT webhook removal
ogagent/pipeline/head This commit looks good Details
2026-07-28 00:34:17 +02:00
Juan Manuel Bardallo 120140365d Fix(ogagent): completely remove START_SCRIPT webhook to avoid trace duplication race conditions in client paths
ogagent/pipeline/head This commit looks good Details
2026-07-27 18:58:53 +02:00
Juan Manuel Bardallo 88297c60c3 Fix(ogagent): prevent duplicate trace creation by removing START_SCRIPT webhook for synchronous execution paths
ogagent/pipeline/head This commit looks good Details
2026-07-27 18:52:00 +02:00
Juan Manuel Bardallo 5b286e005c Fix(ogagent): preserve PowerShell exit code when redirecting output
ogagent/pipeline/head This commit looks good Details
2026-07-27 18:35:50 +02:00
Juan Manuel Bardallo 43c57ea320 Fix(jobmgr): send START_SCRIPT webhook when script is run under user session (client=true)
ogagent/pipeline/head This commit looks good Details
2026-07-22 14:37:17 +02:00
Juan Manuel Bardallo 70f85b1fc7 Feat(jobmgr): send START_SCRIPT webhook to mark trace as in_progress ; Fix(windows): prevent CLIXML output on errors by piping to Out-String
ogagent/pipeline/head This commit looks good Details
2026-07-22 14:19:52 +02:00
Juan Manuel Bardallo d0bde04e92 chore: bump version to 8.5.6 for webhook feature 2026-07-21 12:27:56 +02:00
Juan Manuel Bardallo d2ff2d1b35 feat: notify ogcore when script execution finishes natively 2026-07-21 12:20:17 +02:00
Juan Manuel Bardallo 158cc9ef20 Revert "fix(build): skip gpg signature in debuild to prevent errors for end users"
ogagent/pipeline/head This commit looks good Details
This reverts commit e3eb42f3b3.
2026-07-09 12:21:06 +02:00
Juan Manuel Bardallo e3eb42f3b3 fix(build): skip gpg signature in debuild to prevent errors for end users
ogagent/pipeline/head This commit looks good Details
2026-07-09 12:19:04 +02:00
Juan Manuel Bardallo a149439cc2 fix(build): generate UI files with pyuic6 during build
ogagent/pipeline/head This commit looks good Details
2026-07-09 12:15:51 +02:00
Juan Manuel Bardallo 0db7dbce9f refactor(deps): remove obsolete dependencies and support modern Ubuntu
ogagent/pipeline/head There was a failure building this commit Details
2026-07-09 11:56:53 +02:00
Nicolas Arenas 9ffa27cd03 HANGELOG.md
ogagent/pipeline/head This commit looks good Details
2026-05-20 14:36:34 +02:00
Nicolas Arenas 25fc2cf5f8 feat(ogagent): select network interface by routing table instead of first found
ogagent/pipeline/head This commit looks good Details
On machines with multiple NICs, the agent was always registering with
ogcore using the first interface returned by the OS, which could be the
wrong one if it wasn't the interface with a route to ogcore.

_best_interface() opens a no-op UDP socket toward the ogcore host and
reads the local IP assigned by the kernel routing table, then matches it
against the available interfaces. If the routing query fails (network not
ready yet) it returns None so the detection loop keeps retrying rather
than accepting a wrong interface. Falls back to nets[0] only when the
query succeeds but no interface matches (NAT/VPN scenario).
2026-05-20 14:19:08 +02:00
Nicolas Arenas a222528d00 Add Jenkinsfile for devel
ogagent/pipeline/head This commit looks good Details
2026-05-14 18:24:05 +02:00
Nicolas Arenas 1d6b7f76e6 Merge pull request 'fix(ogagent): improve log levels in ogLive to distinguish actions from heartbeat' (#75) from fix_log into main
ogagent/pipeline/head This commit looks good Details
Reviewed-on: #75
2026-05-13 11:04:30 +02:00
Nicolas Arenas 2433fffac2 Updated CHANGELOG.md 2026-05-13 10:57:17 +02:00
Nicolas Arenas 7728831983 Update oglog levels for oglive
ogagent/pipeline/head This commit looks good Details
2026-05-13 10:04:21 +02:00
Nicolas Arenas eae734458f Merge pull request 'fix-windows-init' (#74) from fix-windows-init into main
ogagent/pipeline/head This commit looks good Details
Reviewed-on: #74
2026-05-11 19:10:03 +02:00
Nicolas Arenas 0bfd129c9e Call CoInitializer in windows at staturup thread 2026-05-11 17:34:16 +02:00
Nicolas Arenas c4ccf60bab Update debian changelog
ogagent/pipeline/head This commit looks good Details
2026-05-11 16:27:56 +02:00
Nicolas Arenas 619edaab50 Updates CHANGELOG.md
ogagent/pipeline/head This commit looks good Details
2026-05-11 16:11:16 +02:00
Nicolas Arenas c3051a75b9 Creates background thread to fix network detectino problem
ogagent/pipeline/head This commit looks good Details
2026-05-11 16:06:27 +02:00
Nicolas Arenas 4e8e26342f Merge pull request 'nofify_status_to_ping' (#73) from nofify_status_to_ping into main
ogagent/pipeline/head This commit looks good Details
Reviewed-on: #73
2026-05-07 20:45:25 +02:00
Nicolas Arenas 4fdc215b98 docs(ogagent): update CHANGELOG for 8.5.3
ogagent/pipeline/head This commit looks good Details
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 20:39:24 +02:00
Nicolas Arenas da50ab5e7f feat(ogagent): report busy state and os type in heartbeat ping
Add 'busy' and 'st' fields to the periodic webhook ping so ogCore can
track whether a client is executing a job without waiting for a command
response.

- oglive_worker: busy derived from thread_list running state, st='OGL'
- OpenGnSys module: busy derived from jobmgr active processes, st mapped
  from operations.os_type (LNX/WIN/OSX)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 20:36:45 +02:00
20 changed files with 771 additions and 234 deletions

View File

@ -5,6 +5,49 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [8.5.10] - 2026-09-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
- 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.
- 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
### Fixed
@ -547,3 +590,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial release for OpenGnsys Agent

View File

@ -67,6 +67,20 @@ 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') {
steps {
dir ("${env.BUILD_DIR}") {
@ -112,8 +126,32 @@ 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 {
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 {
notifyBuildStatus('narenas@qindel.com')
}

View File

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

View File

@ -1,3 +1,111 @@
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
* Parameter fixes

View File

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

238
ogagent.nsi Normal file
View File

@ -0,0 +1,238 @@
# 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.3.1
8.5.9

View File

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

View File

@ -31,13 +31,10 @@
"""
# On centos, old six release does not includes byte2int, nor six.PY2
import six
from . import modules
from .RESTApi import REST, RESTError
VERSION='0'
VERSION='8.5.6'
__title__ = 'OpenGnsys Agent'
__version__ = VERSION
@ -46,11 +43,4 @@ __author__ = 'Adolfo Gómez'
__license__ = "BSD 3-clause"
__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,10 +34,9 @@ import os
import json
import ssl
import threading
from six.moves.socketserver import ThreadingMixIn # @UnresolvedImport
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler # @UnresolvedImport
from six.moves.BaseHTTPServer import HTTPServer # @UnresolvedImport
from six.moves.urllib.parse import unquote # @UnresolvedImport
from socketserver import ThreadingMixIn
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import unquote
from .utils import exceptionToMessage
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
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)
self.jobs[job_id] = { 'p': p, 'pid': p.pid, 'starttime': ts, 'script': script, 'client': is_client, 'status': 'running', 'stdout': '', 'stderr': '' }
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]['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]['t1'].start()

View File

@ -41,7 +41,6 @@ import ctypes.util
import subprocess
import struct
import array
import six
import distro
from opengnsys import utils
@ -53,12 +52,12 @@ def _getMacAddr(ifname):
'''
if isinstance(ifname, list):
return dict([(name, _getMacAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
if isinstance(ifname, str):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15])))
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
return str(''.join(['%02x:' % char for char in info[18:24]])[:-1])
except Exception:
return None
@ -70,11 +69,11 @@ def _getIpAddr(ifname):
'''
if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
if isinstance(ifname, str):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return six.text_type(socket.inet_ntoa(fcntl.ioctl(
return str(socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack(str('256s'), ifname[:15])
@ -138,11 +137,6 @@ def reboot(flags=0):
'''
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.
if os.path.exists('/scripts/oginit'):
subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/reboot', shell=True)
@ -154,11 +148,6 @@ def poweroff(flags=0):
'''
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.
if os.path.exists('/scripts/oginit'):
subprocess.call('source /opt/opengnsys/etc/preinit/loadenviron.sh; /opt/opengnsys/scripts/poweroff', shell=True)
@ -171,11 +160,6 @@ def logoff():
Kills all curent user processes, which must send a logogof
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']])

View File

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

View File

@ -53,6 +53,35 @@ class OpenGnSysWorker(ClientWorker):
job_id = self.jobmgr.launch_job (script, True)
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):
job_id = json_params['job_id']
logger.debug('Processing terminatescript request, job_id "{}"'.format (job_id))

View File

@ -34,7 +34,9 @@ import base64
import os
import random
import shutil
import socket
import string
import sys
import threading
import time
import urllib.error
@ -101,6 +103,7 @@ class OpenGnSysWorker(ServerWorker):
exec_level = None # Execution level (permitted operations)
jobmgr = JobMgr()
_registered = False # Whether ogcore has acknowledged our ogagent/started
client_jobs = set() # Track active client job IDs for busy status
def _send_started(self):
"""Send ogagent/started to ogcore and mark as registered on success."""
@ -124,10 +127,40 @@ class OpenGnSysWorker(ServerWorker):
while True:
time.sleep(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:
_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 = {
"iph": self.interface.ip,
"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)
if result is None:
@ -138,57 +171,63 @@ class OpenGnSysWorker(ServerWorker):
logger.info('Connection to ogcore restored, re-registering')
self._send_started()
def onActivation(self):
def _best_interface(self, nets):
"""
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')
Return the interface whose IP the OS routing table would use to reach
ogcore, by opening a no-op UDP socket (no packets sent) and reading
the local address assigned by the kernel.
e = None # Error info
t = 0 # Count of time
# 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
Returns None if the routing query itself fails (network not ready yet),
so the caller keeps retrying. Falls back to nets[0] only when the query
succeeds but no interface matches the routing IP (e.g. NAT or VPN).
"""
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'
# Get network interfaces until they are active or timeout (5 minutes)
for t in range(0, 300):
host = urllib.parse.urlparse(self.REST.endpoint).hostname
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect((host, 80))
local_ip = s.getsockname()[0]
except Exception as e:
logger.debug('Routing query to ogcore failed (network not ready?): {}'.format(e))
return None
for iface in nets:
if iface.ip == local_ip:
logger.debug('Selected interface {} ({}) for ogcore at {}'.format(
iface.name, local_ip, host))
return iface
# Routing succeeded but no interface matched (NAT/VPN): best effort
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:
# Get the first network interface
nets = list (operations.getNetworkInfo())
if 0 == len (nets):
logger.error ('No network interfaces found')
raise Exception ('No network interfaces found')
self.interface = nets[0]
nets = list(operations.getNetworkInfo())
if nets:
self.interface = self._best_interface(nets)
if self.interface and t > 0:
logger.debug("Network interface found after {} seconds".format(t))
else:
logger.error('No network interfaces found')
except Exception as e:
# Wait 1 sec. and retry
logger.warn (e)
logger.warn(e)
if not self.interface:
time.sleep(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
t += 1
# Loop to send initialization message
init_retries = 100
@ -201,7 +240,7 @@ class OpenGnSysWorker(ServerWorker):
'agent_version': VERSION})
break
except Exception as e:
logger.warn (str (e))
logger.warn(str(e))
# Trying to initialize on alternative server, if defined
# (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)
@ -211,13 +250,14 @@ class OpenGnSysWorker(ServerWorker):
'agent_version': VERSION})
break
except Exception as e:
logger.warn (str (e))
logger.warn(str(e))
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))
elif t == init_retries-1:
raise Exception('Initialization error: Cannot connect to remote server')
else:
# ogcore unreachable after all retries; mon() will re-register when it comes back
logger.error('Cannot connect to remote server after {} retries, will retry via monitoring'.format(init_retries))
# Delete marking files
for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']:
@ -232,10 +272,46 @@ class OpenGnSysWorker(ServerWorker):
if os.path.isfile(new_hosts_file):
shutil.copyfile(new_hosts_file, hosts_file)
self._registered = True
self._registered = t < init_retries - 1
threading.Thread(name='monitoring_thread', target=self.mon, daemon=True).start()
logger.debug('_setup_network complete')
logger.debug('onActivation ok')
def onActivation(self):
"""
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):
"""
@ -246,8 +322,9 @@ class OpenGnSysWorker(ServerWorker):
sess_len = now - elem['login_ts']
logger.debug ('Session of logged in user {} took {} seconds'.format (elem['username'], int (sess_len)))
logger.debug('onDeactivation')
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
'ostype': operations.os_type, 'osversion': operations.os_version})
if self.interface:
self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip,
'ostype': operations.os_type, 'osversion': operations.os_version})
def onLogin(self, data):
"""
Sends session login notification to OpenGnsys server
@ -255,9 +332,10 @@ class OpenGnSysWorker(ServerWorker):
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))
self.user.append ({'username': user, 'login_ts': time.time() })
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
'session': self.session_type,
'ostype': operations.os_type, 'osversion': operations.os_version})
if self.interface:
self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language,
'session': self.session_type,
'ostype': operations.os_type, 'osversion': operations.os_version})
def onLogout(self, user):
"""
@ -272,7 +350,8 @@ class OpenGnSysWorker(ServerWorker):
self.user.pop()
except IndexError:
pass
self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
if self.interface:
self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user})
def process_ogclient(self, path, get_params, post_params, server):
"""
@ -421,11 +500,38 @@ class OpenGnSysWorker(ServerWorker):
def process_client_script_launched(self, data):
import tempfile
job_id = data['job_id']
job_id_path = os.path.join(tempfile.gettempdir(), 'EjecutarScript-jobid')
with open (job_id_path, 'w') as fd:
fd.write (data['job_id'])
fd.write (job_id)
self.client_jobs.add(job_id)
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')
@check_secret
def process_terminatescript(self, path, get_params, post_params, server):

View File

@ -889,7 +889,7 @@ class ogAdmClientWorker (ogLiveWorker):
@check_secret
def process_popup (self, 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.info ('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)))
## 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'>"
@ -898,7 +898,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_Actualizar (self, path, get_params, post_params, server):
logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -907,7 +907,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_Purgar (self, path, get_params, post_params, server):
logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
os.kill (os.getpid(), signal.SIGTERM)
return {}
#exit (0) ## ogAdmClient.c:905
@ -915,7 +915,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_Comando (self, path, get_params, post_params, server):
logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -931,7 +931,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_ConsolaRemota (self, path, get_params, post_params, server):
logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -940,7 +940,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_Arrancar (self, path, get_params, post_params, server):
logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -961,7 +961,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('halt')
@check_secret
def process_Apagar (self, path, get_params, post_params, server):
logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -970,7 +970,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('halt')
@check_secret
def process_Reiniciar (self, path, get_params, post_params, server):
logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -979,7 +979,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_IniciarSesion (self, path, get_params, post_params, server):
logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -988,7 +988,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_EjecutarScript (self, path, get_params, post_params, server):
logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -997,7 +997,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_EjecutaComandosPendientes (self, path, get_params, post_params, server):
logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -1006,7 +1006,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_CrearImagen (self, 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.info ('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)))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1014,7 +1014,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))
def process_CrearImagenGit (self, 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.info ('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)))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1022,7 +1022,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, args=(post_params,))
def process_ModificarImagenGit (self, 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.info ('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)))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1030,7 +1030,7 @@ class ogAdmClientWorker (ogLiveWorker):
return self._long_running_job ('ModificarImagenGit', self.do_ModificarImagenGit, args=(post_params,))
def process_RestaurarImagenGit (self, 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.info ('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)))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1040,7 +1040,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_RestaurarImagen (self, 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.info ('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)))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
@ -1050,7 +1050,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_Configurar (self, path, get_params, post_params, server):
logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -1065,7 +1065,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_InventarioHardware (self, path, get_params, post_params, server):
logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -1074,7 +1074,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_InventarioSoftware (self, path, get_params, post_params, server):
logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -1083,7 +1083,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_KillJob (self, path, get_params, post_params, server):
logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}
@ -1097,7 +1097,7 @@ class ogAdmClientWorker (ogLiveWorker):
@execution_level('full')
@check_secret
def process_GetGitData (self, path, get_params, post_params, server):
logger.debug ('in process_GetGitData, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
logger.info ('in process_GetGitData, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
if post_params is None:
logger.warning ('POST body is empty--setting it to an empty dictionary')
post_params = {}

View File

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

View File

@ -151,6 +151,8 @@ def get_etc_path():
def build_popen_args(script):
## 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
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
return ['powershell', '-WindowStyle', 'Hidden', '-EncodedCommand', b64]

View File

@ -70,82 +70,82 @@ class ogLiveWorker(ServerWorker):
tbErroresScripts = [
"Se han generado errores desconocidos. No se puede continuar la ejecución de este módulo", ## 0
"001-Formato de ejecución incorrecto.",
"002-Fichero o dispositivo no encontrado",
"003-Error en partición de disco",
"004-Partición o fichero bloqueado",
"005-Error al crear o restaurar una imagen",
"006-Sin sistema operativo",
"007-Programa o función BOOLEAN no ejecutable",
"008-Error en la creación del archivo de eco para consola remota",
"009-Error en la lectura del archivo temporal de intercambio",
"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",
"012-Error en el envío de fichero por la red",
"013-Error en la creación del proceso hijo",
"014-Error de escritura en destino",
"015-Sin Cache en el Cliente",
"016-No hay espacio en la cache para almacenar fichero-imagen",
"017-Error al Reducir el Sistema Archivos",
"018-Error al Expandir el Sistema Archivos",
"019-Valor fuera de rango o no válido.",
"020-Sistema de archivos desconocido o no se puede montar",
"021-Error en partición de caché local",
"022-El disco indicado no contiene una particion GPT",
"023-Error no definido",
"024-Error no definido",
"025-Error no definido",
"026-Error no definido",
"027-Error no definido",
"028-Error no definido",
"029-Error no definido",
"030-Error al restaurar imagen - Imagen mas grande que particion",
"031-Error al realizar el comando updateCache",
"032-Error al formatear",
"033-Archivo de imagen corrupto o de otra versión de partclone",
"034-Error no definido",
"035-Error no definido",
"036-Error no definido",
"037-Error no definido",
"038-Error no definido",
"039-Error no definido",
"040-Error imprevisto no definido",
"041-Error no definido",
"042-Error no definido",
"043-Error no definido",
"044-Error no definido",
"045-Error no definido",
"046-Error no definido",
"047-Error no definido",
"048-Error no definido",
"049-Error no definido",
"050-Error en la generación de sintaxis de transferenica unicast",
"051-Error en envio UNICAST de una particion",
"052-Error en envio UNICAST de un fichero",
"053-Error en la recepcion UNICAST de una particion",
"054-Error en la recepcion UNICAST de un fichero",
"055-Error en la generacion de sintaxis de transferenica Multicast",
"056-Error en envio MULTICAST de un fichero",
"057-Error en la recepcion MULTICAST de un fichero",
"058-Error en envio 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",
"061-Error no definido",
"062-Error no definido",
"063-Error no definido",
"064-Error no definido",
"065-Error no definido",
"066-Error no definido",
"067-Error no definido",
"068-Error no definido",
"069-Error no definido",
"070-Error al montar una imagen sincronizada.",
"071-Imagen no sincronizable (es monolitica).",
"072-Error al desmontar la imagen.",
"073-No se detectan diferencias entre la imagen basica y la particion.",
"074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.",
"Error desconocido",
]
"001-Formato de ejecución incorrecto.",
"002-Fichero o dispositivo no encontrado",
"003-Error en partición de disco",
"004-Partición o fichero bloqueado",
"005-Error al crear o restaurar una imagen",
"006-Sin sistema operativo",
"007-Programa o función BOOLEAN no ejecutable",
"008-Error en la creación del archivo de eco para consola remota",
"009-Error en la lectura del archivo temporal de intercambio",
"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",
"012-Error en el envío de fichero por la red",
"013-Error en la creación del proceso hijo",
"014-Error de escritura en destino",
"015-Sin Cache en el Cliente",
"016-No hay espacio en la cache para almacenar fichero-imagen",
"017-Error al Reducir el Sistema Archivos",
"018-Error al Expandir el Sistema Archivos",
"019-Valor fuera de rango o no válido.",
"020-Sistema de archivos desconocido o no se puede montar",
"021-Error en partición de caché local",
"022-El disco indicado no contiene una particion GPT",
"023-Error no definido",
"024-Error no definido",
"025-Error no definido",
"026-Error no definido",
"027-Error no definido",
"028-Error no definido",
"029-Error no definido",
"030-Error al restaurar imagen - Imagen mas grande que particion",
"031-Error al realizar el comando updateCache",
"032-Error al formatear",
"033-Archivo de imagen corrupto o de otra versión de partclone",
"034-Error no definido",
"035-Error no definido",
"036-Error no definido",
"037-Error no definido",
"038-Error no definido",
"039-Error no definido",
"040-Error imprevisto no definido",
"041-Error no definido",
"042-Error no definido",
"043-Error no definido",
"044-Error no definido",
"045-Error no definido",
"046-Error no definido",
"047-Error no definido",
"048-Error no definido",
"049-Error no definido",
"050-Error en la generación de sintaxis de transferenica unicast",
"051-Error en envio UNICAST de una particion",
"052-Error en envio UNICAST de un fichero",
"053-Error en la recepcion UNICAST de una particion",
"054-Error en la recepcion UNICAST de un fichero",
"055-Error en la generacion de sintaxis de transferenica Multicast",
"056-Error en envio MULTICAST de un fichero",
"057-Error en la recepcion MULTICAST de un fichero",
"058-Error en envio 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",
"061-Error no definido",
"062-Error no definido",
"063-Error no definido",
"064-Error no definido",
"065-Error no definido",
"066-Error no definido",
"067-Error no definido",
"068-Error no definido",
"069-Error no definido",
"070-Error al montar una imagen sincronizada.",
"071-Imagen no sincronizable (es monolitica).",
"072-Error al desmontar la imagen.",
"073-No se detectan diferencias entre la imagen basica y la particion.",
"074-Error al sincronizar, puede afectar la creacion/restauracion de la imagen.",
"Error desconocido",
]
def notifier (self, job_id, result):
result['job_id'] = job_id
@ -239,10 +239,10 @@ class ogLiveWorker(ServerWorker):
elem['thread'].join (0.05)
if not elem['thread'].is_alive():
logger.debug (f'is no longer alive, k ({k}) thread ({elem["thread"]})')
elem['running'] = False
elem['result'] = elem['thread'].result
del elem['thread']
logger.info (f'job finished, job_id ({k}) result ({elem["result"]})')
self.notifier (k, elem['result'])
time.sleep (1)
@ -260,6 +260,8 @@ class ogLiveWorker(ServerWorker):
body = {
'iph': self.IPlocal,
'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})')
self.REST.sendMessage ('clients/status/webhook', body)
@ -361,6 +363,9 @@ class ogLiveWorker(ServerWorker):
## /DEBUG
if 0 != p.returncode:
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:')
for l in serr.splitlines():
logger.error (f' {l}')
@ -422,23 +427,33 @@ class ogLiveWorker(ServerWorker):
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')
b = dbus.SystemBus()
dest = 'es.opengnsys.OGBrowser.browser'
path = '/'
interface = None
method = 'setURL'
signature = 's'
try:
b = dbus.SystemBus()
dest = 'es.opengnsys.OGBrowser.browser'
path = '/'
interface = None
method = 'setURL'
b.call_blocking (dest, path, interface, method, 's', [url])
except Exception as e:
if 'ServiceUnknown' in str(e):
logger.warning ('browser is not running, launching a new one')
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()
# Capturamos ServiceUnknown y también NameHasNoOwner que salta a veces por condiciones de carrera
if 'ServiceUnknown' in str(e) or 'NameHasNoOwner' in str(e):
if getattr(self, 'browser_process', None) is None or self.browser_process.poll() is not None:
logger.warning (f'browser is not running ({e}), launching a new one')
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:
logger.warning ('browser is already launching, ignoring duplicate launch request')
else:
logger.error (f'Error al cambiar URL: ({e})')
return False
# Como medida de contingencia, si falla por cualquier otro error de DBus y no hay proceso, lo lanzamos
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

View File

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