Skip to content

Commit 960287b

Browse files
Merge pull request #25 from SomethingGeneric/copilot/fix-64b98d26-40d2-4576-bde5-fb633e37a92d
Add OpenBSD support with pkg_add package manager
2 parents c48e308 + b846bd2 commit 960287b

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A Python tool that SSHs to an inventory of hosts (Ansible format), identifies th
1515
- Arch Linux (pacman)
1616
- Alpine Linux (apk)
1717
- FreeBSD (pkg)
18+
- OpenBSD (pkg_add)
1819
- macOS (brew)
1920

2021
- **Proxmox VE Integration**: VM snapshot management for safe automated updates

miniupdate/os_detector.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class OSDetector:
4444
'pacman': ['/usr/bin/pacman'],
4545
'apk': ['/sbin/apk'],
4646
'pkg': ['/usr/sbin/pkg'], # FreeBSD
47+
'pkg_add': ['/usr/sbin/pkg_add'], # OpenBSD
4748
'brew': ['/usr/local/bin/brew', '/opt/homebrew/bin/brew'], # macOS
4849
}
4950

@@ -63,6 +64,7 @@ class OSDetector:
6364
'manjaro': ('linux', 'pacman'),
6465
'alpine': ('linux', 'apk'),
6566
'freebsd': ('freebsd', 'pkg'),
67+
'openbsd': ('openbsd', 'pkg_add'),
6668
'darwin': ('darwin', 'brew'),
6769
'macos': ('darwin', 'brew'),
6870
}
@@ -185,7 +187,7 @@ def _parse_os_info(self, uname_info: Dict[str, str],
185187
if 'Release' in lsb_info:
186188
version = lsb_info['Release']
187189

188-
# Fallback to uname for macOS/Darwin
190+
# Fallback to uname for macOS/Darwin/BSD systems
189191
if distribution == 'unknown' and uname_info:
190192
kernel_name = uname_info.get('kernel_name', '').lower()
191193
if kernel_name == 'darwin':
@@ -194,6 +196,9 @@ def _parse_os_info(self, uname_info: Dict[str, str],
194196
elif kernel_name == 'freebsd':
195197
distribution = 'freebsd'
196198
version = uname_info.get('kernel_release', 'unknown')
199+
elif kernel_name == 'openbsd':
200+
distribution = 'openbsd'
201+
version = uname_info.get('kernel_release', 'unknown')
197202

198203
# Determine OS family from distribution
199204
for pattern, (family, _) in self.OS_PATTERNS.items():
@@ -204,6 +209,10 @@ def _parse_os_info(self, uname_info: Dict[str, str],
204209
# Clean up distribution name
205210
distribution = self._normalize_distribution_name(distribution)
206211

212+
# Set version for rolling release distributions
213+
if distribution in ['arch', 'manjaro'] and version == 'unknown':
214+
version = 'rolling'
215+
207216
return os_family, distribution, version
208217

209218
def _normalize_distribution_name(self, distribution: str) -> str:
@@ -233,6 +242,8 @@ def _normalize_distribution_name(self, distribution: str) -> str:
233242
return 'alpine'
234243
elif 'freebsd' in distribution:
235244
return 'freebsd'
245+
elif 'openbsd' in distribution:
246+
return 'openbsd'
236247
elif 'darwin' in distribution or 'macos' in distribution:
237248
return 'macos'
238249

miniupdate/package_managers.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,135 @@ def apply_updates(self) -> tuple[bool, Optional[str]]:
716716
return False, error_msg
717717

718718

719+
class PkgAddPackageManager(PackageManager):
720+
"""Package manager for OpenBSD pkg_add."""
721+
722+
def refresh_cache(self) -> bool:
723+
"""Refresh pkg_add cache (not really needed for OpenBSD, but check connection)."""
724+
# OpenBSD pkg_add doesn't have a cache refresh like other systems
725+
# But we can verify the package database is accessible
726+
try:
727+
exit_code, stdout, stderr = self.connection.execute_command(
728+
'pkg_info -Q ""', timeout=30
729+
)
730+
# Any exit code is fine here, we're just checking connection
731+
return True
732+
except Exception as e:
733+
logger.error(f"Failed to check pkg_add availability: {e}")
734+
return False
735+
736+
def check_updates(self) -> List[PackageUpdate]:
737+
"""Check for pkg_add package updates."""
738+
updates = []
739+
740+
try:
741+
# Use pkg_add -u with -n (dry-run) to see what would be updated
742+
exit_code, stdout, stderr = self.connection.execute_command(
743+
'doas pkg_add -u -n', timeout=120
744+
)
745+
746+
if exit_code != 0:
747+
logger.warning(f"pkg_add check command failed: {stderr}")
748+
return updates
749+
750+
updates = self._parse_pkg_add_output(stdout)
751+
752+
except Exception as e:
753+
logger.error(f"Failed to check pkg_add updates: {e}")
754+
755+
return updates
756+
757+
def _parse_pkg_add_output(self, output: str) -> List[PackageUpdate]:
758+
"""Parse pkg_add -u -n output."""
759+
updates = []
760+
761+
for line in output.strip().split('\n'):
762+
line = line.strip()
763+
if not line:
764+
continue
765+
766+
# Format examples:
767+
# "Update to package-1.2.3"
768+
# "quirks-7.14 -> quirks-7.15"
769+
if '->' in line:
770+
# Format: package-old-version -> package-new-version
771+
parts = line.split('->')
772+
if len(parts) >= 2:
773+
left_part = parts[0].strip()
774+
right_part = parts[1].strip()
775+
776+
# Extract package name and versions
777+
# OpenBSD format: packagename-version
778+
if '-' in left_part:
779+
last_dash = left_part.rfind('-')
780+
package_name = left_part[:last_dash]
781+
current_version = left_part[last_dash + 1:]
782+
else:
783+
package_name = left_part
784+
current_version = "unknown"
785+
786+
if '-' in right_part:
787+
last_dash = right_part.rfind('-')
788+
available_version = right_part[last_dash + 1:]
789+
else:
790+
available_version = right_part
791+
792+
update = PackageUpdate(
793+
name=package_name,
794+
current_version=current_version,
795+
available_version=available_version,
796+
repository="OpenBSD"
797+
)
798+
updates.append(update)
799+
elif 'update to' in line.lower():
800+
# Format: "Update to package-version"
801+
match = re.search(r'update to ([^\s]+)', line, re.IGNORECASE)
802+
if match:
803+
package_with_version = match.group(1)
804+
if '-' in package_with_version:
805+
last_dash = package_with_version.rfind('-')
806+
package_name = package_with_version[:last_dash]
807+
available_version = package_with_version[last_dash + 1:]
808+
else:
809+
package_name = package_with_version
810+
available_version = "unknown"
811+
812+
update = PackageUpdate(
813+
name=package_name,
814+
current_version="installed",
815+
available_version=available_version,
816+
repository="OpenBSD"
817+
)
818+
updates.append(update)
819+
820+
return updates
821+
822+
def apply_updates(self) -> tuple[bool, Optional[str]]:
823+
"""Apply all available pkg_add updates."""
824+
try:
825+
# OpenBSD uses doas instead of sudo by default
826+
# Apply updates with -u (update) flag
827+
exit_code, stdout, stderr = self.connection.execute_command(
828+
'doas pkg_add -u',
829+
timeout=1800 # 30 minutes for updates
830+
)
831+
832+
if exit_code == 0:
833+
logger.info("Successfully applied pkg_add updates")
834+
return True, None
835+
else:
836+
error_output = f"pkg_add update failed with exit code {exit_code}\n"
837+
error_output += f"STDOUT:\n{stdout}\n" if stdout.strip() else ""
838+
error_output += f"STDERR:\n{stderr}\n" if stderr.strip() else ""
839+
logger.error(f"Failed to apply pkg_add updates: {stderr}")
840+
return False, error_output
841+
842+
except Exception as e:
843+
error_msg = f"Error applying pkg_add updates: {e}"
844+
logger.error(error_msg)
845+
return False, error_msg
846+
847+
719848
def get_package_manager(connection: SSHConnection, os_info: OSInfo) -> Optional[PackageManager]:
720849
"""Get appropriate package manager instance for the OS."""
721850
manager_map = {
@@ -725,6 +854,7 @@ def get_package_manager(connection: SSHConnection, os_info: OSInfo) -> Optional[
725854
'zypper': ZypperPackageManager,
726855
'pacman': PackmanPackageManager,
727856
'pkg': PkgPackageManager,
857+
'pkg_add': PkgAddPackageManager,
728858
}
729859

730860
pm_class = manager_map.get(os_info.package_manager)

0 commit comments

Comments
 (0)