diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py
index 7ef1c993..4f5b3b9c 100644
--- a/bauh/gems/arch/controller.py
+++ b/bauh/gems/arch/controller.py
@@ -57,6 +57,7 @@ from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCom
URL_GIT = 'https://aur.archlinux.org/{}.git'
URL_SRC_INFO = 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h='
+URL_ARCH_NEWS_FEED = 'https://archlinux.org/feeds/news/'
RE_SPLIT_VERSION = re.compile(r'([=><]+)')
@@ -67,6 +68,9 @@ RE_PKGBUILD_PKGNAME = re.compile(r'pkgname\s*=.+')
RE_CONFLICT_DETECTED = re.compile(r'\n::\s*(.+)\s+are in conflict\s*.')
RE_DEPENDENCY_BREAKAGE = re.compile(r'\n?::\s+installing\s+(.+\s\(.+\))\sbreaks\sdependency\s\'(.+)\'\srequired\sby\s(.+)\s*', flags=re.IGNORECASE)
RE_PKG_ENDS_WITH_BIN = re.compile(r'.+[\-_]bin$')
+RE_ARCH_NEWS_ITEM = re.compile(r'- (.*?)
', flags=re.DOTALL)
+RE_ARCH_NEWS_TITLE = re.compile(r'
|(.*?)', flags=re.DOTALL)
+RE_ARCH_NEWS_LINK = re.compile(r'(.*?)', flags=re.DOTALL)
class TransactionContext:
@@ -3267,6 +3271,10 @@ class ArchManager(SoftwareManager, SettingsController):
type_=MessageType.INFO)
return False
+ if not self._confirm_upgrade_risks(watcher=watcher, internet_available=net_available):
+ watcher.print('Operation cancelled')
+ return False
+
# icon_path = get_repo_icon_path()
# pkg_opts, size = [], 0
@@ -3323,6 +3331,81 @@ class ArchManager(SoftwareManager, SettingsController):
watcher.request_reboot(msg)
return True
+ def _read_pending_pacnew_files(self, limit: int = 30) -> List[str]:
+ output = run_cmd("find /etc -type f -name '*.pacnew' 2>/dev/null", print_error=False)
+
+ if not output:
+ return []
+
+ files = sorted((line.strip() for line in output.splitlines() if line and line.strip()))
+ return files[:limit]
+
+ def _fetch_arch_news(self, limit: int = 5) -> List[Tuple[str, str]]:
+ if not self.context.http_client:
+ return []
+
+ try:
+ res = self.context.http_client.get(URL_ARCH_NEWS_FEED, session=True, single_call=True)
+ except Exception:
+ return []
+
+ if not res or res.status_code != 200 or not res.text:
+ return []
+
+ news = []
+ for item in RE_ARCH_NEWS_ITEM.findall(res.text):
+ title_match = RE_ARCH_NEWS_TITLE.search(item)
+ link_match = RE_ARCH_NEWS_LINK.search(item)
+
+ if not title_match or not link_match:
+ continue
+
+ title = (title_match.group(1) or title_match.group(2) or '').strip()
+ link_ = link_match.group(1).strip()
+
+ if title and link_:
+ news.append((title, link_))
+
+ if len(news) >= limit:
+ break
+
+ return news
+
+ def _confirm_upgrade_risks(self, watcher: ProcessWatcher, internet_available: bool) -> bool:
+ pacnew_files = self._read_pending_pacnew_files()
+ arch_news = self._fetch_arch_news() if internet_available else []
+
+ if not pacnew_files and not arch_news:
+ return True
+
+ body_parts = [
+ 'Please review system notices before upgrading.
'
+ ]
+
+ if pacnew_files:
+ max_items = 10
+ listed = ''.join(f'{p}' for p in pacnew_files[:max_items])
+ extra = len(pacnew_files) - max_items
+ extra_msg = f'... and {extra} more.
' if extra > 0 else ''
+ body_parts.append(
+ f"Detected {len(pacnew_files)} pending .pacnew file(s):
{extra_msg}"
+ )
+
+ if arch_news:
+ listed = ''.join(f"{title}" for title, link_ in arch_news)
+ body_parts.append(
+ f"Recent Arch Linux news (review before upgrading):
"
+ )
+
+ body_parts.append('Proceed with the system upgrade now?
')
+
+ return watcher.request_confirmation(
+ title=self.i18n['arch.custom_action.upgrade_system'],
+ body=''.join(body_parts),
+ confirmation_label=self.i18n['proceed'].capitalize(),
+ deny_label=self.i18n['cancel'].capitalize()
+ )
+
def clean_cache(self, root_password: Optional[str], watcher: ProcessWatcher) -> bool:
cache_dir = pacman.get_cache_dir()