Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,7 @@ jobs:
release_name: ${{ env.VERSION }}
body: |
**Changes**:
- Fixed error while parsing dnf history events on Fedora 41+
- Added tests for Fedora 41, 42, 43 and 44
- Various minor fixes
draft: false
prerelease: false

Expand Down
4 changes: 2 additions & 2 deletions linupdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def main():
# Instanciate classes
my_exit = Exit()
my_app = App()
my_app_config = Config() # instanciate to generate and check config
Config() # instanciate to generate and check config
my_args = Args()
my_system = System()
my_module = Module()
Expand Down Expand Up @@ -104,7 +104,7 @@ def main():
# Check if reboot is required
if System().reboot_required():
print(' ' + Fore.YELLOW + 'Reboot is required' + Style.RESET_ALL)

# If auto reboot is enabled
if my_args.reboot:
reboot = True
Expand Down
29 changes: 11 additions & 18 deletions src/controllers/App/App.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ def initialize(self):
#
#-----------------------------------------------------------------------------------------------
def print_logo(self):
print(' __ ')
print('.__ .__ ____ __( o`- .___ __ ')
print(' __ ')
print('.__ .__ ____ __( o`- .___ __ ')
print(r'| | |__| ____ __ _\ \/ / \__ ________ __| _/____ _/ |_ ____ ')
print(r'| | | |/ \| | \ /| | | \____ \ / __ |\__ \\ ___/ __ \ ')
print(r'| |_| | | | | / \ ^^| | | |_> / /_/ | / __ \| | \ ___/ ')
Expand All @@ -145,21 +145,14 @@ def print_summary(self):
myAppConfig = Config()
mySystem = System()

# Define execution method
if not sys.stdin.isatty():
exec_method = 'automatic (no tty)'
else:
exec_method = 'manual (tty)'

print('Hostname: ' + Fore.YELLOW + socket.getfqdn() + Style.RESET_ALL)
print('OS: ' + Fore.YELLOW + mySystem.get_os_name() + ' ' + mySystem.get_os_version() + Style.RESET_ALL)
print('Kernel: ' + Fore.YELLOW + mySystem.get_kernel() + Style.RESET_ALL)
print('Virtualization: ' + Fore.YELLOW + mySystem.get_virtualization() + Style.RESET_ALL)
print('Profile: ' + Fore.YELLOW + myAppConfig.get_profile() + Style.RESET_ALL)
print('Environment: ' + Fore.YELLOW + myAppConfig.get_environment() + Style.RESET_ALL)
print('Execution date: ' + Fore.YELLOW + datetime.now().strftime('%d-%m-%Y %H:%M:%S') + Style.RESET_ALL)
print('Executed by user: ' + Fore.YELLOW + getpass.getuser() + Style.RESET_ALL)
print('Execution method: ' + Fore.YELLOW + exec_method + Style.RESET_ALL + '\n')
print('Hostname ' + Fore.YELLOW + socket.getfqdn() + Style.RESET_ALL)
print('OS ' + Fore.YELLOW + mySystem.get_os_name() + ' ' + mySystem.get_os_version() + Style.RESET_ALL)
print('Kernel ' + Fore.YELLOW + mySystem.get_kernel() + Style.RESET_ALL)
print('Virtualization ' + Fore.YELLOW + mySystem.get_virtualization() + Style.RESET_ALL)
print('Profile ' + Fore.YELLOW + myAppConfig.get_profile() + Style.RESET_ALL)
print('Environment ' + Fore.YELLOW + myAppConfig.get_environment() + Style.RESET_ALL)
print('Execution date ' + Fore.YELLOW + datetime.now().strftime('%d-%m-%Y %H:%M:%S') + Style.RESET_ALL)
print('Interactive exec. ' + Fore.YELLOW + 'Yes' + Style.RESET_ALL + '\n' if sys.stdin.isatty() else 'No' + Style.RESET_ALL + '\n')


#-----------------------------------------------------------------------------------------------
Expand All @@ -175,7 +168,7 @@ def is_running(self)-> bool:
universal_newlines = True,
shell = True
)

if result.returncode == 0:
return True

Expand Down
10 changes: 10 additions & 0 deletions src/controllers/App/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ def check_conf(self) -> None:
if 'recipient' not in configuration['main']['mail']:
raise Exception('main.mail.recipient key is missing in ' + self.config_file)

# If main.mail.smtp_host is not set, default to 'localhost'
if 'smtp_host' not in configuration['main']['mail']:
configuration['main']['mail']['smtp_host'] = 'localhost'
write_config = True

# If main.mail.smtp_port is not set, default to 25
if 'smtp_port' not in configuration['main']['mail']:
configuration['main']['mail']['smtp_port'] = 25
write_config = True

# If main.log_retention_days is not set, default to 180 days (~6 months)
if 'log_retention_days' not in configuration['main']:
configuration['main']['log_retention_days'] = 180
Expand Down
6 changes: 3 additions & 3 deletions src/controllers/Args.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def parse(self):
else:
myAppConfig.set_mail_smtp_port(args.mail_smtp_port)
print('Mail SMTP port set to ' + Fore.GREEN + str(myAppConfig.get_mail_smtp_port()) + Style.RESET_ALL, end='\n\n')

myExit.clean_exit()
except Exception as e:
raise ArgsException('Could not set mail SMTP port: ' + str(e))
Expand Down Expand Up @@ -420,7 +420,7 @@ def parse(self):
myExit.clean_exit()
except Exception as e:
raise ArgsException('Could not configure exit on package update error: ' + str(e))

#
# If --reboot param has been set, set reboot to True
#
Expand Down Expand Up @@ -555,7 +555,7 @@ def parse(self):
try:
if not args.service_restart:
print('Services to restart after package update: ' + Fore.GREEN)

services = myAppConfig.get_service_to_restart()

# If no service is set to restart
Expand Down
20 changes: 10 additions & 10 deletions src/controllers/Logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, log_file_path, level=logging.INFO):
self.logger = logging.getLogger("StreamToLogger")
self.level = level
self.buffer = '' # Used to accumulate partial messages

# Detect if we're running in interactive mode (TTY)
# Use original streams for TTY detection (not modified by Rich or other libraries)
self.is_interactive = sys.__stdout__.isatty()
Expand Down Expand Up @@ -51,17 +51,17 @@ def write(self, message):
self.buffer += message
while '\n' in self.buffer: # If a newline is detected
line, self.buffer = self.buffer.split('\n', 1)

# Clean the line for file output (always without ANSI)
cleaned_line = Utils().clean_log(line)

if self.is_interactive:
# Interactive mode: console gets original line (with ANSI), file gets cleaned line
record_console = self.logger.makeRecord(self.logger.name, self.level, '', 0, line, (), None)
else:
# Non-interactive mode: console also gets cleaned line (without ANSI)
record_console = self.logger.makeRecord(self.logger.name, self.level, '', 0, cleaned_line, (), None)

# Emit to console
self.console_handler.emit(record_console)

Expand All @@ -76,15 +76,15 @@ def flush(self):

if self.is_interactive:
# Interactive mode: console gets original buffer (with ANSI), file gets cleaned buffer
record_console = self.logger.makeRecord(self.logger.name, self.level, '', 0, self.buffer, (), None)
record_console = self.logger.makeRecord(self.logger.name, self.level, '', 0, self.buffer, (), None)
else:
# Non-interactive mode: console also gets cleaned buffer (without ANSI)
record_console = self.logger.makeRecord(self.logger.name, self.level, '', 0, cleaned_buffer, (), None)

# Emit to console
# Emit to console
self.console_handler.emit(record_console)

# Emit to file (always cleaned buffer)
# Emit to file (always cleaned buffer)
record_file = self.logger.makeRecord(self.logger.name, self.level, '', 0, cleaned_buffer, (), None)
self.file_handler.emit(record_file)
self.buffer = ''
Expand All @@ -98,15 +98,15 @@ def __enter__(self):
def __exit__(self, exc_type, exc_value, traceback):
# Flush the buffer if data remains
self.flush()

# Restore standard streams
sys.stdout = self.original_stdout
sys.stderr = self.original_stderr

# Properly close handlers to avoid resource leaks
self.file_handler.close()
self.console_handler.close()

# Do not log SystemExit and KeyboardInterrupt which are normal exits
# if exc_type is not None and exc_type not in (SystemExit, KeyboardInterrupt):
# self.logger.error("Exception occurred", exc_info=(exc_type, exc_value, traceback))
2 changes: 1 addition & 1 deletion src/controllers/Module/Reposerver/Agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def websocket_on_message(self, ws, message):
if message['info'] == 'Request response received':
# First retrieve the request id
if 'request-id' in message:
request_id = message['request-id']
request_id = str(message['request-id'])

# If the server has tell what kinf of data it has received, then delete the corresponding files if they exist
if 'data' in message:
Expand Down
8 changes: 4 additions & 4 deletions src/controllers/Module/Reposerver/Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,11 @@ def check_conf(self):
# Check if client.get_repos_from_reposerver.format is set to legacy or deb822
if configuration['client']['get_repos_from_reposerver']['format'] not in ['standard', 'legacy', 'deb822']:
raise Exception('client.get_repos_from_reposerver.format key must be set to legacy or deb822')

# If format is set to standard, overwrite it to legacy and write the config file
if configuration['client']['get_repos_from_reposerver']['format'] == 'standard':
configuration['client']['get_repos_from_reposerver']['format'] = 'legacy'
write_config = True
write_config = True

# Check if agent is set
if 'agent' not in configuration:
Expand Down Expand Up @@ -626,7 +626,7 @@ def get_profile_repos(self):
file.unlink()
except Exception as e:
raise Exception('failed to remove existing repositories: ' + str(e))

# Quit if no results
if not results['repos']:
print(Fore.YELLOW + 'No repositories configured ' + Style.RESET_ALL)
Expand Down Expand Up @@ -654,7 +654,7 @@ def get_profile_repos(self):
self.httpRequestController.download(gpgkey_url, gpgkey_output_file, 5, 3)
except Exception:
raise Exception('failed to download GPG key from ' + gpgkey_url)

# Set file permissions to 644
try:
Path(gpgkey_output_file).chmod(0o644)
Expand Down
8 changes: 4 additions & 4 deletions src/controllers/Package/Apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def clear_cache(self):
aptcache = apt.Cache()
aptcache.clear()
aptcache.close()

del aptcache
except Exception as e:
raise Exception('could not clear apt cache: ' + str(e))
Expand All @@ -384,7 +384,7 @@ def update_cache(self):
try:
# Wait for dpkg lock first
self.wait_for_dpkg_lock()

# Use apt update command directly for more reliable cache refresh
subprocess.run(
["apt-get", "update"],
Expand All @@ -393,7 +393,7 @@ def update_cache(self):
universal_newlines = True,
check = True # Raise exception if command fails
)

except subprocess.CalledProcessError as e:
raise Exception('could not update apt cache via command line: ' + e.stderr)
except Exception as e:
Expand All @@ -404,7 +404,7 @@ def update_cache(self):
self.wait_for_dpkg_lock()
aptcache = apt.Cache()
aptcache.open(None)

# Force a complete refresh of the cache object
aptcache.close()
aptcache.clear()
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/Package/Package.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def update(self,
keep_oldconf: bool = True,
clear_cache: bool = False,
dry_run: bool = False):

restart_file = '/tmp/linupdate.restart-needed'

# Package update summary
Expand Down Expand Up @@ -462,7 +462,7 @@ def update(self,
# Remove all exclusions before exiting
self.remove_all_exclusions()
self.exitController.clean_exit()

# If assume_yes, just print the message
update_status(' ')

Expand Down
6 changes: 3 additions & 3 deletions src/controllers/Status.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ def __new__(cls):
if cls._instance is None:
cls._instance = super(StatusManager, cls).__new__(cls)
return cls._instance

#-----------------------------------------------------------------------------------------------
#
# Set or update the status message
#
#-----------------------------------------------------------------------------------------------
def set_status(self, status):
self._status = status

#-----------------------------------------------------------------------------------------------
#
# Update the status message
Expand Down Expand Up @@ -59,7 +59,7 @@ def restore_saved_message(self):
self._status.update(self._saved_message)
self._current_message = self._saved_message
self._saved_message = None # Clear saved message after restore

#-----------------------------------------------------------------------------------------------
#
# Clear the status object reference
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/System.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def get_cpu_info(self):
raise Exception('could not get number of CPU cores: ' + str(e))

return cpu_info


#-----------------------------------------------------------------------------------------------
#
Expand Down Expand Up @@ -204,7 +204,7 @@ def reboot_required(self):
return True

return False


#-----------------------------------------------------------------------------------------------
#
Expand Down
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.17.2
3.17.3
Loading