#!/usr/bin/env python3 """ Script to automatically update the Flatpak manifest with new versions of Autopsy and Sleuth Kit. """ import argparse import re import sys import requests import hashlib from pathlib import Path from typing import Dict, Optional class ManifestUpdater: def __init__(self, manifest_path: str): self.manifest_path = Path(manifest_path) if not self.manifest_path.exists(): raise FileNotFoundError(f"Manifest file not found: {manifest_path}") def get_github_release_info(self, repo: str, tag: str) -> Dict[str, str]: """Get release information from GitHub API.""" url = f"https://api.github.com/repos/{repo}/releases/tags/{tag}" response = requests.get(url) if response.status_code != 200: raise ValueError(f"Failed to get release info for {repo}:{tag}") data = response.json() return { 'tag': data['tag_name'], 'tarball_url': data['tarball_url'], 'zipball_url': data['zipball_url'], 'published_at': data['published_at'] } def calculate_archive_sha256(self, url: str) -> str: """Download and calculate SHA256 hash of an archive.""" print(f"Downloading and calculating hash for: {url}") response = requests.get(url, stream=True) response.raise_for_status() sha256_hash = hashlib.sha256() for chunk in response.iter_content(chunk_size=8192): sha256_hash.update(chunk) return sha256_hash.hexdigest() def update_module_version(self, content: str, module_name: str, new_tag: str, repo_url: str) -> str: """Update a specific module's version in the manifest.""" # Pattern to match the module block module_pattern = rf'(- name: {re.escape(module_name)}.*?)((?=- name: |\Z))' match = re.search(module_pattern, content, re.DOTALL) if not match: raise ValueError(f"Module '{module_name}' not found in manifest") module_block = match.group(1) # Update source-tag updated_block = re.sub( r'(\s+source-tag:\s+).*', rf'\g<1>{new_tag}', module_block ) # Remove source-branch if it exists (tags take precedence) updated_block = re.sub( r'\s+source-branch:.*?\n', '', updated_block ) # Update the URL if it's different if repo_url: updated_block = re.sub( r'(\s+source:\s+).*', rf'\g<1>{repo_url}', updated_block ) # Replace the module block in the content return content.replace(module_block, updated_block) def update_app_version(self, content: str, version: str) -> str: """Update the main application version.""" # Extract version number from tag (e.g., "autopsy-4.22.1" -> "4.22.1") version_match = re.search(r'(\d+\.\d+\.\d+)', version) if version_match: version_number = version_match.group(1) else: version_number = version.replace('autopsy-', '').replace('v', '') return re.sub( r'^version:\s+.*', f'version: {version_number}', content, flags=re.MULTILINE ) def update_manifest(self, autopsy_version: Optional[str] = None, sleuthkit_version: Optional[str] = None) -> bool: """Update the manifest file with new versions.""" # Read current manifest content = self.manifest_path.read_text() original_content = content # Update Autopsy if specified if autopsy_version: print(f"Updating Autopsy to version: {autopsy_version}") content = self.update_module_version( content, 'autopsy', autopsy_version, 'https://github.com/sleuthkit/autopsy.git' ) content = self.update_app_version(content, autopsy_version) # Update Sleuth Kit if specified if sleuthkit_version: print(f"Updating Sleuth Kit to version: {sleuthkit_version}") content = self.update_module_version( content, 'sleuthkit', sleuthkit_version, 'https://github.com/sleuthkit/sleuthkit.git' ) # Write updated manifest if changes were made if content != original_content: # Create backup backup_path = self.manifest_path.with_suffix('.yml.backup') backup_path.write_text(original_content) print(f"Created backup: {backup_path}") # Write updated content self.manifest_path.write_text(content) print(f"Updated manifest: {self.manifest_path}") return True else: print("No changes needed.") return False def validate_manifest(self) -> bool: """Basic validation of the manifest file.""" content = self.manifest_path.read_text() # Check for required fields required_fields = ['app-id:', 'runtime:', 'sdk:', 'modules:'] for field in required_fields: if field not in content: print(f"Error: Required field '{field}' not found in manifest") return False # Check for module structure if '- name: autopsy' not in content: print("Error: Autopsy module not found in manifest") return False if '- name: sleuthkit' not in content: print("Error: Sleuth Kit module not found in manifest") return False print("Manifest validation passed.") return True def main(): parser = argparse.ArgumentParser( description='Update Flatpak manifest with new Autopsy/Sleuth Kit versions' ) parser.add_argument( '--manifest', required=True, help='Path to the Flatpak manifest file' ) parser.add_argument( '--autopsy-version', help='New Autopsy version tag (e.g., autopsy-4.22.1)' ) parser.add_argument( '--sleuthkit-version', help='New Sleuth Kit version tag (e.g., sleuthkit-4.14.0)' ) parser.add_argument( '--validate-only', action='store_true', help='Only validate the manifest without updating' ) args = parser.parse_args() try: updater = ManifestUpdater(args.manifest) if args.validate_only: success = updater.validate_manifest() sys.exit(0 if success else 1) if not args.autopsy_version and not args.sleuthkit_version: print("Error: At least one version must be specified") sys.exit(1) # Validate before updating if not updater.validate_manifest(): print("Error: Manifest validation failed") sys.exit(1) # Perform updates updated = updater.update_manifest( autopsy_version=args.autopsy_version, sleuthkit_version=args.sleuthkit_version ) if updated: print("Manifest updated successfully!") # Validate after updating if not updater.validate_manifest(): print("Warning: Updated manifest failed validation") sys.exit(1) except Exception as e: print(f"Error: {e}") sys.exit(1) if __name__ == '__main__': main()