From 1a1b4ece614fb5da776ccd750e60b975c9977065 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 6 Mar 2025 12:07:55 +0100 Subject: [PATCH] implemented link instead of bash wrapper --- main.py | 22 +++++++--- pkgmgr/create_executable.py | 73 -------------------------------- pkgmgr/create_ink.py | 50 ++++++++++++++++++++++ pkgmgr/deinstall_repos.py | 16 ++++--- pkgmgr/install_repos.py | 46 ++++++++++++++++++-- pkgmgr/pull_with_verification.py | 47 ++++++++++++++++++++ pkgmgr/update_repos.py | 16 +++++-- 7 files changed, 176 insertions(+), 94 deletions(-) delete mode 100644 pkgmgr/create_executable.py create mode 100644 pkgmgr/create_ink.py create mode 100644 pkgmgr/pull_with_verification.py diff --git a/main.py b/main.py index 128001f..6156062 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ BIN_DIR = os.path.expanduser("~/.local/bin") from pkgmgr.clone_repos import clone_repos from pkgmgr.config_init import config_init -from pkgmgr.create_executable import create_executable +from pkgmgr.create_ink import create_ink from pkgmgr.deinstall_repos import deinstall_repos from pkgmgr.delete_repos import delete_repos from pkgmgr.exec_git_command import exec_git_command @@ -72,20 +72,25 @@ For detailed help on each command, use: subparsers = parser.add_subparsers(dest="command", help="Subcommands") def add_identifier_arguments(subparser): subparser.add_argument("identifiers", nargs="*", help="Identifier(s) for repositories") - subparser.add_argument("--all", action="store_true", default=False, help="Apply to all repositories in the config") + subparser.add_argument( + "--all", + action="store_true", + default=False, + help="Apply the subcommand to all repositories in the config. Some commands ask for confirmation. If you want to give this confirmation for all repositories, pipe 'yes'. E.g: yes | pkgmgr {subcommand} --all" + ) subparser.add_argument("--preview", action="store_true", help="Preview changes without executing commands") subparser.add_argument("--list", action="store_true", help="List affected repositories (with preview or status)") subparser.add_argument("-a", "--args", nargs=argparse.REMAINDER, dest="extra_args", help="Additional parameters to be forwarded e.g. to the git command",default=[]) - install_parser = subparsers.add_parser("install", help="Install repository/repositories") + install_parser = subparsers.add_parser("install", help="Setup repository/repositories alias links to executables") add_identifier_arguments(install_parser) install_parser.add_argument("-q", "--quiet", action="store_true", help="Suppress warnings and info messages") install_parser.add_argument("--no-verification", default=False, action="store_true", help="Disable verification of repository commit") - deinstall_parser = subparsers.add_parser("deinstall", help="Deinstall repository/repositories") + deinstall_parser = subparsers.add_parser("deinstall", help="Remove alias links to repository/repositories") add_identifier_arguments(deinstall_parser) - delete_parser = subparsers.add_parser("delete", help="Delete repository directory for repository/repositories") + delete_parser = subparsers.add_parser("delete", help="Delete repository/repositories alias links to executables") add_identifier_arguments(delete_parser) update_parser = subparsers.add_parser("update", help="Update (pull + install) repository/repositories") @@ -143,11 +148,14 @@ For detailed help on each command, use: selected = get_selected_repos(args.all,all_repos_list,args.identifiers) install_repos(selected,repositories_base_dir, BIN_DIR, all_repos_list, args.no_verification, preview=args.preview, quiet=args.quiet) elif args.command in GIT_DEFAULT_COMMANDS: - selected = get_selected_repos(args.all,all_repos_list,args.identifiers) + selected = get_selected_repos(args.all, all_repos_list, args.identifiers) if args.command == "clone": clone_repos(selected, repositories_base_dir, all_repos_list, args.preview) + elif args.command == "pull": + from pkgmgr.pull_with_verification import pull_with_verification + pull_with_verification(selected, repositories_base_dir, all_repos_list, args.extra_args, no_verification=args.no_verification, preview=args.preview) else: - exec_git_command(selected, repositories_base_dir, all_repos_list, args.command, args.extra_args, preview) + exec_git_command(selected, repositories_base_dir, all_repos_list, args.command, args.extra_args, args.preview) elif args.command == "list": list_repositories(all_repos_list, repositories_base_dir, BIN_DIR, search_filter=args.search, status_filter=args.status) elif args.command == "deinstall": diff --git a/pkgmgr/create_executable.py b/pkgmgr/create_executable.py deleted file mode 100644 index 933a2f8..0000000 --- a/pkgmgr/create_executable.py +++ /dev/null @@ -1,73 +0,0 @@ -import os - -def create_executable(repo, repositories_base_dir, bin_dir, all_repos, quiet=False, preview=False, no_verification=False): - """ - Create an executable bash wrapper for the repository. - - If 'verified' is set, the wrapper will checkout that commit and warn (unless quiet is True). - If no verified commit is set, a warning is printed unless quiet is True. - If an 'alias' field is provided, a symlink is created in bin_dir with that alias. - """ - repo_identifier = get_repo_identifier(repo, all_repos) - repo_dir = get_repo_dir(repositories_base_dir,repo) - command = repo.get("command") - if not command: - main_sh = os.path.join(repo_dir, "main.sh") - main_py = os.path.join(repo_dir, "main.py") - if os.path.exists(main_sh): - command = "bash main.sh" - elif os.path.exists(main_py): - command = "python3 main.py" - else: - if not quiet: - print(f"No command defined and no main.sh/main.py found in {repo_dir}. Skipping alias creation.") - return - - ORANGE = r"\033[38;5;208m" - RESET = r"\033[0m" - - if no_verification: - preamble = "" - else: - if verified := repo.get("verified"): - if not quiet: - preamble = f"""\ -git checkout {verified} || echo -e "{ORANGE}Warning: Failed to checkout commit {verified}.{RESET}" -CURRENT=$(git rev-parse HEAD) -if [ "$CURRENT" != "{verified}" ]; then - echo -e "{ORANGE}Warning: Current commit ($CURRENT) does not match verified commit ({verified}).{RESET}" -fi -""" - else: - preamble = "" - else: - preamble = "" if quiet else f'echo -e "{ORANGE}Warning: No verified commit set for this repository.{RESET}"' - - script_content = f"""#!/bin/bash -cd "{repo_dir}" -{preamble} -{command} "$@" -""" - alias_path = os.path.join(bin_dir, repo_identifier) - if preview: - print(f"[Preview] Would create executable '{alias_path}' with content:\n{script_content}") - else: - os.makedirs(bin_dir, exist_ok=True) - with open(alias_path, "w") as f: - f.write(script_content) - os.chmod(alias_path, 0o755) - if not quiet: - print(f"Installed executable for {repo_identifier} at {alias_path}") - - alias_name = repo.get("alias") - if alias_name: - alias_link_path = os.path.join(bin_dir, alias_name) - try: - if os.path.exists(alias_link_path) or os.path.islink(alias_link_path): - os.remove(alias_link_path) - os.symlink(alias_path, alias_link_path) - if not quiet: - print(f"Created alias '{alias_name}' pointing to {repo_identifier}") - except Exception as e: - if not quiet: - print(f"Error creating alias '{alias_name}': {e}") \ No newline at end of file diff --git a/pkgmgr/create_ink.py b/pkgmgr/create_ink.py new file mode 100644 index 0000000..4690272 --- /dev/null +++ b/pkgmgr/create_ink.py @@ -0,0 +1,50 @@ +import os +from pkgmgr.get_repo_identifier import get_repo_identifier +from pkgmgr.get_repo_dir import get_repo_dir + +def create_ink(repo, repositories_base_dir, bin_dir, all_repos, quiet=False, preview=False): + """ + Creates a symbolic link for the repository's command. + + Instead of creating an executable wrapper, this function creates a symlink + that points to the command file within the repository (e.g., main.sh or main.py). + """ + repo_identifier = get_repo_identifier(repo, all_repos) + repo_dir = get_repo_dir(repositories_base_dir, repo) + command = repo.get("command") + if not command: + # Automatically detect main.sh or main.py: + main_sh = os.path.join(repo_dir, "main.sh") + main_py = os.path.join(repo_dir, "main.py") + if os.path.exists(main_sh): + command = main_sh + elif os.path.exists(main_py): + command = main_py + else: + if not quiet: + print(f"No command defined and neither main.sh nor main.py found in {repo_dir}. Skipping link creation.") + return + + link_path = os.path.join(bin_dir, repo_identifier) + if preview: + print(f"[Preview] Would create symlink '{link_path}' pointing to '{command}'.") + else: + os.makedirs(bin_dir, exist_ok=True) + if os.path.exists(link_path) or os.path.islink(link_path): + os.remove(link_path) + os.symlink(command, link_path) + if not quiet: + print(f"Symlink for {repo_identifier} created at {link_path}.") + + alias_name = repo.get("alias") + if alias_name: + alias_link_path = os.path.join(bin_dir, alias_name) + try: + if os.path.exists(alias_link_path) or os.path.islink(alias_link_path): + os.remove(alias_link_path) + os.symlink(link_path, alias_link_path) + if not quiet: + print(f"Alias '{alias_name}' has been set to point to {repo_identifier}.") + except Exception as e: + if not quiet: + print(f"Error creating alias '{alias_name}': {e}") diff --git a/pkgmgr/deinstall_repos.py b/pkgmgr/deinstall_repos.py index 78241fd..7ebeb06 100644 --- a/pkgmgr/deinstall_repos.py +++ b/pkgmgr/deinstall_repos.py @@ -1,17 +1,21 @@ import os +from pkgmgr.get_repo_identifier import get_repo_identifier +from pkgmgr.get_repo_dir import get_repo_dir def deinstall_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, preview=False): for repo in selected_repos: repo_identifier = get_repo_identifier(repo, all_repos) alias_path = os.path.join(bin_dir, repo_identifier) if os.path.exists(alias_path): - if preview: - print(f"[Preview] Would remove executable '{alias_path}'.") - else: - os.remove(alias_path) - print(f"Removed executable for {repo_identifier}.") + confirm = input(f"Are you sure you want to delete link '{alias_path}' for {repo_identifier}? [y/N]: ").strip().lower() + if confirm == "y": + if preview: + print(f"[Preview] Would remove link '{alias_path}'.") + else: + os.remove(alias_path) + print(f"Removed link for {repo_identifier}.") else: - print(f"No executable found for {repo_identifier} in {bin_dir}.") + print(f"No link found for {repo_identifier} in {bin_dir}.") teardown_cmd = repo.get("teardown") repo_dir = get_repo_dir(repositories_base_dir,repo) if teardown_cmd and os.path.exists(repo_dir): diff --git a/pkgmgr/install_repos.py b/pkgmgr/install_repos.py index c345742..5d686d8 100644 --- a/pkgmgr/install_repos.py +++ b/pkgmgr/install_repos.py @@ -1,14 +1,52 @@ import os +import subprocess +import sys +from pkgmgr.get_repo_identifier import get_repo_identifier +from pkgmgr.get_repo_dir import get_repo_dir +from pkgmgr.create_ink import create_ink +from pkgmgr.run_command import run_command -def install_repos(selected_repos, repositories_base_dir, bin_dir, all_repos:[], no_verification:bool, preview=False, quiet=False): - """Install repositories by creating executable wrappers and running setup.""" +def install_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, no_verification, preview=False, quiet=False): + """ + Install repositories by creating symbolic links (via create_ink) and running setup commands. + + This version applies hash verification: + - It retrieves the current commit hash using 'git rev-parse HEAD' and compares it to the + configured 'verified' hash. + - If the hashes do not match and no_verification is False, the user is prompted for confirmation. + - If the user does not confirm, the installation for that repository is skipped. + """ for repo in selected_repos: repo_identifier = get_repo_identifier(repo, all_repos) - repo_dir = get_repo_dir(repositories_base_dir,repo) + repo_dir = get_repo_dir(repositories_base_dir, repo) if not os.path.exists(repo_dir): print(f"Repository directory '{repo_dir}' does not exist. Clone it first.") continue - create_executable(repo, repositories_base_dir, bin_dir, all_repos, quiet=quiet, preview=preview, no_verification=no_verification) + + # Apply hash verification if a verified hash is defined. + verified_hash = repo.get("verified") + if verified_hash: + current_hash = "" + try: + result = subprocess.run("git rev-parse HEAD", cwd=repo_dir, shell=True, check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + current_hash = result.stdout.strip() + except Exception as e: + print(f"Error retrieving current commit for {repo_identifier}: {e}") + + proceed = True + if not no_verification and current_hash and current_hash != verified_hash: + print(f"Warning: For {repo_identifier}, the current commit hash ({current_hash}) does not match the verified hash ({verified_hash}).") + choice = input("Proceed with installation? (y/N): ").strip().lower() + if choice != "y": + proceed = False + if not proceed: + print(f"Skipping installation for {repo_identifier}.") + continue + + # Create the symlink using the new create_ink function. + create_ink(repo, repositories_base_dir, bin_dir, all_repos, quiet=quiet, preview=preview) + setup_cmd = repo.get("setup") if setup_cmd: run_command(setup_cmd, cwd=repo_dir, preview=preview) \ No newline at end of file diff --git a/pkgmgr/pull_with_verification.py b/pkgmgr/pull_with_verification.py new file mode 100644 index 0000000..097bf40 --- /dev/null +++ b/pkgmgr/pull_with_verification.py @@ -0,0 +1,47 @@ +import os +import subprocess +import sys +from pkgmgr.get_repo_identifier import get_repo_identifier +from pkgmgr.get_repo_dir import get_repo_dir + +def pull_with_verification(selected_repos, repositories_base_dir, all_repos, extra_args, no_verification, preview=False): + """ + Executes "git pull" for each repository with hash verification. + + For repositories with a 'verified' hash in the configuration, this function first + checks the current commit hash. If it does not match the verified hash, the user is prompted + to confirm the pull (unless --no-verification is set, in which case the pull proceeds automatically). + """ + for repo in selected_repos: + repo_identifier = get_repo_identifier(repo, all_repos) + repo_dir = get_repo_dir(repositories_base_dir, repo) + if not os.path.exists(repo_dir): + print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.") + continue + + verified_hash = repo.get("verified") + current_hash = "" + try: + result = subprocess.run("git rev-parse HEAD", cwd=repo_dir, shell=True, check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + current_hash = result.stdout.strip() + except Exception as e: + print(f"Error retrieving current commit for {repo_identifier}: {e}") + + proceed = True + if not no_verification and verified_hash and current_hash != verified_hash: + print(f"Warning: For {repo_identifier}, the current hash ({current_hash}) does not match the verified hash ({verified_hash}).") + choice = input("Proceed with 'git pull'? (y/N): ").strip().lower() + if choice != "y": + proceed = False + + if proceed: + full_cmd = f"git pull {' '.join(extra_args)}" + if preview: + print(f"[Preview] In '{repo_dir}': {full_cmd}") + else: + print(f"Running in '{repo_dir}': {full_cmd}") + result = subprocess.run(full_cmd, cwd=repo_dir, shell=True) + if result.returncode != 0: + print(f"'git pull' for {repo_identifier} failed with exit code {result.returncode}.") + sys.exit(result.returncode) diff --git a/pkgmgr/update_repos.py b/pkgmgr/update_repos.py index f3f589f..57ef16f 100644 --- a/pkgmgr/update_repos.py +++ b/pkgmgr/update_repos.py @@ -1,8 +1,16 @@ import sys +from pkgmgr.pull_with_verification import pull_with_verification +from pkgmgr.install_repos import install_repos -def update_repos(selected_repos, repositories_base_dir, bin_dir, all_repos:[], no_verification:bool, system_update=False, preview=False, quiet=False): - git_default_exec(selected_repos, repositories_base_dir, all_repos, extra_args=[],command="pull", preview=preview) - install_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, no_verification, preview=preview, quiet=quiet) +def update_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, no_verification, system_update=False, preview=False, quiet=False): + # Use pull_with_verification instead of the old git_default_exec. + pull_with_verification(selected_repos, repositories_base_dir, all_repos, extra_args=[], no_verification=no_verification, preview=preview) + + # Proceed with the installation process. + # Note: In the install process, we remove the --no-verification flag to avoid hash checks. + install_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, no_verification=no_verification, preview=preview, quiet=quiet) + if system_update: + from pkgmgr.run_command import run_command run_command("yay -Syu", preview=preview) - run_command("sudo pacman -Syyu", preview=preview) \ No newline at end of file + run_command("sudo pacman -Syyu", preview=preview)