implemented link instead of bash wrapper

This commit is contained in:
Kevin Veen-Birkenbach
2025-03-06 12:07:55 +01:00
parent 3ac717b223
commit 1a1b4ece61
7 changed files with 176 additions and 94 deletions

22
main.py
View File

@@ -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":

View File

@@ -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}")

50
pkgmgr/create_ink.py Normal file
View File

@@ -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}")

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
run_command("sudo pacman -Syyu", preview=preview)