#!/bin/sh # # APKG-BIN - Alice Binary Package Manager (C) 2023-2026 Emmett1 # # Fetches pre-built .spm packages from a binary repo, resolves dependencies, # and installs via spm. Alternative to source-based 'apkg'. msg() { printf "%s\n" "[apkg-bin] $*" >&2 } die() { [ "$1" ] && printf "%s\n" "error: $1" >&2 exit 1 } needroot() { [ "$(id -u)" = 0 ] || die "This operation requires root access" } prompt_user() { [ "$APKG_NOPROMPT" ] && return msg "Press ENTER to continue. Press Ctrl+C to abort." read -r _null } needbinsrc() { [ "$APKGBIN_REPO" ] || die "APKGBIN_REPO is not set" } needcachedb() { [ -f "$APKGBIN_CACHE_DIR/APKGBINDB" ] || die "No cached APKGBINDB found. Run 'apkg-bin -S' first." [ -r "$APKGBIN_CACHE_DIR/APKGBINDB" ] || die "No read permission on $APKGBIN_CACHE_DIR/APKGBINDB" } pkg_is_installed() { [ -s "$SPM_PKGDB/$1" ] } pkg_installed_ver() { head -n1 "$SPM_PKGDB/$1" 2>/dev/null } # get field from a APKGBINDB line: bin_fetch_field # 1=namever, 2=size, 3=sha3sum, 4=deps, 5=preinstall, 6=postinstall, 7=desc bin_fetch_field() { _line=$1 _field=$2 _rest=$_line _i=1 while [ "$_i" -lt "$_field" ]; do _rest=${_rest#*|} _i=$((_i + 1)) done case $_field in 7) printf "%s\n" "${_rest#*|}" ;; *) printf "%s\n" "${_rest%%|*}" ;; esac } # find a package name in APKGBINDB, output the full line bin_find_pkg() { _pkg=$1 _line=$(grep "^$_pkg#" "$APKGBIN_CACHE_DIR/APKGBINDB" 2>/dev/null | head -n1) [ "$_line" ] && printf "%s\n" "$_line" } # get full name#version-release for a package name bin_get_pkgid() { _line=$(bin_find_pkg "$1") || return 1 bin_fetch_field "$_line" 1 } # get deps for a package (space-separated names) from APKGBINDB only bin_get_deps() { _line=$(bin_find_pkg "$1" 2>/dev/null) if [ "$_line" ]; then _deps=$(bin_fetch_field "$_line" 4) if [ "$_deps" ]; then printf "%s\n" "$_deps" | tr ',' ' ' fi fi } # get description for a package bin_get_desc() { _line=$(bin_find_pkg "$1") || return 0 bin_fetch_field "$_line" 7 } # get preinstall script (base64) for a package bin_get_preinstall() { _line=$(bin_find_pkg "$1" 2>/dev/null) || return 0 bin_fetch_field "$_line" 5 } # get postinstall script (base64) for a package bin_get_postinstall() { _line=$(bin_find_pkg "$1" 2>/dev/null) || return 0 bin_fetch_field "$_line" 6 } # run a pre/post install script from base64 bin_run_script() { _name=$1 _b64=$2 _tag=$3 # "pre" or "post" [ "$_b64" ] || return 0 msg "Running ${_tag}install script for $_name ..." printf "%s\n" "$_b64" | base64 -d | sh -e || { msg "Warning: ${_tag}install script for $_name failed" } } solve_alias() { [ "$APKG_ALIAS" ] || { printf "%s\n" "$@" return } for _a in "$@"; do _d=$(printf "%s\n" $APKG_ALIAS | tr ' ' '\n' | grep "^$_a:" | head -n1 | awk -F : '{print $2}') printf "%s\n" "${_d:-$_a}" done } bin_checkdep() { # skip packages not in APKGBINDB if ! bin_find_pkg "$1" >/dev/null 2>&1; then [ "$_process" ] && msg "Warning: '$1' not found in APKGBINDB, skipping" return fi _process="$_process $1" for _d in $(solve_alias $(bin_get_deps "$1")); do [ "$_d" = "$1" ] && continue if [ "$_skip_installed" ]; then pkg_is_installed "$_d" && continue fi # cycle detection printf "%s\n" $_process | tr ' ' '\n' | grep -Fxq "$_d" && continue # already resolved printf "%s\n" $DEPS | tr ' ' '\n' | grep -Fxq "$_d" && continue bin_checkdep "$_d" done printf "%s\n" $DEPS | tr ' ' '\n' | grep -Fxq "$1" || DEPS="$DEPS $1" } bin_deplist() { DEPS="" _process="" _skip_installed="" for _p in "$@"; do printf "%s\n" $DEPS | tr ' ' '\n' | grep -Fxq "$_p" || bin_checkdep "$_p" done printf "%s\n" $DEPS | tr ' ' '\n' | grep -v ^$ } bin_sync() { needbinsrc mkdir -p "$APKGBIN_CACHE_DIR" 2>/dev/null [ -w "$APKGBIN_CACHE_DIR" ] || die "No write permission on $APKGBIN_CACHE_DIR" case $APKGBIN_REPO in http://*|https://*|ftp://*) msg "Syncing $APKGBIN_REPO ..." curl -fL -o "$APKGBIN_CACHE_DIR/APKGBINDB.tmp" "$APKGBIN_REPO/APKGBINDB" || { rm -f "$APKGBIN_CACHE_DIR/APKGBINDB.tmp" die "Failed to fetch APKGBINDB from $APKGBIN_REPO" } mv "$APKGBIN_CACHE_DIR/APKGBINDB.tmp" "$APKGBIN_CACHE_DIR/APKGBINDB" ;; *) [ -f "$APKGBIN_REPO/APKGBINDB" ] || die "No APKGBINDB found at $APKGBIN_REPO" msg "Syncing $APKGBIN_REPO (local) ..." cp "$APKGBIN_REPO/APKGBINDB" "$APKGBIN_CACHE_DIR/APKGBINDB" ;; esac msg "Sync complete." } bin_generate() { [ "$APKG_REPO" ] || die "APKG_REPO is not set (needed to read abuild metadata)" [ -d "$APKG_PACKAGE_DIR" ] || die "APKG_PACKAGE_DIR ($APKG_PACKAGE_DIR) not found" [ -r "$APKG_PACKAGE_DIR" ] || die "No read permission on $APKG_PACKAGE_DIR" [ -w "$APKG_PACKAGE_DIR" ] || die "No write permission on $APKG_PACKAGE_DIR" mkdir -p "$APKGBIN_CACHE_DIR" 2>/dev/null [ -w "$APKGBIN_CACHE_DIR" ] || die "No write permission on $APKGBIN_CACHE_DIR" _output="${APKG_PACKAGE_DIR}/APKGBINDB" > "$_output" _count=0 for _spm in "$APKG_PACKAGE_DIR"/*.spm; do [ -f "$_spm" ] || continue _filename=${_spm##*/} _namever=${_filename%.spm} _name=${_namever%%#*} _size=$(stat -c%s "$_spm" 2>/dev/null || wc -c < "$_spm") _sha3=$(sha3sum "$_spm" 2>/dev/null | awk '{print $1}') _deps="" for _r in $APKG_REPO; do if [ -f "$_r/$_name/depends" ]; then for _d in $(grep -Ev '^(#|$)' "$_r/$_name/depends" | awk '{print $1}'); do for _rr in $APKG_REPO; do [ -d "$_rr/$_d" ] && { _deps="$_deps$_d,"; break; } done done _deps=${_deps%,} break fi done _desc="" _pre="" _post="" for _r in $APKG_REPO; do [ -f "$_r/$_name/info" ] && [ ! "$_desc" ] && \ _desc=$(grep '^description:' "$_r/$_name/info" | sed 's/^description:[[:space:]]*//') [ -f "$_r/$_name/preinstall" ] && [ ! "$_pre" ] && \ _pre=$(base64 "$_r/$_name/preinstall" 2>/dev/null | tr -d '\n') [ -f "$_r/$_name/postinstall" ] && [ ! "$_post" ] && \ _post=$(base64 "$_r/$_name/postinstall" 2>/dev/null | tr -d '\n') done printf "%s|%s|%s|%s|%s|%s|%s\n" "$_namever" "$_size" "$_sha3" "$_deps" "$_pre" "$_post" "$_desc" >> "$_output" _count=$((_count + 1)) done # also cache locally so -l/-s/-i work immediately mkdir -p "$APKGBIN_CACHE_DIR" [ "$_output" = "$APKGBIN_CACHE_DIR/APKGBINDB" ] || cp "$_output" "$APKGBIN_CACHE_DIR/APKGBINDB" msg "Wrote APKGBINDB with $_count entries to $_output" } bin_download_pkg() { _pkgid=$1 _name=${_pkgid%%#*} _pkgfile="$_pkgid.spm" mkdir -p "$APKGBIN_CACHE_DIR" 2>/dev/null [ -w "$APKGBIN_CACHE_DIR" ] || die "No write permission on $APKGBIN_CACHE_DIR" case $APKGBIN_REPO in http://*|https://*|ftp://*) _urlfile=$(printf "%s\n" "$_pkgfile" | sed 's/#/%23/g') _target="$APKGBIN_CACHE_DIR/$_pkgfile" if [ "$force" ] || [ ! -f "$_target" ]; then msg "Downloading $APKGBIN_REPO/$_pkgfile" curl -fL -o "$_target.tmp" "$APKGBIN_REPO/$_urlfile" || { rm -f "$_target.tmp" die "Failed to download $_pkgfile from $APKGBIN_REPO" } mv "$_target.tmp" "$_target" fi ;; *) _target="$APKGBIN_REPO/$_pkgfile" [ -f "$_target" ] || die "Package file not found: $_target" ;; esac # verify sha3sum if available in APKGBINDB _line=$(bin_find_pkg "$_name") _expected=$(bin_fetch_field "$_line" 3) if [ "$_expected" ]; then _actual=$(sha3sum "$_target" | awk '{print $1}') [ "$_actual" = "$_expected" ] || die "sha3sum mismatch for $_pkgfile" fi printf "%s\n" "$_target" } bin_install() { _resolve=${1:-1}; shift # 1=resolve deps, 0=no deps needroot needcachedb [ "$1" ] || die "No packages specified" _todo="" for _p in "$@"; do if pkg_is_installed "$_p"; then msg "Package '$_p' is already installed. Skipping." else _todo="$_todo $_p" fi done [ "$_todo" ] || { msg "Nothing to install."; exit 0; } if [ "$_resolve" = 1 ]; then msg "Solving dependencies..." _deps=$(_skip_installed=1 bin_deplist $_todo) else _deps=$_todo fi [ "$_deps" ] || { msg "Nothing to install."; exit 0; } _total=$(printf "%s\n" "$_deps" | wc -l) msg "Installing $_total package(s): $(printf "%s\n" "$_deps" | tr '\n' ' ')" for _d in $_deps; do _pkgid=$(bin_get_pkgid "$_d") || continue msg " $_pkgid" done prompt_user for _d in $_deps; do _pkgid=$(bin_get_pkgid "$_d") || continue _pkgpath=$(bin_download_pkg "$_pkgid") || continue bin_run_script "$_d" "$(bin_get_preinstall "$_d")" pre msg "Installing $_pkgid ..." SPM_ROOT=${APKG_ROOT%/} spm -i "$_pkgpath" || die "Failed to install $_pkgid" bin_run_script "$_d" "$(bin_get_postinstall "$_d")" post done } bin_upgrade() { needroot needcachedb [ "$1" ] || die "No packages specified" for _p in "$@"; do if ! pkg_is_installed "$_p"; then msg "Package '$_p' not installed. Skipping." continue fi _pkgid=$(bin_get_pkgid "$_p") || { msg "Package '$_p' not found in APKGBINDB. Skipping." continue } _pkgpath=$(bin_download_pkg "$_pkgid") || continue _installed=$(pkg_installed_ver "$_p") _available=${_pkgid#*#} [ "$_installed" = "$_available" ] && msg "Package '$_p' is up to date, reinstalling." bin_run_script "$_p" "$(bin_get_preinstall "$_p")" pre msg "Upgrading $_pkgid ..." SPM_ROOT=${APKG_ROOT%/} spm -u "$_pkgpath" || die "Failed to upgrade $_pkgid" bin_run_script "$_p" "$(bin_get_postinstall "$_p")" post done } bin_sysupgrade() { needroot needcachedb msg "Checking for outdated packages..." _outdated=$(_apply_mask=1 bin_list_outdated | awk '{print $1}') [ "$_outdated" ] || { msg "No outdated packages."; exit 0; } msg "Resolving dependencies..." _skip_installed="" DEPS="" for _name in $_outdated; do printf "%s\n" $DEPS | tr ' ' '\n' | grep -Fxq "$_name" || { _process="" bin_checkdep "$_name" } done _all=$(printf "%s\n" $DEPS | tr ' ' '\n' | grep -v ^$ | sort -u) _newpkgs="" _upgradepkgs="" _seen_new="" _seen_up="" for _name in $_all; do if pkg_is_installed "$_name"; then printf "%s\n" $_seen_up | tr ' ' '\n' | grep -Fxq "$_name" && continue _seen_up="$_seen_up $_name" _pkgid=$(bin_get_pkgid "$_name") || continue _installed=$(pkg_installed_ver "$_name") _available=${_pkgid#*#} [ "$_installed" = "$_available" ] && continue _upgradepkgs="$_upgradepkgs $_name" else printf "%s\n" $_seen_new | tr ' ' '\n' | grep -Fxq "$_name" && continue _seen_new="$_seen_new $_name" _newpkgs="$_newpkgs $_name" fi done [ "$_newpkgs" ] && msg "Installing new package(s): $_newpkgs" [ "$_upgradepkgs" ] || { msg "All packages up to date."; exit 0; } _ncount=$(printf "%s\n" $_upgradepkgs | wc -l) msg "Upgrading $_ncount package(s): $(printf "%s\n" $_upgradepkgs | tr '\n' ' ')" prompt_user if [ "$_newpkgs" ]; then for _name in $_newpkgs; do _pkgid=$(bin_get_pkgid "$_name") || continue _pkgpath=$(bin_download_pkg "$_pkgid") || continue bin_run_script "$_name" "$(bin_get_preinstall "$_name")" pre msg "Installing $_pkgid ..." SPM_ROOT=${APKG_ROOT%/} spm -i "$_pkgpath" || die "Failed to install $_pkgid" bin_run_script "$_name" "$(bin_get_postinstall "$_name")" post done fi for _name in $_upgradepkgs; do _pkgid=$(bin_get_pkgid "$_name") || continue _pkgpath=$(bin_download_pkg "$_pkgid") || continue bin_run_script "$_name" "$(bin_get_preinstall "$_name")" pre msg "Upgrading $_pkgid ..." SPM_ROOT=${APKG_ROOT%/} spm -u "$_pkgpath" || die "Failed to upgrade $_pkgid" bin_run_script "$_name" "$(bin_get_postinstall "$_name")" post done msg "System upgrade complete." } bin_list_outdated() { needcachedb [ -d "$SPM_PKGDB" ] || return 0 for _db in "$SPM_PKGDB"/*; do [ -f "$_db" ] || continue _name=${_db##*/} if [ "$APKG_MASK" ] && [ "$_apply_mask" ]; then printf "%s\n" "$APKG_MASK" | tr ' ' '\n' | grep -Fxq "$_name" && continue fi _installed=$(head -n1 "$_db") _line=$(bin_find_pkg "$_name") || continue _namever=$(bin_fetch_field "$_line" 1) _available=${_namever#*#} [ "$_installed" = "$_available" ] || printf "%s\n" "$_name $_installed -> $_available" done } bin_search() { needcachedb _pattern=$1 while IFS= read -r _line; do [ "$_line" ] || continue _namever=$(bin_fetch_field "$_line" 1) _name=${_namever%%#*} _display=$(printf "%s\n" "$_namever" | tr '#' ' ') if [ "$_pattern" ]; then printf "%s\n" "$_name" | grep -q "$_pattern" || continue fi if [ "$verbose" ]; then _desc=$(bin_fetch_field "$_line" 7) printf "%s %s\n" "$_display" "$_desc" else printf "%s\n" "$_name" fi done < "$APKGBIN_CACHE_DIR/APKGBINDB" } bin_list_installed() { [ -d "$SPM_PKGDB" ] || return 0 for _db in "$SPM_PKGDB"/*; do [ -f "$_db" ] || continue _name=${_db##*/} if [ "$verbose" ]; then _ver=$(head -n1 "$_db") printf "%s %s\n" "$_name" "$_ver" else printf "%s\n" "$_name" fi done } bin_clean() { needcachedb _dldir="$APKGBIN_CACHE_DIR" [ -d "$_dldir" ] || { msg "No downloaded packages to clean."; exit 0; } _count=0 for _spm in "$_dldir"/*.spm; do [ -f "$_spm" ] || continue _filename=${_spm##*/} _name=${_filename%%#*} _line=$(bin_find_pkg "$_name" 2>/dev/null) [ "$_line" ] || continue _expected=$(bin_fetch_field "$_line" 3) [ "$_expected" ] || continue _actual=$(sha3sum "$_spm" | awk '{print $1}') if [ "$_actual" != "$_expected" ]; then msg "Removing $_filename (sha3sum mismatch)" rm -f "$_spm" _count=$((_count + 1)) fi done msg "Removed $_count package(s) with sha3sum mismatch." } bin_show_deps() { needcachedb [ "$1" ] || die "No package specified" _deps=$(bin_get_deps "$1") [ "$_deps" ] && printf "%s\n" $_deps } bin_show_deptree() { needcachedb [ "$1" ] || die "No package specified" [ "$(bin_find_pkg "$1")" ] || exit 0 _deps=$(bin_deplist "$1") printf "%s\n" "$_deps" | grep -Fxq "$1" || _deps="$_deps $1" printf "%s\n" "$_deps" | grep -v ^$ } bin_help() { cat << 'EOF' usage: apkg-bin