From 589116d516ee44ccf498b708bfe1c5c6712956b9 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Mon, 18 May 2026 14:44:43 +0200 Subject: [PATCH] first commit --- README.md | 145 +++++++++++++++++++++++++++++++ aurinstall | 124 +++++++++++++++++++++++++++ aurremove | 59 +++++++++++++ aurupdate | 194 ++++++++++++++++++++++++++++++++++++++++++ completions/_aurtools | 58 +++++++++++++ 5 files changed, 580 insertions(+) create mode 100644 README.md create mode 100755 aurinstall create mode 100755 aurremove create mode 100755 aurupdate create mode 100755 completions/_aurtools diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0c91b2 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# aurtools + +A small set of shell scripts to manage AUR packages on Arch Linux **without** `yay`, `paru`, or any other AUR helper. + +This project follows the classic AUR workflow: clone the package repository, review the `PKGBUILD`, build with `makepkg`, and install with `pacman -U`. [web:22][web:303][web:305] + +## Included scripts + +- `aurinstall` — clone or update an AUR package repository, optionally review `PKGBUILD`, build the package, and install it with `pacman -U`. [web:22][web:305] +- `aurupdate` — iterate through local AUR package repositories, pull updates, rebuild selected packages, and reinstall them locally. [web:22][web:305] +- `aurremove` — remove installed AUR packages with `pacman -Rns` and optionally delete the local repository directory. [web:46][web:107] +- `_aurtools` — Zsh completion file for the scripts, including optional online package-name completion for `aurinstall` via the AUR RPC interface. [web:281] + +## Why use this + +This setup is intended for users who want full visibility into what gets built and installed from the AUR. Instead of relying on a full AUR helper, it keeps each package as a normal Git repository under `~/Aur`, making updates and audits easier to understand and review. [web:22][web:189] + +## Requirements + +Install the usual Arch build tools first: + +```bash +sudo pacman -S --needed base-devel git +``` + +Some optional features require additional tools: + +```bash +sudo pacman -S curl jq zsh +``` + +- `curl` and `jq` are used by the optional online completion for `aurinstall` through the AUR RPC API. [web:281] +- `zsh` is needed only for the provided completion script. +- An editor such as `nvim` is useful if `aurinstall` is run with review options like `-e`, `-E`, or `-d`. + +## Installation + +Copy the scripts somewhere in your `PATH`, for example `~/.local/bin`: + +```bash +mkdir ~/Aur +install -Dm755 aurinstall ~/.local/bin/aurinstall +install -Dm755 aurupdate ~/.local/bin/aurupdate +install -Dm755 aurremove ~/.local/bin/aurremove +``` + +Install the Zsh completion file: + +```bash +mkdir -p ~/.zsh/completions +install -Dm644 completions/_aurtools ~/.zsh/completions/_aurtools +``` + +Then ensure the completion directory is in `fpath` and initialize completion in `~/.zshrc`: + +```zsh +fpath=("$HOME/.zsh/completions" $fpath) +autoload -Uz compinit +compinit +``` + +## Repository layout + +The scripts assume that AUR repositories are stored under `~/Aur` by default: + +```text +~/Aur/ +├── package-one/ +├── package-two/ +└── package-three/ +``` + +Each package is kept as its own Git repository cloned from `https://aur.archlinux.org/.git`, which matches the documented AUR workflow. [web:22][web:189] + +## Usage + +### Install a package + +```bash +aurinstall proton-pass-bin +``` + +This clones the package if needed, builds it with `makepkg`, and installs the resulting package file with `pacman -U`. [web:22][web:305] + +### Prompt before editing build files + +```bash +aurinstall -e swayfx +``` + +### Always open `PKGBUILD` before building + +```bash +aurinstall -E swayfx +``` + +Reviewing `PKGBUILD` before building is an important AUR safety habit because `makepkg` executes build instructions defined there. [web:303][web:305] + +### Download and audit only + +```bash +aurinstall -d swayfx +``` + +This mode updates or clones the repository and lets the user inspect package files without building or installing anything. + +### Update local AUR repositories + +```bash +aurupdate +``` + +This walks through the repositories stored in `~/Aur`, pulls upstream changes, and rebuilds selected packages. + +### Remove a package + +```bash +aurremove calcure +``` + +This removes the installed package with `pacman -Rns`, which removes the package, unneeded dependencies, and package-managed config files. [web:46][web:107][web:212] + +## Zsh completion + +The included completion script supports: + +- local completion from existing repositories in `~/Aur` for `aurinstall` +- optional remote name completion for `aurinstall` through the AUR RPC interface [web:281] +- completion of installed foreign packages for `aurremove` via `pacman -Qm` [web:46] + +If completion behaves strangely after changes, rebuild the completion cache: + +```zsh +rm -f ~/.cache/zsh/zcompdump* +autoload -Uz compinit +compinit +``` + +## Security note + +AUR packages are user-contributed build scripts, not official binary packages. The safest workflow is to inspect `PKGBUILD`, sources, checksums, and build steps before installation. [web:22][web:303][web:305] + +## License + +A permissive license such as MIT or ISC fits this kind of shell-script project well. GitHub recommends adding an explicit license if the repository is meant to be reused by others. [web:317][web:322] diff --git a/aurinstall b/aurinstall new file mode 100755 index 0000000..43cb9fd --- /dev/null +++ b/aurinstall @@ -0,0 +1,124 @@ +#!/bin/bash +set -euo pipefail + +AUR_DIR="$HOME/Aur" +EDITOR_CMD="${EDITOR:-nvim}" + +ASK_EDIT=0 +FORCE_EDIT=0 +DOWNLOAD_ONLY=0 + +while getopts ":eEd" opt; do + case "$opt" in + e) ASK_EDIT=1 ;; + E) FORCE_EDIT=1 ;; + d) DOWNLOAD_ONLY=1 ;; + *) + echo "Usage: aurinstall [-e|-E] [-d] [paquet2 ...]" + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +RESET='\033[0m' +BOLD='\033[1m' +RED='\033[1;31m' +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +CYAN='\033[1;36m' + +info() { printf "${BLUE}==>${RESET} %s\n" "$1"; } +ok() { printf "${GREEN}==>${RESET} %s\n" "$1"; } +warn() { printf "${YELLOW}==>${RESET} %s\n" "$1"; } +error() { printf "${RED}==>${RESET} %s\n" "$1"; } +section() { printf "\n${CYAN}${BOLD}>>> %s${RESET}\n" "$1"; } + +if [[ $# -lt 1 ]]; then + error "Usage: aurinstall [-e|-E] [-d] [paquet2 ...]" + exit 1 +fi + +mkdir -p "$AUR_DIR" + +if [[ "$DOWNLOAD_ONLY" -eq 0 ]]; then + sudo -v || exit 1 + while true; do + sudo -n true || exit + sleep 30 + done 2>/dev/null & + SUDO_LOOP_PID=$! + trap 'kill "$SUDO_LOOP_PID" 2>/dev/null' EXIT INT TERM +fi + +for pkg in "$@" ; do + section "$pkg" + cd "$AUR_DIR" || exit 1 + + if [[ "$DOWNLOAD_ONLY" -eq 0 ]] && pacman -Q "$pkg" >/dev/null 2>&1; then + warn "$pkg est déjà installé, on passe." + continue + fi + + if [[ -d "$pkg/.git" ]]; then + warn "$pkg déjà cloné, utilisation du dossier existant" + cd "$pkg" || { error "Impossible d'entrer dans $pkg"; continue; } + + pull_output=$(git pull --ff-only 2>&1) || { + error "git pull failed pour $pkg" + cd "$AUR_DIR" || exit 1 + continue + } + printf "%s\n" "$pull_output" + else + info "Clonage de $pkg" + if ! git clone "https://aur.archlinux.org/${pkg}.git"; then + error "Impossible de cloner $pkg" + continue + fi + cd "$pkg" || { error "Impossible d'entrer dans $pkg"; continue; } + fi + + if [[ "$FORCE_EDIT" -eq 1 ]]; then + info "Ouverture de PKGBUILD et .SRCINFO dans $EDITOR_CMD" + "$EDITOR_CMD" PKGBUILD .SRCINFO + elif [[ "$ASK_EDIT" -eq 1 || "$DOWNLOAD_ONLY" -eq 1 ]]; then + printf "${YELLOW}Éditer PKGBUILD/.SRCINFO pour %s ? [y/N] ${RESET}" "$pkg" + read -r ans + if [[ "$ans" == "y" || "$ans" == "Y" ]]; then + "$EDITOR_CMD" PKGBUILD .SRCINFO + fi + fi + + if [[ "$DOWNLOAD_ONLY" -eq 1 ]]; then + ok "$pkg prêt pour audit dans $AUR_DIR/$pkg" + continue + fi + + info "Vérifie au minimum source=(), checksums, prepare()/build()/package() et validpgpkeys si présents" + rm -f ./*.pkg.tar ./*.pkg.tar.* + + if ! makepkg -sf; then + error "Build failed pour $pkg" + cd "$AUR_DIR" || exit 1 + continue + fi + + pkgfile=$(find . -maxdepth 1 -type f \( -name '*.pkg.tar' -o -name '*.pkg.tar.*' \) ! -name '*.sig' | head -n1 || true) + echo "DEBUG: pkgfile='$pkgfile'" + if [[ -z "$pkgfile" ]]; then + error "Aucun paquet généré pour $pkg" + cd "$AUR_DIR" || exit 1 + continue + fi + + info "Installation de $pkgfile" + if ! sudo pacman -U --noconfirm "$pkgfile"; then + error "Install failed pour $pkg" + cd "$AUR_DIR" || exit 1 + continue + fi + + ok "$pkg installé avec succès" +done diff --git a/aurremove b/aurremove new file mode 100755 index 0000000..6dc683f --- /dev/null +++ b/aurremove @@ -0,0 +1,59 @@ +#!/bin/bash +set -euo pipefail + +AUR_DIR="$HOME/Aur" + +RESET='\033[0m' +BOLD='\033[1m' +RED='\033[1;31m' +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +CYAN='\033[1;36m' + +info() { printf "${BLUE}==>${RESET} %s\n" "$1"; } +ok() { printf "${GREEN}==>${RESET} %s\n" "$1"; } +warn() { printf "${YELLOW}==>${RESET} %s\n" "$1"; } +error() { printf "${RED}==>${RESET} %s\n" "$1"; } +section() { printf "\n${CYAN}${BOLD}>>> %s${RESET}\n" "$1"; } + +if [[ $# -lt 1 ]]; then + error "Usage: aurremove [paquet2 ...]" + exit 1 +fi + +sudo -v || exit 1 +while true; do + sudo -n true || exit + sleep 30 +done 2>/dev/null & +SUDO_LOOP_PID=$! +trap 'kill "$SUDO_LOOP_PID" 2>/dev/null' EXIT INT TERM + +for pkg in "$@" ; do + section "$pkg" + + if ! pacman -Q "$pkg" >/dev/null 2>&1; then + warn "$pkg n'est pas installé" + continue + fi + + info "Désinstallation de $pkg" + if ! sudo pacman -Rns "$pkg"; then + error "Échec de suppression pour $pkg" + continue + fi + + ok "$pkg supprimé du système" + + if [[ -d "$AUR_DIR/$pkg" ]]; then + printf "${YELLOW}Supprimer aussi le dossier %s ? [y/N] ${RESET}" "$AUR_DIR/$pkg" + read -r ans + if [[ "$ans" == "y" || "$ans" == "Y" ]]; then + rm -rf "$AUR_DIR/$pkg" + ok "Dossier $AUR_DIR/$pkg supprimé" + else + warn "Dossier conservé : $AUR_DIR/$pkg" + fi + fi +done diff --git a/aurupdate b/aurupdate new file mode 100755 index 0000000..7264d95 --- /dev/null +++ b/aurupdate @@ -0,0 +1,194 @@ +#!/bin/bash +set -euo pipefail + +AUR_DIR="${AUR_DIR:-$HOME/Aur}" +END_UPDATE="Mis à jour avec succès ;p." +SYSTEM_NAME="$(cat /etc/hostname 2>/dev/null || echo "unknown")" + +RESET='\033[0m' +BOLD='\033[1m' +RED='\033[1;31m' +GREEN='\033[1;32m' +YELLOW='\033[1;33m' +BLUE='\033[1;34m' +CYAN='\033[1;36m' + +info() { printf "${BLUE}==>${RESET} %s\n" "$1"; } +ok() { printf "${GREEN}==>${RESET} %s\n" "$1"; } +warn() { printf "${YELLOW}==>${RESET} %s\n" "$1"; } +error() { printf "${RED}==>${RESET} %s\n" "$1"; } +section() { printf "\n${CYAN}${BOLD}>>> %s${RESET}\n" "$1"; } + +[[ -d "$AUR_DIR" ]] || { + error "Répertoire AUR introuvable : $AUR_DIR" + exit 1 +} + +sudo -v || exit 1 +while true; do + sudo -n true || exit + sleep 30 +done 2>/dev/null & +SUDO_LOOP_PID=$! +trap 'kill "$SUDO_LOOP_PID" 2>/dev/null' EXIT INT TERM + +cd "$AUR_DIR" || exit 1 + +shopt -s nullglob +repos=(*/) +shopt -u nullglob + +if [[ ${#repos[@]} -eq 0 ]]; then + warn "Aucun dépôt AUR dans $AUR_DIR" + exit 0 +fi + +declare -a outdated_repos=() + +audit_repo() { + local repo="$1" + local branch upstream local_rev remote_rev base_rev + + cd "$AUR_DIR/$repo" || return 1 + + if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + error "$repo n'est pas un dépôt git valide" + return 1 + fi + + branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true) + [[ -n "$branch" ]] || { + warn "$repo : HEAD détachée, ignoré" + return 1 + } + + if ! git remote update --prune >/dev/null 2>&1; then + error "Impossible de contacter le remote pour $repo" + return 1 + fi + + upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true) + [[ -n "$upstream" ]] || upstream="origin/$branch" + + local_rev=$(git rev-parse HEAD 2>/dev/null || true) + remote_rev=$(git rev-parse "$upstream" 2>/dev/null || true) + base_rev=$(git merge-base HEAD "$upstream" 2>/dev/null || true) + + if [[ -z "$remote_rev" || -z "$local_rev" || -z "$base_rev" ]]; then + error "Impossible de comparer $repo avec $upstream" + return 1 + fi + + if [[ "$local_rev" == "$remote_rev" ]]; then + return 1 + fi + + if [[ "$local_rev" != "$base_rev" ]]; then + warn "$repo a des commits locaux non poussés, ignoré" + return 1 + fi + + outdated_repos+=("$repo") + return 0 +} + +ask_update() { + local repo="$1" + while true; do + printf "${YELLOW}Mettre à jour %s ? [y/N/q] ${RESET}" "$repo" + read -r ans + case "$ans" in + y|Y) return 0 ;; + n|N|"") return 1 ;; + q|Q) return 2 ;; + *) warn "Réponse invalide, utilise y, n ou q" ;; + esac + done +} + +info "Scan des dépôts AUR..." +for d in "${repos[@]}"; do + repo="${d%/}" + audit_repo "$repo" || true +done + +if [[ ${#outdated_repos[@]} -eq 0 ]]; then + ok "Aucune mise à jour AUR détectée" + exit 0 +fi + +section "Mises à jour détectées" +for repo in "${outdated_repos[@]}"; do + cd "$AUR_DIR/$repo" || continue + upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "origin/HEAD") + printf "${YELLOW}- %s${RESET}\n" "$repo" + git --no-pager log --oneline --decorate HEAD.."$upstream" | sed 's/^/ /' +done + +declare -a selected_repos=() + +section "Sélection" +for repo in "${outdated_repos[@]}"; do + if ask_update "$repo"; then + selected_repos+=("$repo") + else + rc=$? + if [[ $rc -eq 2 ]]; then + warn "Sélection interrompue" + break + fi + fi +done + +if [[ ${#selected_repos[@]} -eq 0 ]]; then + warn "Aucun paquet sélectionné" + exit 0 +fi + +section "Paquets sélectionnés" +for repo in "${selected_repos[@]}"; do + printf "${GREEN}- %s${RESET}\n" "$repo" +done + +for repo in "${selected_repos[@]}"; do + cd "$AUR_DIR/$repo" || continue + section "$repo" + + info "Récupération des changements" + if ! git pull --ff-only; then + error "git pull failed for $repo" + continue + fi + + info "Vérifie au minimum PKGBUILD, sources, checksums et fonctions de build" + rm -f ./*.pkg.tar.* + + if ! makepkg -sf; then + error "Build failed for $repo" + continue + fi + + pkgfile=$(find . -maxdepth 1 -type f -name '*.pkg.tar.*' ! -name '*.sig' | head -n1 || true) + if [[ -z "$pkgfile" ]]; then + error "Aucun paquet trouvé pour $repo" + continue + fi + + info "Installation de $pkgfile" + if ! sudo pacman -U --noconfirm "$pkgfile"; then + error "Install failed for $repo" + continue + fi + + ok "$repo installé avec succès" +done + +printf "\n" +if command -v figlet >/dev/null 2>&1; then + figlet -f slant "$SYSTEM_NAME" | while IFS= read -r line; do + printf "${GREEN}%s${RESET}\n" "$line" + done + printf "${GREEN}%s${RESET}\n" "$END_UPDATE" +else + printf "${GREEN}%s : %s${RESET}\n" "$SYSTEM_NAME" "$END_UPDATE" +fi diff --git a/completions/_aurtools b/completions/_aurtools new file mode 100755 index 0000000..345505c --- /dev/null +++ b/completions/_aurtools @@ -0,0 +1,58 @@ +#compdef aurinstall aurupdate aurremove + +_aurlist_dirs() { + local -a pkgs + local AUR_DIR=${AUR_DIR:-$HOME/Aur} + + pkgs=() + if [[ -d $AUR_DIR ]]; then + local d + for d in "$AUR_DIR"/*; do + [[ -d $d ]] || continue + pkgs+=("${d:t}") + done + fi + + compadd "$pkgs[@]" +} + +_aurlist_installed() { + local -a pkgs + pkgs=(${(f)"$(pacman -Qm 2>/dev/null | awk '{print $1}')"}) + compadd "$pkgs[@]" +} + +_aursearch_online() { + local cur + cur="${words[CURRENT]}" + + [[ -z "$cur" || ${#cur} -lt 2 ]] && return 0 + + command -v curl >/dev/null 2>&1 || return 0 + command -v jq >/dev/null 2>&1 || return 0 + + local -a remote_pkgs + remote_pkgs=(${(f)"$( + curl -fsSL "https://aur.archlinux.org/rpc/v5/search/${cur}?by=name" 2>/dev/null \ + | jq -r '.results[].Name' 2>/dev/null + )"}) + + (( ${#remote_pkgs[@]} )) && compadd "$remote_pkgs[@]" +} + +_aurinstall() { + _aurlist_dirs + _aursearch_online +} + +_aurupdate() { + _message 'aucun argument' +} + +_aurremove() { + _aurlist_installed +} + +compdef _aurinstall aurinstall +compdef _aurupdate aurupdate +compdef _aurremove aurremove