Compare commits

..

2 Commits

Author SHA1 Message Date
Vadim vtroshchinskiy f4ec71ccfa Packaging changes 2025-05-29 09:39:13 +02:00
Vadim vtroshchinskiy 14f4a0c7a0 Soporte de oggit 2025-05-29 09:37:53 +02:00
12 changed files with 150 additions and 213 deletions

View File

@ -6,37 +6,6 @@ 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).
## [5.8.0] - 2025-06-12
### Changed
- Agents aren't being sent any signals on user logout. On the server side, assume that client disconnection == logout
## [5.7.1] - 2025-06-05
### Fixed
- Correcly handle UNIX signals in the user instance of the agent
## [5.7.0] - 2025-05-27
### Changed
- Use TLS again
## [5.6.0] - 2025-05-21
### Changed
- Launch QT6 browser
- Change URLs using dbus
## [5.5.0] - 2025-05-19
### Changed
- Revert to the QT4 browser again
## [5.4.0] - 2025-05-19 ## [5.4.0] - 2025-05-19
### Changed ### Changed

View File

@ -3,4 +3,4 @@
cd $(dirname "$0") cd $(dirname "$0")
# Debian based # Debian based
debuild -D --build=binary --post-clean --lintian-opts --profile debian debuild -D -us -uc --build=binary --post-clean --lintian-opts --profile debian

View File

@ -1,33 +1,8 @@
ogagent (5.8.0-1) stable; urgency=medium ogagent (5.4.1-1) stable; urgency=medium
* When client disconnect, assume that the user logged out * Oggit support
-- OpenGnsys developers <info@opengnsys.es> Thu, 12 Jun 2025 15:30:50 +0200 -- OpenGnsys developers <info@opengnsys.es> Mon, 29 May 2025 09:46:42 +0200
ogagent (5.7.1-1) stable; urgency=medium
* Correctly handle UNIX signals
-- OpenGnsys developers <info@opengnsys.es> Thu, 05 Jun 2025 12:07:30 +0200
ogagent (5.7.0-1) stable; urgency=medium
* Use TLS again
-- OpenGnsys developers <info@opengnsys.es> Wed, 21 May 2025 17:39:13 +0200
ogagent (5.6.0-1) stable; urgency=medium
* Execute 'launch_browser' rather than 'browser'
* Change URLs using dbus
-- OpenGnsys developers <info@opengnsys.es> Wed, 21 May 2025 15:06:52 +0200
ogagent (5.5.0-1) stable; urgency=medium
* Return to the QT4 browser again
-- OpenGnsys developers <info@opengnsys.es> Mon, 19 May 2025 10:57:37 +0200
ogagent (5.4.0-1) stable; urgency=medium ogagent (5.4.0-1) stable; urgency=medium

View File

@ -35,8 +35,6 @@ import json
import sys import sys
import time import time
import os import os
import socket
import signal
from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from about_dialog_ui import Ui_OGAAboutDialog from about_dialog_ui import Ui_OGAAboutDialog
@ -330,29 +328,6 @@ if __name__ == '__main__':
trayIcon.quit() trayIcon.quit()
sys.exit(1) sys.exit(1)
## begin SIGTERM handling
signal_socket = socket.socketpair()
signal_socket[0].setblocking(False)
signal_socket[1].setblocking(False)
signal.set_wakeup_fd(signal_socket[0].fileno())
def signal_handler(signum, frame):
#print (f"Received signal {signum}")
pass
def qt_signal_handler():
data = signal_socket[1].recv(1)
#print(f"Signal ({data}) received via socket, shutting down gracefully...")
if trayIcon:
trayIcon.quit()
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
notifier = QtCore.QSocketNotifier(signal_socket[1].fileno(), QtCore.QSocketNotifier.Type.Read)
notifier.activated.connect(qt_signal_handler)
## end SIGTERM handling
app.aboutToQuit.connect(trayIcon.cleanup) app.aboutToQuit.connect(trayIcon.cleanup)
trayIcon.show() trayIcon.show()

View File

@ -1 +1 @@
5.8.0 5.4.0

View File

@ -20,11 +20,9 @@ log=DEBUG
imgname= imgname=
# TLS # TLS
# The agent will look for these files in /opt/opengnsys/etc, /usr/share/OGAgent, ca=C:\Program Files (x86)\OGagent\ca.crt
# windows "Program Files (x86)" and the current working directory crt=C:\Program Files (x86)\OGagent\ogagent.crt
ca=ca.crt key=C:\Program Files (x86)\OGagent\ogagent.key
crt=ogagent.crt
key=ogagent.key
# Module specific # Module specific

View File

@ -45,7 +45,6 @@ from .log import logger
from .utils import exceptionToMessage from .utils import exceptionToMessage
TIMEOUT = 5 # Connection timout, in seconds TIMEOUT = 5 # Connection timout, in seconds
VERIFY_TLS=True
class RESTError(Exception): class RESTError(Exception):
@ -96,7 +95,6 @@ class REST(object):
@param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired. @param url The url of the REST API Base. The trailing '/' can be included or omitted, as desired.
""" """
self.endpoint = url self.endpoint = url
global VERIFY_TLS
if self.endpoint[-1] != '/': if self.endpoint[-1] != '/':
self.endpoint += '/' self.endpoint += '/'
@ -111,47 +109,21 @@ class REST(object):
logger.debug ('TLS not available: python requests library is old') logger.debug ('TLS not available: python requests library is old')
self.use_tls = url.startswith ('https') self.use_tls = url.startswith ('https')
if self.use_tls: #if self.use_tls:
if not ca_file or not crt_file or not key_file: # if not ca_file or not crt_file or not key_file:
raise Exception ('missing TLS parameters in REST constructor') # raise Exception ('missing TLS parameters in REST constructor')
#
certs_dirs = ['/opt/opengnsys/etc', '/usr/share/OGAgent'] # errs = 0
pf = os.environ.get ('PROGRAMFILES(X86)') # for f in [ca_file, crt_file, key_file]:
if pf: certs_dirs.append (os.path.join (pf, 'OGAgent')) # if not os.path.exists (f):
certs_dirs.append (os.getcwd()) # logger.error (f'{f}: No such file or directory')
certs_dir = None # errs += 1
for sp in certs_dirs: # if errs:
if os.path.exists (sp): # raise Exception ('TLS files not found')
logger.debug (f'Looking for TLS files in ({sp})') #
certs_dir = sp #self.ca_file = ca_file
break #self.crt_file = crt_file
#self.key_file = key_file
if not certs_dir:
logger.debug ("Don't know where to look for TLS files")
errs = 1
else:
errs = 0
for f in [ca_file, crt_file, key_file]:
if os.path.exists (f'{certs_dir}/{f}'):
logger.debug (f'{certs_dir}/{f}: found')
else:
logger.error (f'{f}: No such file or directory')
errs += 1
if errs:
self.verify_tls = False
logger.debug ('HTTP client: using insecure TLS to talk to ogcore due to missing files')
else:
self.ca_file = f'{certs_dir}/{ca_file}'
self.crt_file = f'{certs_dir}/{crt_file}'
self.key_file = f'{certs_dir}/{key_file}'
self.verify_tls = VERIFY_TLS
if self.verify_tls:
logger.debug ('HTTP client: using TLS to talk to ogcore')
else:
logger.debug ('HTTP client: using insecure TLS as requested to talk to ogcore')
else:
logger.debug ('HTTP client: not using TLS to talk to ogcore')
# Disable logging requests messages except for errors, ... # Disable logging requests messages except for errors, ...
logging.getLogger("requests").setLevel(logging.CRITICAL) logging.getLogger("requests").setLevel(logging.CRITICAL)
@ -184,11 +156,7 @@ class REST(object):
# Old requests version does not support verify, but it do not checks ssl certificate by default # Old requests version does not support verify, but it do not checks ssl certificate by default
if self.newerRequestLib: if self.newerRequestLib:
if self.use_tls: if self.use_tls:
if self.verify_tls: r = requests.get(url, verify=False, timeout=TIMEOUT)
r = requests.get(url, cert=(self.crt_file, self.key_file), verify=self.ca_file, timeout=TIMEOUT)
else:
logger.warning ('using insecure TLS for GET')
r = requests.get(url, verify=False, timeout=TIMEOUT)
else: else:
r = requests.get(url, timeout=TIMEOUT) r = requests.get(url, timeout=TIMEOUT)
else: else:
@ -197,11 +165,7 @@ class REST(object):
logger.debug('Requesting using POST {}, data: {}'.format(url, data)) logger.debug('Requesting using POST {}, data: {}'.format(url, data))
if self.newerRequestLib: if self.newerRequestLib:
if self.use_tls: if self.use_tls:
if self.verify_tls: r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=False, timeout=TIMEOUT)
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, cert=(self.crt_file, self.key_file), verify=self.ca_file, timeout=TIMEOUT)
else:
logger.warning ('using insecure TLS for POST')
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, verify=False, timeout=TIMEOUT)
else: else:
r = requests.post(url, data=data, headers={'content-type': 'application/json'}, timeout=TIMEOUT) r = requests.post(url, data=data, headers={'content-type': 'application/json'}, timeout=TIMEOUT)
else: else:

View File

@ -43,7 +43,6 @@ from .utils import exceptionToMessage
from .certs import createSelfSignedCert from .certs import createSelfSignedCert
from .log import logger from .log import logger
VERIFY_TLS=True
class HTTPServerHandler(BaseHTTPRequestHandler): class HTTPServerHandler(BaseHTTPRequestHandler):
service = None service = None
@ -154,46 +153,15 @@ class HTTPThreadingServer(ThreadingMixIn, HTTPServer):
class HTTPServerThread(threading.Thread): class HTTPServerThread(threading.Thread):
def __init__(self, address, service): def __init__(self, address, service):
super(self.__class__, self).__init__() super(self.__class__, self).__init__()
global VERIFY_TLS
HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it HTTPServerHandler.service = service # Keep tracking of service so we can intercact with it
self.certFile = createSelfSignedCert()
self.server = HTTPThreadingServer(address, HTTPServerHandler) self.server = HTTPThreadingServer(address, HTTPServerHandler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=self.certFile)
pf = os.environ.get ('PROGRAMFILES(X86)')
if pf: pf = os.path.join (pf, 'OGAgent')
if os.path.exists ('/opt/opengnsys/etc/ogagent.crt') and os.path.exists ('/opt/opengnsys/etc/ogagent.key') and os.path.exists ('/opt/opengnsys/etc/ca.crt'):
logger.debug ('HTTP server: using certificate/CA from /opt/opengnsys/etc')
context.load_cert_chain (certfile='/opt/opengnsys/etc/ogagent.crt', keyfile='/opt/opengnsys/etc/ogagent.key')
context.load_verify_locations (cafile='/opt/opengnsys/etc/ca.crt')
elif pf and os.path.exists (os.path.join (pf, 'ogagent.crt')) and os.path.exists (os.path.join (pf, 'ogagent.key')) and os.path.exists (os.path.join (pf, 'ca.crt')):
logger.debug (f'HTTP server: using certificate/CA from the installation path ({pf})')
context.load_cert_chain (certfile=os.path.join (pf, 'ogagent.crt'), keyfile=os.path.join (pf, 'ogagent.key'))
context.load_verify_locations (cafile=os.path.join (pf, 'ca.crt'))
elif os.path.exists ('./ogagent.crt') and os.path.exists ('./ogagent.key') and os.path.exists ('./ca.crt'):
cwd = os.getcwd()
logger.debug (f'HTTP server: using certificate/CA from the current working directory ({cwd})')
context.load_cert_chain (certfile=f'{cwd}/ogagent.crt', keyfile=f'{cwd}/ogagent.key')
context.load_verify_locations (cafile=f'{cwd}/ca.crt')
else:
logger.debug ('HTTP server: using a self-signed certificate')
self.certFile = createSelfSignedCert()
context.load_cert_chain (certfile=self.certFile)
VERIFY_TLS = False
if VERIFY_TLS:
context.verify_mode = ssl.CERT_REQUIRED
context.verify_flags &= ssl.VERIFY_X509_STRICT
else:
context.verify_mode = ssl.CERT_NONE
context.verify_flags &= ~ssl.VERIFY_X509_STRICT
s = context.cert_store_stats()
if 'x509_ca' in s: logger.debug (f'HTTP server: {s['x509_ca']} CAs loaded')
if 'x509' in s: logger.debug (f'HTTP server: {s['x509']} certs loaded')
self.server.socket = context.wrap_socket(self.server.socket, server_side=True) self.server.socket = context.wrap_socket(self.server.socket, server_side=True)
logger.debug('Initialized HTTPS Server thread on {}'.format(address)) logger.debug('Initialized HTTPS Server thread on {}'.format(address))
def getServerUrl(self): def getServerUrl(self):

View File

@ -30,7 +30,6 @@
""" """
import os
import json import json
import queue import queue
import socket import socket
@ -194,8 +193,6 @@ class ClientProcessor(threading.Thread):
logger.error('Invalid message in queue: {}'.format(e)) logger.error('Invalid message in queue: {}'.format(e))
logger.debug('Client processor stopped') logger.debug('Client processor stopped')
if os.path.exists ('/windows/temp'): open ('/windows/temp/ogagentuser_died', 'w').close()
else: open ( '/tmp/ogagentuser_died', 'w').close()
try: try:
self.clientSocket.close() self.clientSocket.close()
except Exception: except Exception:

View File

@ -360,6 +360,60 @@ class ogAdmClientWorker (ogLiveWorker):
} }
return self.respuestaEjecucionComando (cmd, herror, ids) return self.respuestaEjecucionComando (cmd, herror, ids)
def do_CrearImagenGit (self, post_params):
for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk'] ## Disco
par = post_params['par'] ## Número de partición
cpt = post_params['cpt'] ## Código de la partición
idi = post_params['idi'] ## Identificador de la imagen
nci = post_params['nci'] ## Nombre canónico de la imagen
ipr = post_params['ipr'] ## Ip del repositorio
nfn = post_params['nfn']
ids = post_params['ids']
tag = post_params['tag'] ## Tag a crear en git una vez hecho el commit
self.muestraMensaje (7)
try:
res = self.InventariandoSoftware (dsk, par, 'InventarioSoftware') ## Crea inventario Software previamente
except:
logger.warning ('Error al ejecutar el comando')
return {}
if res['contents']:
self.muestraMensaje (2)
inv_sft = res['contents']
try:
self.interfaceAdmin (nfn, [dsk, par, nci, ipr, tag])
self.muestraMensaje (9)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (10)
herror = 1
else:
logger.warning ('Error al ejecutar el comando')
herror = 1
inv_sft = ''
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_CrearImagenGit',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición de donde se creó
'cpt': cpt, ## Tipo o código de partición
'ipr': ipr, ## Ip del repositorio donde se alojó
'inv_sft': inv_sft
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_RestaurarImagen (self, post_params): def do_RestaurarImagen (self, post_params):
for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']: for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']:
if k not in post_params: if k not in post_params:
@ -406,6 +460,54 @@ class ogAdmClientWorker (ogLiveWorker):
} }
return self.respuestaEjecucionComando (cmd, herror, ids) return self.respuestaEjecucionComando (cmd, herror, ids)
def do_RestaurarImagenGit (self, post_params):
for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids', 'ref']:
if k not in post_params:
logger.error (f'required parameter ({k}) not in POST params')
return {}
dsk = post_params['dsk']
par = post_params['par']
idi = post_params['idi']
ipr = post_params['ipr']
nci = post_params['nci']
ifs = post_params['ifs']
ptc = post_params['ptc'] ## Protocolo de clonación: Unicast, Multicast, Torrent
nfn = post_params['nfn']
ids = post_params['ids']
ref = post_params['ref'] ## Referencia de git a restaurar
self.muestraMensaje (3)
try:
## the ptc.split() is useless right now, since interfaceAdmin() does ' '.join(params) in order to spawn a shell
## however we're going to need it in the future (when everything gets translated into python), plus it's harmless now. So let's do it
#self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc])
self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ref] + ptc.split())
self.muestraMensaje (11)
herror = 0
except:
logger.warning ('Error al ejecutar el comando')
self.muestraMensaje (12)
herror = 1
cfg = self.LeeConfiguracion()
if not cfg:
logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
self.muestraMenu()
cmd = {
'nfn': 'RESPUESTA_RestaurarImagenGit',
'idi': idi, ## Identificador de la imagen
'dsk': dsk, ## Número de disco
'par': par, ## Número de partición
'ifs': ifs, ## Identificador del perfil software
'cfg': self.cfg2obj(cfg), ## Configuración de discos
}
return self.respuestaEjecucionComando (cmd, herror, ids)
def do_Configurar (self, post_params): def do_Configurar (self, post_params):
for k in ['nfn', 'dsk', 'cfg', 'ids']: for k in ['nfn', 'dsk', 'cfg', 'ids']:
if k not in post_params: if k not in post_params:
@ -816,6 +918,16 @@ class ogAdmClientWorker (ogLiveWorker):
logger.debug ('type(post_params) "{}"'.format (type (post_params))) logger.debug ('type(post_params) "{}"'.format (type (post_params)))
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):
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)))
return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, 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.debug ('type(post_params) "{}"'.format (type (post_params)))
return self._long_running_job ('RestaurarImagenGit', self.do_RestaurarImagenGit, args=(post_params,))
#def process_CrearImagenBasica (self, path, get_params, post_params, server): #def process_CrearImagenBasica (self, path, get_params, post_params, server):
# logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server)) # logger.debug ('in process_CrearImagenBasica, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
# logger.warning ('this method has been removed') # logger.warning ('this method has been removed')

View File

@ -30,7 +30,6 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com @author: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import os
import json import json
import socket import socket
import time import time
@ -198,16 +197,6 @@ class CommonService(object):
Invoked to wait a bit Invoked to wait a bit
CAN be OVERRIDDEN CAN be OVERRIDDEN
""" """
client_died=False
if os.path.exists ('/windows/temp/ogagentuser_died'):
os.unlink ('/windows/temp/ogagentuser_died')
client_died=True
elif os.path.exists ('/tmp/ogagentuser_died'):
os.unlink ('/tmp/ogagentuser_died')
client_died=True
if client_died:
self.notifyLogout (b'')
time.sleep(float(miliseconds) / 1000) time.sleep(float(miliseconds) / 1000)
def notifyStop(self): def notifyStop(self):

View File

@ -33,8 +33,6 @@
import os import os
import re import re
import time import time
try: import dbus ## don't fail on windows (the worker will later refuse to load anyway)
except: pass
import random import random
import subprocess import subprocess
import threading import threading
@ -259,7 +257,7 @@ class ogLiveWorker(ServerWorker):
self.REST.sendMessage ('clients/status/webhook', body) self.REST.sendMessage ('clients/status/webhook', body)
def interfaceAdmin (self, method, parametros=[]): def interfaceAdmin (self, method, parametros=[]):
if method in ['Apagar', 'CambiarAcceso', 'Configurar', 'CrearImagen', 'EjecutarScript', 'getConfiguration', 'getIpAddress', 'IniciarSesion', 'InventarioHardware', 'InventarioSoftware', 'Reiniciar', 'RestaurarImagen']: if method in ['Apagar', 'CambiarAcceso', 'Configurar', 'CrearImagen', 'CrearImagenGit', 'EjecutarScript', 'getConfiguration', 'getIpAddress', 'IniciarSesion', 'InventarioHardware', 'InventarioSoftware', 'Reiniciar', 'RestaurarImagen', 'RestaurarImagenGit']:
## python ## python
logger.debug (f'({method}) is a python method') logger.debug (f'({method}) is a python method')
exe = '{}/{}.py'.format (self.pathinterface, method) exe = '{}/{}.py'.format (self.pathinterface, method)
@ -371,25 +369,17 @@ class ogLiveWorker(ServerWorker):
def cargaPaginaWeb (self, url=None): def cargaPaginaWeb (self, url=None):
if (not url): url = self.urlMenu if (not url): url = self.urlMenu
os.system ('pkill -f -9 browser')
os.system ('pkill -f -9 OGBrowser')
os.system ('pkill -f -9 QtWebEngineProcess')
dbus_address = os.environ.get ('DBUS_SESSION_BUS_ADDRESS') p = subprocess.Popen (['/usr/bin/launch_browser', url])
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.call_blocking (dest, path, interface, method, 's', [url]) p.wait (2) ## if the process dies before 2 seconds...
except Exception as e: logger.error ('Error al ejecutar OGBrowser, return code "{}"'.format (p.returncode))
if 'ServiceUnknown' in str(e): return False
## browser not running except subprocess.TimeoutExpired:
subprocess.Popen (['/usr/bin/launch_browser', url]) pass
else:
logger.error (f'Error al cambiar URL: ({e})')
return False
return True return True