diff --git a/README.md b/README.md index 0d12bbb..a8cba04 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ miaur -Qua # List available AUR upgrades miaur -Sua # Upgrade AUR packages only miaur -G # Download a PKGBUILD miaur --noconfirm -Syu # Upgrade without prompts +miaur self-update # Update miaur from Git ``` Run `miaur -h` for more options. @@ -45,6 +46,8 @@ Run `miaur -h` for more options. The optional configuration file is `~/.config/miaur/miaur.conf`. See `config/miaur.conf.example`. +Self-update reads `RepoUrl` / `Branch` (or `MIAUR_REPO` / `MIAUR_BRANCH`) when set. + ## License MIT diff --git a/completions/miaur.bash b/completions/miaur.bash index d42a299..c400740 100644 --- a/completions/miaur.bash +++ b/completions/miaur.bash @@ -5,8 +5,8 @@ _miaur() { cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD - 1]} - local ops='-S -Syu -Sua -Ss -Si -Sc -Q -Qua -Qm -R -Rns -G -Gp -B -Bi -h -V' - local longs='--aur --repo --skipreview --cleanafter --removemake --noremovemake --noremovermake --bottomup --topdown --clonedir --mflags --sudoloop --nosudoloop --noconfirm --help --version' + local ops='-S -Syu -Sua -Ss -Si -Sc -Q -Qua -Qm -R -Rns -G -Gp -B -Bi -h -V self-update' + local longs='--aur --repo --skipreview --cleanafter --removemake --noremovemake --noremovermake --bottomup --topdown --clonedir --mflags --sudoloop --nosudoloop --noconfirm --self-update --repo-url --branch --help --version' case "$prev" in -S | -Si | -Ss | -G | -Gp | -R | -Rns | -Qi) @@ -17,12 +17,16 @@ _miaur() { COMPREPLY=($(compgen -d -- "$cur")) return ;; + self-update | --self-update) + COMPREPLY=($(compgen -W '--noconfirm --repo-url --branch' -- "$cur")) + return + ;; esac if [[ $cur == -* ]]; then COMPREPLY=($(compgen -W "$ops $longs" -- "$cur")) else - COMPREPLY=($(compgen -W "$(pacman -Ssq "^${cur}" 2>/dev/null | head -40)" -- "$cur")) + COMPREPLY=($(compgen -W "self-update $(pacman -Ssq "^${cur}" 2>/dev/null | head -40)" -- "$cur")) fi } complete -F _miaur miaur diff --git a/completions/miaur.zsh b/completions/miaur.zsh index 9f18a39..10da25c 100644 --- a/completions/miaur.zsh +++ b/completions/miaur.zsh @@ -14,11 +14,13 @@ _miaur() { '-Bi:build and install' '-Sc:clean caches' '-Rns:remove a package' + 'self-update:update miaur from Git' '--aur:use the AUR only' '--repo:use repositories only' '--skipreview:skip PKGBUILD review' '--noremovemake:keep make dependencies' '--noconfirm:bypass confirmation prompts' + '--self-update:update miaur from Git' '--bottomup:show AUR results first' '--help:show help' ) diff --git a/config/miaur.conf.example b/config/miaur.conf.example index e6eaa08..2288dee 100644 --- a/config/miaur.conf.example +++ b/config/miaur.conf.example @@ -17,3 +17,5 @@ Noconfirm = false # SearchBy = name-desc # Editor = nvim # Bat = bat +# RepoUrl = https://github.com/WHC2006/miaur.git +# Branch = main diff --git a/lib/cli.sh b/lib/cli.sh index 00b758b..d59a2d4 100644 --- a/lib/cli.sh +++ b/lib/cli.sh @@ -36,6 +36,7 @@ Pacman operations extended with AUR support: Additional operations: miaur {-G --getpkgbuild} [packages] miaur {-B --build} [directories] + miaur self-update Update miaur from Git With no arguments, miaur runs 'miaur -Syu'. @@ -49,6 +50,7 @@ Common commands: miaur -G Download a PKGBUILD miaur -Gp Print a PKGBUILD miaur -Bi . Build and install a local PKGBUILD + miaur self-update Update miaur itself Options: -a, --aur Assume targets are from the AUR @@ -63,6 +65,7 @@ Options: --sudoloop Keep sudo credentials alive (default) --nosudoloop Disable the sudo keep-alive loop --noconfirm Bypass any confirmation prompts + --self-update Same as: miaur self-update EOF } @@ -76,11 +79,14 @@ strip_miaur_opts() { case "$arg" in --aur | --repo | --skipreview | --review | --cleanafter | --nocleanafter | \ --removemake | --noremovemake | --noremovermake | --bottomup | --topdown | \ - --sudoloop | --nosudoloop | --noconfirm) ;; + --sudoloop | --nosudoloop | --noconfirm | --self-update) ;; --clonedir | --mflags | --mode) i=$((i + 1)) ;; - --clonedir=* | --mflags=* | --mode=*) ;; + --clonedir=* | --mflags=* | --mode=* | --repo-url=* | --branch=*) ;; + --repo-url | --branch) + i=$((i + 1)) + ;; *) printf '%s\n' "$arg" ;; @@ -97,6 +103,40 @@ parse_args() { return 0 fi + # Subcommand: self-update [--noconfirm] [--repo-url URL] [--branch NAME] + if [[ $1 == self-update || $1 == --self-update ]]; then + OP=selfupdate + local -a raw=("$@") + local i=1 + while ((i < ${#raw[@]})); do + local arg=${raw[$i]} + case "$arg" in + --noconfirm) MIAUR_NOCONFIRM=1 ;; + --repo-url) + i=$((i + 1)) + MIAUR_REPO=${raw[$i]:-} + [[ -n $MIAUR_REPO ]] || die "--repo-url requires a URL" + ;; + --repo-url=*) MIAUR_REPO=${arg#*=} ;; + --branch) + i=$((i + 1)) + MIAUR_BRANCH=${raw[$i]:-} + [[ -n $MIAUR_BRANCH ]] || die "--branch requires a name" + ;; + --branch=*) MIAUR_BRANCH=${arg#*=} ;; + -h | --help) + print_help + exit 0 + ;; + *) + die "unknown self-update option: $arg" + ;; + esac + i=$((i + 1)) + done + return 0 + fi + # No leading dash → interactive search if [[ $1 != -* ]]; then OP=interactive @@ -117,6 +157,9 @@ parse_args() { printf 'miaur v%s\n' "$MIAUR_VERSION" exit 0 ;; + --self-update) + OP=selfupdate + ;; --aur) OP_AUR_ONLY=1 MIAUR_MODE=aur @@ -153,6 +196,18 @@ parse_args() { MIAUR_MODE=${raw[$i]:-} ;; --mode=*) MIAUR_MODE=${arg#*=} ;; + --repo-url) + i=$((i + 1)) + MIAUR_REPO=${raw[$i]:-} + [[ -n $MIAUR_REPO ]] || die "--repo-url requires a URL" + ;; + --repo-url=*) MIAUR_REPO=${arg#*=} ;; + --branch) + i=$((i + 1)) + MIAUR_BRANCH=${raw[$i]:-} + [[ -n $MIAUR_BRANCH ]] || die "--branch requires a name" + ;; + --branch=*) MIAUR_BRANCH=${arg#*=} ;; --sync) OP=S ;; --query) OP=Q ;; --remove) diff --git a/lib/common.sh b/lib/common.sh index 90a83c2..24b6328 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -2,7 +2,7 @@ # shellcheck disable=SC2034 # Common utilities for miaur -MIAUR_VERSION="0.1.0" +MIAUR_VERSION="0.2.0" # Colors (respect pacman Color / NO_COLOR / non-tty) _miaur_color_enabled() { diff --git a/lib/config.sh b/lib/config.sh index 4044455..114437f 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -22,6 +22,8 @@ MIAUR_MAKEPKG="${MIAUR_MAKEPKG:-makepkg}" MIAUR_PACMAN="${MIAUR_PACMAN:-pacman}" MIAUR_BAT="${MIAUR_BAT:-bat}" MIAUR_EDITOR="${VISUAL:-${EDITOR:-nano}}" +MIAUR_REPO="${MIAUR_REPO:-https://github.com/WHC2006/miaur.git}" +MIAUR_BRANCH="${MIAUR_BRANCH:-main}" _bool_val() { case "${1,,}" in @@ -73,6 +75,8 @@ miaur_load_config() { Pacman) MIAUR_PACMAN=$val ;; Bat) MIAUR_BAT=$val ;; Editor) MIAUR_EDITOR=$val ;; + RepoUrl | MIAUR_REPO) MIAUR_REPO=$val ;; + Branch | MIAUR_BRANCH) MIAUR_BRANCH=$val ;; esac done <"$conf" } diff --git a/lib/dispatch.sh b/lib/dispatch.sh index 4c976fc..17c77c2 100644 --- a/lib/dispatch.sh +++ b/lib/dispatch.sh @@ -199,11 +199,15 @@ cmd_clean() { dispatch() { # Passthrough for R/D/F/T/U is handled in main() via strip_miaur_opts. # Keep a reconstruction fallback only if somehow reached without orig argv. - if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive ]]; then + if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive && $OP != selfupdate ]]; then die "internal error: passthrough should be handled in main" fi case "$OP" in + selfupdate) + need_cmd git + cmd_self_update + ;; interactive) need_cmd curl jq cmd_interactive "${TARGETS[@]}" diff --git a/lib/self_update.sh b/lib/self_update.sh new file mode 100644 index 0000000..49c110e --- /dev/null +++ b/lib/self_update.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +# Self-update miaur from its Git repository + +# Detect install prefix from the running binary location. +# Prints: user|/path/prefix or system|/path/prefix +miaur_detect_prefix() { + case "$MIAUR_HOME" in + "${HOME}/.local/share/miaur") + printf 'user|%s\n' "${HOME}/.local" + ;; + /usr/local/share/miaur) + printf 'system|%s\n' /usr/local + ;; + *) + # Dev checkout or custom layout — default to a user install + printf 'user|%s\n' "${HOME}/.local" + ;; + esac +} + +# Read MIAUR_VERSION=... from a common.sh path +_miaur_read_version_file() { + local file=$1 + [[ -f $file ]] || return 1 + local line + line=$(grep -E '^MIAUR_VERSION=' "$file" | head -n1) || return 1 + line=${line#MIAUR_VERSION=} + line=${line#\"} + line=${line%\"} + line=${line#\'} + line=${line%\'} + printf '%s\n' "$line" +} + +# Install tree from SRC into PREFIX (user|system via $1 mode) +_miaur_install_tree() { + local mode=$1 + local prefix=$2 + local src=$3 + local share="${prefix}/share/miaur" + local binlink="${prefix}/bin/miaur" + + [[ -f $src/miaur && -d $src/lib ]] || die "incomplete source tree: $src" + + _run() { + if [[ $mode == system ]]; then + as_root "$@" + else + "$@" + fi + } + + msg "Installing to ${share}" + _run mkdir -p "$share" "${prefix}/bin" + _run rm -rf "$share" + _run mkdir -p "$share" + _run cp -a "$src/miaur" "$src/lib" "$share/" + if [[ -d $src/completions ]]; then + _run cp -a "$src/completions" "$share/" + fi + if [[ -d $src/config ]]; then + _run cp -a "$src/config" "$share/" + fi + _run chmod 755 "$share/miaur" + _run ln -sfn "$share/miaur" "$binlink" + + if [[ -d $src/completions ]]; then + if [[ $mode == user ]]; then + mkdir -p "${HOME}/.local/share/bash-completion/completions" + cp -f "$src/completions/miaur.bash" \ + "${HOME}/.local/share/bash-completion/completions/miaur" 2>/dev/null || true + else + if [[ -d /usr/share/bash-completion/completions ]]; then + as_root cp -f "$src/completions/miaur.bash" \ + /usr/share/bash-completion/completions/miaur 2>/dev/null || true + fi + fi + fi + + printf '%s\n' "$binlink" +} + +# Fetch latest sources into a temp dir; prints path to clone +_miaur_fetch_latest() { + local repo=$1 + local branch=$2 + local tmp + tmp=$(mktemp -d) + # Caller cleans up via SELF_UPDATE_TMP + SELF_UPDATE_TMP=$tmp + msg2 "Cloning ${repo} (${branch})..." + ${MIAUR_GIT} clone --depth 1 --branch "$branch" "$repo" "$tmp/miaur" \ + || die "failed to clone ${repo} (branch ${branch}); set RepoUrl / MIAUR_REPO if you forked" + [[ -f $tmp/miaur/miaur && -d $tmp/miaur/lib ]] || die "cloned tree looks incomplete" + printf '%s\n' "$tmp/miaur" +} + +cmd_self_update() { + need_cmd git + + local repo=${MIAUR_REPO:-https://github.com/WHC2006/miaur.git} + local branch=${MIAUR_BRANCH:-main} + local info mode prefix + info=$(miaur_detect_prefix) + mode=${info%%|*} + prefix=${info#*|} + + local old_ver=${MIAUR_VERSION} + msg "Current version: v${old_ver}" + msg2 "Install prefix: ${prefix} (${mode})" + msg2 "Source: ${repo} @ ${branch}" + + if [[ $MIAUR_HOME != "${prefix}/share/miaur" ]]; then + warn "Running from ${MIAUR_HOME}; update will install into ${prefix}/share/miaur" + fi + + confirm_ask "Update miaur from Git?" || { + warn "Cancelled by user" + return 1 + } + + SELF_UPDATE_TMP= + # shellcheck disable=SC2064 + trap '[[ -n ${SELF_UPDATE_TMP:-} ]] && rm -rf "$SELF_UPDATE_TMP"; sudoloop_stop' RETURN + + local src new_ver binlink + src=$(_miaur_fetch_latest "$repo" "$branch") + new_ver=$(_miaur_read_version_file "$src/lib/common.sh") || new_ver=unknown + + if [[ $new_ver == "$old_ver" ]]; then + msg "Remote reports the same version (v${new_ver}); reinstalling anyway" + else + msg "Updating: v${old_ver} -> v${new_ver}" + fi + + if [[ $mode == system ]]; then + sudoloop_start + fi + + binlink=$(_miaur_install_tree "$mode" "$prefix" "$src") + + msg "Done: ${binlink}" + if [[ -x $binlink ]]; then + "$binlink" -V + fi +} diff --git a/miaur b/miaur index 0c98a63..4f75ae5 100755 --- a/miaur +++ b/miaur @@ -21,6 +21,8 @@ source "$MIAUR_HOME/lib/deps.sh" source "$MIAUR_HOME/lib/build.sh" # shellcheck source=lib/upgrade.sh source "$MIAUR_HOME/lib/upgrade.sh" +# shellcheck source=lib/self_update.sh +source "$MIAUR_HOME/lib/self_update.sh" # shellcheck source=lib/cli.sh source "$MIAUR_HOME/lib/cli.sh" # shellcheck source=lib/dispatch.sh @@ -34,7 +36,7 @@ main() { # Pure pacman passthrough: forward original argv minus miaur-only options # so long options like --ignore survive (unlike short-cluster reconstruction). - if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive ]]; then + if [[ $DO_PASSTHRU -eq 1 && $OP != S && $OP != Q && $OP != G && $OP != B && $OP != interactive && $OP != selfupdate ]]; then local -a filtered=() mapfile -t filtered < <(strip_miaur_opts "${orig[@]}") [[ ${#filtered[@]} -gt 0 ]] || die "nothing to pass through to pacman" diff --git a/tests/run.sh b/tests/run.sh index e569056..64f37d7 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -121,4 +121,67 @@ INNER pass "resolve_src survives process substitution" } +# --- self-update CLI --- +# shellcheck source=lib/self_update.sh +source "$ROOT/lib/self_update.sh" + +{ + OP= + MIAUR_NOCONFIRM=0 + MIAUR_REPO=https://example.invalid/miaur.git + MIAUR_BRANCH=main + TARGETS=() + PACMAN_EXTRA=() + parse_args self-update --noconfirm --repo-url https://example.com/fork.git --branch dev + [[ $OP == selfupdate ]] || fail "OP selfupdate" + [[ $MIAUR_NOCONFIRM -eq 1 ]] || fail "noconfirm for self-update" + [[ $MIAUR_REPO == https://example.com/fork.git ]] || fail "repo url: $MIAUR_REPO" + [[ $MIAUR_BRANCH == dev ]] || fail "branch: $MIAUR_BRANCH" + pass "parse_args self-update options" +} + +{ + OP= + parse_args --self-update + [[ $OP == selfupdate ]] || fail "OP from --self-update" + pass "parse_args --self-update" +} + +{ + HOME=/tmp/miaur-home-test + MIAUR_HOME=/tmp/miaur-home-test/.local/share/miaur + info=$(miaur_detect_prefix) + [[ $info == "user|/tmp/miaur-home-test/.local" ]] || fail "user prefix detect: $info" + MIAUR_HOME=/usr/local/share/miaur + info=$(miaur_detect_prefix) + [[ $info == "system|/usr/local" ]] || fail "system prefix detect: $info" + pass "miaur_detect_prefix" +} + +{ + ver=$(_miaur_read_version_file "$ROOT/lib/common.sh") + [[ $ver == "$MIAUR_VERSION" ]] || fail "version read: $ver vs $MIAUR_VERSION" + pass "_miaur_read_version_file" +} + +# Mocked self-update install into a temp HOME +{ + fake_home=$(mktemp -d) + fake_src=$(mktemp -d) + mkdir -p "$fake_src/lib" "$fake_src/completions" "$fake_src/config" + printf '%s\n' '#!/usr/bin/env bash' 'echo miaur-fake' >"$fake_src/miaur" + chmod +x "$fake_src/miaur" + printf '%s\n' 'MIAUR_VERSION="9.9.9"' >"$fake_src/lib/common.sh" + printf '%s\n' '# fake' >"$fake_src/completions/miaur.bash" + printf '%s\n' '# fake' >"$fake_src/config/miaur.conf.example" + + HOME=$fake_home + bin=$(_miaur_install_tree user "$fake_home/.local" "$fake_src") + [[ -x $bin ]] || fail "bin missing: $bin" + [[ -f $fake_home/.local/share/miaur/lib/common.sh ]] || fail "lib not installed" + grep -q '9.9.9' "$fake_home/.local/share/miaur/lib/common.sh" || fail "version file not copied" + pass "_miaur_install_tree user mode" + rm -rf "$fake_home" "$fake_src" +} + echo "All tests passed."