#!/usr/bin/env python3 # fempkg - a simple package manager # Copyright (C) 2026 Gabriel Di Martino # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys, os from db import register_package, load_db, save_db from build import build_package, fetch_recipe, MANIFEST_CACHE_DIR from utils import fetch_all RECIPE_CACHE_DIR = "/var/lib/fempkg/repo" def list_installed(): db = load_db() for pkg, ver in db["installed"].items(): print(f"{pkg}-{ver}") def update_all(): fetch_all() print("Recipe, manifest and the binpkg index cache updated.") def upgrade_packages(pkg=None): db = load_db() if pkg: recipe_file = os.path.join(RECIPE_CACHE_DIR, f"{pkg}.recipe.py") if not os.path.exists(recipe_file): print(f"Recipe for {pkg} not found. Run `fempkg update` first.") return build_package(recipe_file) else: for pkgname in db["installed"]: recipe_file = os.path.join(RECIPE_CACHE_DIR, f"{pkgname}.recipe.py") if not os.path.exists(recipe_file): print(f"Recipe for {pkgname} missing, skipping.") continue try: build_package(recipe_file) except Exception as e: print(f"Failed to upgrade {pkgname}: {e}") def uninstall_package(pkgname): manifest_path = os.path.join(MANIFEST_CACHE_DIR, f"{pkgname}.txt") if not os.path.exists(manifest_path): print(f"No manifest for {pkgname}, cannot uninstall safely. Run `fempkg update` first.") return with open(manifest_path) as f: files = [line.strip() for line in f if line.strip()] # Remove files/directories in reverse order (leaves first) for file in reversed(sorted(files)): try: if os.path.islink(file) or os.path.isfile(file): os.remove(file) print(f"Removed {file}") elif os.path.isdir(file): try: shutil.rmtree(file) print(f"Removed directory {file}") except Exception: print(f"Directory {file} not empty or protected, skipping") except Exception as e: print(f"Failed to remove {file}: {e}") # Remove manifest try: os.remove(manifest_path) except Exception as e: print(f"Failed to remove manifest {manifest_path}: {e}") # Remove from DB db = load_db() if pkgname in db["installed"]: db["installed"].pop(pkgname) save_db(db) print(f"{pkgname} uninstalled.") def help_menu(): print("Usage: fempkg [list|register|install|update|upgrade|uninstall]") print(" list - list installed packages") print(" register - manually register package") print(" install - install from recipe") print(" update - update recipe+manifest+binpkg cache") print(" upgrade - update all packages, optionally one") print(" uninstall - uninstall a package") def main(): if len(sys.argv) < 2: help_menu() return cmd = sys.argv[1] if cmd == "list": list_installed() elif cmd == "register" and len(sys.argv) == 4: register_package(sys.argv[2], sys.argv[3]) elif cmd == "install" and len(sys.argv) >= 3: for pkgname in sys.argv[2:]: print(f"Installing {pkgname} …") recipe_file = fetch_recipe(pkgname) build_package(recipe_file) print(f"{pkgname} installed") elif cmd == "update" and len(sys.argv) == 2: update_all() elif cmd == "upgrade": if len(sys.argv) == 3: upgrade_packages(sys.argv[2]) elif len(sys.argv) == 2: upgrade_packages() else: help_menu() elif cmd == "uninstall" and len(sys.argv) == 3: uninstall_package(sys.argv[2]) else: help_menu() if __name__ == "__main__": main()