├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── main.yml ├── LICENSE ├── README ├── contrib ├── kiss-chroot ├── kiss-depends ├── kiss-fork ├── kiss-help ├── kiss-link ├── kiss-maintainer ├── kiss-manifest ├── kiss-new ├── kiss-orphans ├── kiss-outdated ├── kiss-owns ├── kiss-preferred ├── kiss-revdepends └── kiss-size └── kiss /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - [ ] Does this issue occur in master? 2 | 3 | ## Description 4 | 5 | 6 | ## Error message 7 | 8 | ``` 9 | terminal output 10 | ``` 11 | 12 | ## Verbose log 13 | 14 | 1. Download https://github.com/kisslinux/kiss/raw/master/kiss 15 | 2. Run `env KISS_PROMPT=0 sh -x ./kiss > log 2>&1` 16 | 2. Upload the contents of `log`. 17 | 18 | --- 19 | 20 | Issues without attached log file will be closed unless steps to reproduce the 21 | problem are provided in place of it. Issues not using the template will be 22 | closed. Feature requests and otherwise non-issues can disregard this notice. 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Shellcheck 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - name: Run shellcheck. 9 | run: shellcheck kiss contrib/* 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2021 Dylan Araps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | |/ 2 | |\ISS LINUX https://kisslinux.github.io 3 | ________________________________________________________________________________ 4 | 5 | 6 | Package Manager 7 | ________________________________________________________________________________ 8 | 9 | KISS' tiny package manager. 10 | 11 | Documentation: 12 | 13 | - https://kisslinux.github.io/package-manager 14 | - https://kisslinux.github.io/package-system 15 | -------------------------------------------------------------------------------- /contrib/kiss-chroot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Enter a kiss chroot 3 | 4 | log() { 5 | printf '\033[32m->\033[m %s.\n' "$*" 6 | } 7 | 8 | die() { 9 | log "$*" >&2 10 | exit 1 11 | } 12 | 13 | run() { 14 | printf '%s\n' "$*" 15 | "$@" || return "${_ret:=0}" 16 | } 17 | 18 | clean() { 19 | log Unmounting host paths; { 20 | run umount "$1/dev/shm" 2>/dev/null 21 | run umount "$1/dev/pts" 22 | run umount "$1/dev" 23 | run umount "$1/proc" 24 | run umount "$1/run" 25 | run umount "$1/sys/firmware/efi/efivars" 2>/dev/null 26 | run umount "$1/sys" 27 | run umount "$1/tmp" 28 | run umount "$1/etc/resolv.conf" 29 | } 30 | } 31 | 32 | mounted() { 33 | # This is a pure shell mountpoint implementation. We're dealing 34 | # with basic (and fixed/known) input so this doesn't need to 35 | # handle more complex cases. 36 | [ -e "$1" ] || return 1 37 | [ -e /proc/mounts ] || return 1 38 | 39 | while read -r _ target _; do 40 | [ "$target" = "$1" ] && return 0 41 | done < /proc/mounts 42 | 43 | return 1 44 | } 45 | 46 | mmount() { 47 | dest=$1 48 | shift 49 | mounted "$dest" || run mount "$@" "$dest" 50 | } 51 | 52 | main() { 53 | # Ensure input does not end in '/'. 54 | set -- "${1%"${1##*[!/]}"}" 55 | 56 | [ "$1" ] || die Need a path to the chroot 57 | [ -d "$1" ] || die Given path does not exist 58 | [ "$(id -u)" = 0 ] || die Script needs to be run as root 59 | 60 | trap 'clean "${1%"${1##*[!/]}"}"' EXIT INT 61 | 62 | log Mounting host paths; { 63 | mmount "$1/dev" -o bind /dev 64 | mmount "$1/dev/pts" -o bind /dev/pts 65 | mmount "$1/dev/shm" -t tmpfs shmfs 2>/dev/null 66 | mmount "$1/proc" -t proc proc 67 | mmount "$1/run" -t tmpfs tmpfs 68 | mmount "$1/sys" -t sysfs sys 69 | mmount "$1/sys/firmware/efi/efivars" -t efivarfs efivarfs 2>/dev/null 70 | mmount "$1/tmp" -o mode=1777,nosuid,nodev -t tmpfs tmpfs 71 | 72 | touch "$1/etc/resolv.conf" 73 | mmount "$1/etc/resolv.conf" -o bind /etc/resolv.conf 74 | } 75 | 76 | log Entering chroot; { 77 | _ret=1 78 | 79 | run chroot "$1" /usr/bin/env -i \ 80 | HOME=/root \ 81 | TERM="$TERM" \ 82 | SHELL=/bin/sh \ 83 | USER=root \ 84 | LOGNAME=root \ 85 | CFLAGS="${CFLAGS:--march=x86-64 -mtune=generic -pipe -O2}" \ 86 | CXXFLAGS="${CXXFLAGS:--march=x86-64 -mtune=generic -pipe -O2}" \ 87 | MAKEFLAGS="${MAKEFLAGS:--j$(nproc 2>/dev/null || echo 1)}" \ 88 | /bin/sh -l 89 | } || die chroot failed 90 | } 91 | 92 | main "$1" 93 | -------------------------------------------------------------------------------- /contrib/kiss-depends: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ef 2 | # Display a package's dependencies 3 | 4 | pkg=${1:-"${PWD##*/}"} 5 | 6 | kiss list "$pkg" >/dev/null || { 7 | printf 'usage: kiss-depends [pkg]\n' >&2 8 | exit 1 9 | } 10 | 11 | while read -r dep mak || [ "$dep" ]; do 12 | printf '%s%s\n' "$dep" "${mak:+ "$mak"}" 13 | done 2>/dev/null < "$KISS_ROOT/var/db/kiss/installed/$pkg/depends" 14 | -------------------------------------------------------------------------------- /contrib/kiss-fork: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ef 2 | # Copy a package's repository files into the current directory 3 | 4 | pkg=${1:-"${PWD##*/}"} 5 | num=$(printf %d "${2:-0}") 6 | 7 | dir=$(kiss search "$pkg" 2>/dev/null) || { 8 | printf 'usage: [kiss-fork [pkg]] [index]\n' 9 | exit 1 10 | } 11 | 12 | # Globbing is disabled and word splitting is intentional. 13 | # shellcheck disable=2086 14 | set -- $dir 15 | 16 | [ "$num" -ge "$#" ] && { 17 | printf 'index exceeds maximum\n' 18 | exit 1 19 | } 20 | 21 | shift "$num" 22 | 23 | [ "$1" ] || [ -d "$1" ] || { 24 | printf 'failed to locate package\n' 25 | exit 1 26 | } 27 | 28 | printf 'found package in %s\n' "$1" 29 | cp -Lrf "$1" . 30 | printf 'forked package to %s\n' "$PWD/$pkg" 31 | -------------------------------------------------------------------------------- /contrib/kiss-help: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Read KISS documentation 3 | 4 | cd "$KISS_ROOT/usr/share/doc/kiss" 2>/dev/null || { 5 | printf 'Documentation is missing from /usr/share/doc/kiss\n' 6 | exit 1 7 | } 8 | 9 | _q=$1 10 | 11 | ! [ -f "${_q:-.}/index.txt" ] || file=./${_q:-.}/index.txt 12 | ! [ -f "${_q:-.}.txt" ] || file=./${_q:-.}.txt 13 | ! [ -f "${_q:-:}" ] || file=./${_q:-.} 14 | 15 | # Fallback to package READMEs. 16 | # False positive, intended behavior. 17 | # shellcheck disable=2046 18 | [ "$file" ] || { 19 | set -f 20 | set +f -- $(kiss s "${_q##*/}") 21 | file=${1:+"$1/README"} 22 | } 23 | 24 | # Fallback to search (allows 'kiss help firefox' to work). 25 | # False positive, intended behavior. 26 | # shellcheck disable=2046 27 | [ "$file" ] || { 28 | set -f 29 | set +f -- $(find . -name "${_q##*/}.txt") 30 | file=$1 31 | } 32 | 33 | : "${file:=404.txt}" 34 | 35 | cat </dev/null 20 | 21 | # Disable this warning as globbing is disabled and word splitting 22 | # is intentional. This grabs the location of the package's files. 23 | # shellcheck disable=2046 24 | { 25 | # Generate a list of repositories in which the package 26 | # exists. Then 'cd' to the first found directory to do a 27 | # comparison. 28 | set -- $(kiss search "${PWD##*/}"); cd "$1" 29 | 30 | # Error if the package exists nowhere but the current 31 | # directory and this script would create a broken symlink. 32 | [ -z "$2" ] && [ "$PWD" = "$oPWD" ] && { 33 | printf 'error: cannot symlink file to itself\n' 34 | exit 1 35 | } 36 | 37 | # If the first repository in '$KISS_PATH' is the current 38 | # directory, use the second repository in the list. 39 | [ "$PWD" = "$oPWD" ] && shift 40 | 41 | # Finally, make the link to the file in whatever repository 42 | # it was found in. 43 | ln -sf "$1/$file" "$oPWD/$file" 44 | } 45 | 46 | printf 'linked %s to %s\n' "$file" "$1" 47 | -------------------------------------------------------------------------------- /contrib/kiss-maintainer: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ef 2 | # Find the maintainer of a package 3 | 4 | # Use the current directory as the package name if no package is given. 5 | [ "$1" ] || { 6 | export KISS_PATH=${PWD%/*}:$KISS_PATH 7 | set -- "${PWD##*/}" 8 | } 9 | 10 | kiss search "$@" | uniq -u | while read -r repo; do cd "$repo" 11 | m=$(git log -1 version 2>/dev/null) ||: 12 | m=${m##*Author: } 13 | m=${m%%>*} 14 | 15 | [ "$m" ] || continue 16 | 17 | printf '=> %s\n%s>\n' "$PWD" "$m" 18 | done 19 | -------------------------------------------------------------------------------- /contrib/kiss-manifest: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ef 2 | # Display all files owned by a package 3 | 4 | pkg=${1:-"${PWD##*/}"} 5 | 6 | kiss list "$pkg" >/dev/null || { 7 | printf 'usage: kiss-manifest [pkg]\n' >&2 8 | exit 1 9 | } 10 | 11 | cat "$KISS_ROOT/var/db/kiss/installed/$pkg/manifest" 2>/dev/null 12 | -------------------------------------------------------------------------------- /contrib/kiss-new: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Create a boilerplate package 3 | 4 | die() { 5 | printf '%s\n' "$*" 6 | exit 1 7 | } 8 | 9 | log() { 10 | printf '=> %s.\n' "$1" 11 | } 12 | 13 | [ "$1" ] || die "usage: kiss-new [name] [version] [source]" 14 | [ -d "$1" ] && die "error: Package $1 already exists" 15 | mkdir -p "$1" || die "error: Couldn't create directory in $PWD" 16 | cd "$1" || die "error: Couldn't enter directory $1/" 17 | 18 | log "Creating build file"; { 19 | printf '#!/bin/sh -e\n' > build 20 | chmod +x build 21 | } 22 | 23 | log "Creating version file with '${2%% *} 1'"; { 24 | printf '%s\n' "${2%% *} 1" > version 25 | } 26 | 27 | log "Creating sources file with '$3'"; { 28 | printf '%s\n' "$3" > sources 29 | } 30 | 31 | log "Package $1 created in $PWD" 32 | -------------------------------------------------------------------------------- /contrib/kiss-orphans: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # List orphaned packages 3 | 4 | n=' 5 | ' 6 | 7 | cd "$KISS_ROOT/var/db/kiss/installed" 8 | set -- * 9 | 10 | l=$n$( 11 | for pkg do shift 12 | set -- "$@" -e "$pkg" 13 | done 14 | 15 | # Get a list of non-orphans. 16 | grep -Fx "$@" -- */depends | 17 | 18 | { 19 | # Strip filename. 20 | sed s,.\*/depends:,, 21 | 22 | # Exclude packages which are not really orphans. 23 | printf '%s\n' baseinit baselayout busybox bzip2 e2fsprogs gcc \ 24 | git grub kiss make musl 25 | } | 26 | 27 | # Remove duplicates. 28 | sort -u 29 | )$n 30 | 31 | # Generate the list of orphans by finding the inverse of the non-orphan list. 32 | for pkg do shift 33 | case $l in (*"$n$pkg$n"*) 34 | continue 35 | esac 36 | 37 | set -- "$@" "$pkg" 38 | done 39 | 40 | printf '%s\n' "$@" 41 | 42 | -------------------------------------------------------------------------------- /contrib/kiss-outdated: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Check repository for outdated packages 3 | 4 | die() { 5 | printf '%s\n' "$*" >&2 6 | exit 1 7 | } 8 | 9 | mkcd() { 10 | mkdir -p "$1" && cd "$1" 11 | } 12 | 13 | repology_name() { 14 | # Fix any known naming inconsistences between packages and Repology. 15 | remote=$( 16 | # Strip unrelated suffixes. 17 | remote=${1%%-bin} 18 | remote=${remote%%-git} 19 | 20 | # Remote names are all lowercase. 21 | tr '[:upper:]' '[:lower:]' < manpages 152 | # man-pages-posix -> man-pages-posix 153 | remote=manpages 154 | ;; 155 | 156 | netsurf-fb) 157 | remote=netsurf 158 | ;; 159 | 160 | openjpeg2) 161 | # TODO [community]: Rename package? 162 | remote=openjpeg 163 | ;; 164 | 165 | osh) 166 | remote=oil-shell 167 | ;; 168 | 169 | pinentry-dmenu) 170 | remote=pinentry-dmenu-cemkeylan 171 | ;; 172 | 173 | pyqt5) 174 | # TODO [community]: Rename package? 175 | remote=python-qt 176 | ;; 177 | 178 | python2) 179 | remote=python 180 | ;; 181 | 182 | qt5*) 183 | remote=qt 184 | ;; 185 | 186 | rage) 187 | remote=rage-encryption-tool 188 | ;; 189 | 190 | sane) 191 | remote=sane-backends 192 | ;; 193 | 194 | spleen-font) 195 | remote=fonts:spleen 196 | ;; 197 | 198 | sshfs) 199 | remote=fusefs:sshfs 200 | ;; 201 | 202 | surf) 203 | remote=surf-browser 204 | ;; 205 | 206 | st) 207 | remote=st-term 208 | ;; 209 | 210 | sway-no-seat | sway-tiny) 211 | remote=sway 212 | ;; 213 | 214 | terminus-font) 215 | remote=fonts:terminus 216 | ;; 217 | 218 | tiv) 219 | remote=tiv-unclassified 220 | ;; 221 | 222 | unifont) 223 | remote=fonts:unifont 224 | ;; 225 | 226 | webkit2gtk) 227 | # TODO [community]: Rename package? 228 | remote=webkitgtk 229 | ;; 230 | 231 | xf86-*) 232 | remote=xdrv:${remote##*-} 233 | ;; 234 | 235 | xmlsec1) 236 | # TODO [community]: Rename package? 237 | remote=xmlsec 238 | ;; 239 | esac 240 | } 241 | 242 | repology_version() { 243 | [ -f "$1.svg" ] || return 1 244 | read -r remote_ver < "$1.svg" || : 245 | remote_ver=${remote_ver%*} 246 | remote_ver=${remote_ver##*>} 247 | } 248 | 249 | repo_version() { 250 | read -r ver _ 2>/dev/null < "$2/version" || { 251 | printf '%-30s local version not found\n' "$1" >&2 252 | return 1 253 | } 254 | 255 | [ "$ver" != git ] 256 | } 257 | 258 | get_outdated() { 259 | repo=${repo%%/} 260 | printf '\n[Checking Repology for outdated packages in %s]\n\n' "$repo" >&2 261 | 262 | for pkg in */; do 263 | pkg=${pkg%%/} 264 | repology_name "${pkg##*/}" 265 | 266 | [ "$remote" = - ] || 267 | set -- "$@" -z "$remote.svg" \ 268 | "https://repology.org/badge/latest-versions/$remote.svg" 269 | done 270 | 271 | mkcd "$tmp/${repo##*/}" 272 | 273 | curl -SsZ --parallel-max 16 --remote-name-all "$@" || 274 | die 'fatal: network error' 275 | 276 | for _pkg in "$OLDPWD"/*/; do 277 | pkg=${_pkg%%/} 278 | pkg=${pkg##*/} 279 | 280 | repo_version "$pkg" "$_pkg" || continue 281 | repology_name "$pkg" 282 | repology_version "$remote" || continue 283 | 284 | case $remote_ver in *", $ver"* | *"$ver,"* | "$ver" | - | '') 285 | continue 286 | esac 287 | 288 | printf '%-30s %s -> %s\n' "$pkg" "$ver" "$remote_ver" 289 | done 290 | } 291 | 292 | main() { 293 | set -e 294 | 295 | [ "$1" ] || 296 | die 'usage: kiss [ou]tdated /path/to/repo...' 297 | 298 | mkdir -p "${tmp:=${XDG_CACHE_HOME:-"$HOME/.cache"}/kiss/repology}" 299 | 300 | for repo do 301 | old_pwd=$PWD 302 | cd "$repo" 303 | get_outdated 304 | cd "$old_pwd" 305 | done 306 | } 307 | 308 | main "$@" 309 | -------------------------------------------------------------------------------- /contrib/kiss-owns: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Check which package owns a file 3 | 4 | # Follow symlinks to any paths. 5 | case $1 in 6 | /*) 7 | cd -P "$KISS_ROOT${1%/*}" 8 | ;; 9 | 10 | */*) 11 | cd -P "${1%/*}" 12 | ;; 13 | 14 | *) 15 | cd -P . 16 | ;; 17 | esac 18 | 19 | [ -f "$PWD/${1##*/}" ] || { 20 | printf 'usage: kiss-owns [/path/to/file]\n' >&2 21 | exit 1 22 | } 23 | 24 | # Print the full path to the manifest file which contains 25 | # the match to our search. 26 | pkg_owns=$(grep -lFx \ 27 | "$PWD/${1##*/}" \ 28 | "$KISS_ROOT/var/db/kiss/installed/"*/manifest) 29 | 30 | 31 | # Extract the package name from the path above. 32 | pkg_owns=${pkg_owns%/*} 33 | pkg_owns=${pkg_owns##*/} 34 | 35 | printf '%s\n' "$pkg_owns" 36 | -------------------------------------------------------------------------------- /contrib/kiss-preferred: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Lists the owners of all files with conflicts 3 | 4 | kiss a | while read -r _ path; do 5 | if owner=$(kiss owns "$path" 2>/dev/null) && [ "$owner" ]; then 6 | printf '%s %s\n' "$owner" "$path" 7 | 8 | else 9 | printf 'warning: %s has no owner\n' "$path" >&2 10 | fi 11 | done 12 | 13 | -------------------------------------------------------------------------------- /contrib/kiss-revdepends: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # Display packages which depend on package 3 | 4 | [ "$1" ] || set -- "${PWD##*/}" 5 | 6 | cd "$KISS_ROOT/var/db/kiss/installed" 7 | 8 | grep -E "^$1( |$)" -- */depends 9 | 10 | -------------------------------------------------------------------------------- /contrib/kiss-size: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ef 2 | # Show the size on disk for a package 3 | 4 | get_size() { 5 | # Naive function to convert bytes to human readable 6 | # sizes (MB, KB, etc). This is probably wrong in places 7 | # though we can fix this over time. It's a start. 8 | case ${#1} in 9 | [0-3]) hum=$(($1))KB ;; 10 | [4-6]) hum=$(($1 / 1024))MB ;; 11 | [7-9]) hum=$(($1 / 1024 / 1024))GB ;; 12 | *) hum=$(($1)) ;; 13 | esac 14 | 15 | printf '%s\t%s\n' "$hum" "$2" 16 | } 17 | 18 | # Use the current directory as the package name if no package is given. 19 | [ "$1" ] || set -- "${PWD##*/}" 20 | 21 | # Ignore shellcheck as we want the warning's behavior. 22 | # shellcheck disable=2015 23 | kiss list "${1:-null}" >/dev/null || { 24 | printf 'usage: kiss-size [pkg]\n' 25 | exit 1 26 | } 27 | 28 | # Filter directories from manifest and leave only files. 29 | # Directories in the manifest end in a trailing '/'. 30 | # Send the file list to 'xargs' to run through 'du', 31 | # this prevents du from exiting due to too many arguments 32 | sed -e "s|^|$KISS_ROOT|" -e 's|.*/$||' \ 33 | "$KISS_ROOT/var/db/kiss/installed/$1/manifest" \ 34 | | xargs du -sk -- 2>/dev/null | 35 | 36 | # Iterate over each line and convert the byte output to human 37 | # readable (MB, KB, GB, etc). 38 | while read -r size file || { 39 | get_size "$tot" total >&2 40 | break 41 | } do 42 | get_size "$size" "$file" 43 | tot=$((tot + size)) 44 | done 45 | -------------------------------------------------------------------------------- /kiss: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck source=/dev/null 3 | # 4 | # Simple package manager written in POSIX shell for https://kisslinux.github.io 5 | # 6 | # The MIT License (MIT) 7 | # 8 | # Copyright (c) 2019-2021 Dylan Araps 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in all 18 | # copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | # SOFTWARE. 27 | 28 | log() { 29 | printf '%b%s %b%s%b %s\n' \ 30 | "$c1" "${3:-->}" "${c3}${2:+$c2}" "$1" "$c3" "$2" >&2 31 | } 32 | 33 | war() { 34 | log "$1" "$2" "${3:-WARNING}" 35 | } 36 | 37 | die() { 38 | log "$1" "$2" "${3:-ERROR}" 39 | exit 1 40 | } 41 | 42 | run() { 43 | # Print the command, then run it. 44 | printf '%s\n' "$*" 45 | "$@" 46 | } 47 | 48 | contains() { 49 | # Check if a "string list" contains a word. 50 | case " $1 " in *" $2 "*) return 0; esac; return 1 51 | } 52 | 53 | equ() { 54 | # Check if a string is equal to enother. 55 | # This replaces '[ "$var" = str ]' and '[ "$var" != str ]'. 56 | case $1 in "$2") return 0 ;; *) return 1; esac 57 | } 58 | 59 | ok() { 60 | # Check if a string is non-null. 61 | # This replaces '[ "$var" ]', '[ -n "$var" ]'. 62 | case $1 in '') return 1 ;; *) return 0; esac 63 | } 64 | 65 | null() { 66 | # Check if a string is non-null. 67 | # This replaces '[ -z "$var" ]'. 68 | case $1 in '') return 0 ;; *) return 1; esac 69 | } 70 | 71 | tmp_file() { 72 | # Create a uniquely named temporary file and store its absolute path 73 | # in a variable (_tmp_file). 74 | # 75 | # To prevent subshell usage and to handle cases where multiple files 76 | # are needed, this saves the last two temporary files to variables 77 | # for access by the caller (allowing 3 files at once). 78 | _tmp_file_pre_pre=$_tmp_file_pre 79 | _tmp_file_pre=$_tmp_file 80 | _tmp_file=$tmp_dir/$1-$2 81 | 82 | : > "$_tmp_file" || die "$1" "Failed to create temporary file" 83 | } 84 | 85 | tmp_file_copy() { 86 | # Create a uniquely named temporary file and make a duplicate of 87 | # the file in '$3' if it exists. 88 | tmp_file "$1" "$2" 89 | 90 | ! [ -f "$3" ] || cp -f "$3" "$_tmp_file" 91 | } 92 | 93 | prompt() { 94 | null "$1" || log "$1" 95 | 96 | log "Continue?: Press Enter to continue or Ctrl+C to abort" 97 | 98 | # korn-shell does not exit on interrupt of read. 99 | equ "$KISS_PROMPT" 0 || read -r _ || exit 1 100 | } 101 | 102 | mkcd() { 103 | mkdir -p "$@" && cd "$1" 104 | } 105 | 106 | fnr() { 107 | # Replace all occurrences of substrings with substrings. This 108 | # function takes pairs of arguments iterating two at a time 109 | # until everything has been replaced. 110 | _fnr=$1 111 | shift 1 112 | 113 | while :; do case $_fnr-$# in 114 | *"$1"*) _fnr=${_fnr%"$1"*}${2}${_fnr##*"$1"} ;; 115 | *-2) break ;; 116 | *) shift 2 117 | esac done 118 | } 119 | 120 | am_owner() { 121 | # Figure out if we need to change users to operate on 122 | # a given file or directory. 123 | inf=$(ls -ld "$1") || 124 | die "Failed to file information for '$1'" 125 | 126 | # Split the ls output into fields. 127 | read -r _ _ user _ </dev/null; then 157 | _parent=$PWD 158 | cd "$OLDPWD" 159 | else 160 | _parent=${_rpath%/*} 161 | fi 162 | 163 | _rpath=${_parent#"$KISS_ROOT"}/${_rpath##*/} 164 | } 165 | 166 | run_hook() { 167 | # Run all hooks in KISS_HOOK (a colon separated 168 | # list of absolute file paths). 169 | IFS=: 170 | 171 | for hook in ${KISS_HOOK:-}; do case $hook in *?*) 172 | "$hook" "$@" || die "$1 hook failed: '$hook'" 173 | esac done 174 | 175 | unset IFS 176 | } 177 | 178 | run_hook_pkg() { 179 | # Run a hook from the package's database files. 180 | if [ -x "$sys_db/$2/$1" ]; then 181 | log "$2" "Running $1 hook" 182 | "$sys_db/$2/$1" 183 | 184 | elif [ -f "$sys_db/$2/$1" ]; then 185 | war "$2" "skipping $1 hook: not executable" 186 | fi 187 | } 188 | 189 | decompress() { 190 | case $1 in 191 | *.tbz|*.bz2) bzip2 -d ;; 192 | *.lzma) lzma -dc ;; 193 | *.lz) lzip -dc ;; 194 | *.tar) cat ;; 195 | *.tgz|*.gz) gzip -d ;; 196 | *.xz|*.txz) xz -dc ;; 197 | *.zst) zstd -dc ;; 198 | esac < "$1" 199 | } 200 | 201 | sh256() { 202 | # Higher level sh256 function which filters out non-existent 203 | # files (and also directories). 204 | for f do shift 205 | [ -d "$f" ] || [ ! -e "$f" ] || set -- "$@" "$f" 206 | done 207 | 208 | _sh256 "$@" 209 | } 210 | 211 | _sh256() { 212 | # There's no standard utility to generate sha256 checksums. 213 | # This is a simple wrapper around sha256sum, sha256, shasum, 214 | # openssl, digest, ... which will use whatever is available. 215 | # 216 | # All utilities must match 'sha256sum' output. 217 | # 218 | # Example: ' ' 219 | unset hash 220 | 221 | # Skip generation if no arguments. 222 | ! equ "$#" 0 || return 0 223 | 224 | # Set the arguments based on found sha256 utility. 225 | case ${cmd_sha##*/} in 226 | openssl) set -- dgst -sha256 -r "$@" ;; 227 | sha256) set -- -r "$@" ;; 228 | shasum) set -- -a 256 "$@" ;; 229 | digest) set -- -a sha256 "$@" ;; 230 | esac 231 | 232 | IFS=$newline 233 | 234 | # Generate checksums for all input files. This is a single 235 | # call to the utility rather than one per file. 236 | _hash=$("$cmd_sha" "$@") || die "Failed to generate checksums" 237 | 238 | # Strip the filename from each element. 239 | # ' ?' -> '' 240 | for sum in $_hash; do 241 | hash=$hash${hash:+"$newline"}${sum%% *} 242 | done 243 | 244 | printf '%s\n' "$hash" 245 | unset IFS 246 | } 247 | 248 | pkg_find_version() { 249 | ver_pre=$repo_ver 250 | rel_pre=$repo_rel 251 | 252 | pkg_find "$@" 253 | 254 | read -r repo_ver repo_rel 2>/dev/null < "$repo_dir/version" || 255 | die "$1" "Failed to read version file ($repo_dir/version)" 256 | 257 | ok "$repo_rel" || 258 | die "$1" "Release field not found in version file" 259 | 260 | # This belongs somewhere else, for now it can live here. 261 | [ -x "$repo_dir/build" ] || 262 | die "$pkg" "Build file not found or not executable" 263 | } 264 | 265 | pkg_find_version_split() { 266 | pkg_find_version "$@" 267 | 268 | # Split the version on '.+-_' to obtain individual components. 269 | IFS=.+-_ read -r repo_major repo_minor repo_patch repo_ident </dev/null || 454 | git remote add origin "${2%[#@]*}" 455 | 456 | git fetch --depth=1 origin "$com" 457 | git reset --hard FETCH_HEAD 458 | } 459 | 460 | pkg_source_tar() { 461 | # This is a portable shell implementation of GNU tar's 462 | # '--strip-components 1'. Use of this function denotes a 463 | # performance penalty. 464 | tmp_file "$repo_name" tarball 465 | tmp_file "$repo_name" tarball-manifest 466 | 467 | decompress "$1" > "$_tmp_file_pre" || 468 | die "$repo_name" "Failed to decompress $1" 469 | 470 | tar xf "$_tmp_file_pre" || 471 | die "$repo_name" "Failed to extract $1" 472 | 473 | # The sort command filters out all duplicate top-level 474 | # directories from the tarball's manifest. This is an optimization 475 | # as we avoid looping (4000 times for Python(!)). 476 | tar tf "$_tmp_file_pre" | sort -ut / -k1,1 > "$_tmp_file" || 477 | die "$repo_name" "Failed to extract manifest" 478 | 479 | # Iterate over all directories in the first level of the 480 | # tarball's manifest. Each directory is moved up a level. 481 | while IFS=/ read -r dir _; do case ${dir#.} in *?*) 482 | # Skip entries which aren't directories. 483 | [ -d "$dir" ] || continue 484 | 485 | # Move the parent directory to prevent naming conflicts 486 | # with the to-be-moved children. 487 | mv -f "$dir" "$KISS_PID-$dir" 488 | 489 | # Move all children up a directory level. If the mv command 490 | # fails, fallback to copying the remainder of the files. 491 | # 492 | # We can't use '-exec {} +' with any arguments between 493 | # the '{}' and '+' as this is not POSIX. We must also 494 | # use '$0' and '$@' to reference all arguments. 495 | find "$KISS_PID-$dir/." ! -name . -prune \ 496 | -exec sh -c 'mv -f "$0" "$@" .' {} + 2>/dev/null || 497 | 498 | find "$KISS_PID-$dir/." ! -name . -prune \ 499 | -exec sh -c 'cp -fRp "$0" "$@" .' {} + 500 | 501 | # Remove the directory now that all files have been 502 | # transferred out of it. This can't be a simple 'rmdir' 503 | # as we may leave files in here if any were copied. 504 | rm -rf "$KISS_PID-$dir" 505 | esac done < "$_tmp_file" 506 | 507 | # Remove the tarball now that we are done with it. 508 | rm -f "$_tmp_file_pre" 509 | } 510 | 511 | pkg_extract() { 512 | # Extract all source archives to the build directory and copy over any 513 | # local repository files. 514 | # 515 | # NOTE: repo_dir comes from caller. 516 | log "$1" "Extracting sources" 517 | 518 | # arg1: pre-extract 519 | # arg2: package name 520 | # arg3: path to DESTDIR 521 | run_hook pre-extract "$pkg" "$pkg_dir/$pkg" 522 | 523 | while read -r src dest || ok "$src"; do 524 | pkg_source_resolve "$1" "$src" "$dest" >/dev/null 525 | 526 | # Create the source's directories if not null. 527 | null "$_res" || mkcd "$mak_dir/$1/$dest" 528 | 529 | case $_res in 530 | git+*) 531 | cp -LRf "$_des/." . 532 | ;; 533 | 534 | *.tar|*.tar.??|*.tar.???|*.tar.????|*.t?z) 535 | pkg_source_tar "$_res" 536 | ;; 537 | 538 | *?*) 539 | cp -LRf "$_res" . 540 | ;; 541 | esac 542 | done < "$repo_dir/sources" || die "$1" "Failed to extract $_res" 543 | } 544 | 545 | pkg_depends() { 546 | # Resolve all dependencies and generate an ordered list. The deepest 547 | # dependencies are listed first and then the parents in reverse order. 548 | ! contains "$deps" "$1" || return 0 549 | 550 | # Filter out non-explicit, already installed packages. 551 | null "$3" || ok "$2" || contains "$explicit" "$1" || 552 | ! [ -d "$sys_db/$1" ] || return 0 553 | 554 | # Detect circular dependencies and bail out. 555 | # Looks for multiple repeating patterns of (dep dep_parent) (5 is max). 556 | case " $4 " in 557 | *" ${4##* } "*" $1 "\ 558 | *" ${4##* } "*" $1 "\ 559 | *" ${4##* } "*" $1 "\ 560 | *" ${4##* } "*" $1 "\ 561 | *" ${4##* } "*" $1 "*) 562 | die "Circular dependency detected $1 <> ${4##* }" 563 | esac 564 | 565 | # Packages which exist and have depends. 566 | ! _pkg_find "$1" || ! [ -e "$repo_dir/depends" ] || 567 | 568 | # Recurse through the dependencies of the child packages. 569 | while read -r dep dep_type || ok "$dep"; do 570 | ! ok "${dep##\#*}" || pkg_depends "$dep" '' "$3" "$4 $1" "$dep_type" 571 | done < "$repo_dir/depends" || : 572 | 573 | # Add parent to dependencies list. 574 | if ! equ "$2" expl || { equ "$5" make && ! pkg_cache "$1"; }; then 575 | deps="$deps $1" 576 | fi 577 | } 578 | 579 | pkg_order() { 580 | # Order a list of packages based on dependence and take into account 581 | # pre-built tarballs if this is to be called from 'kiss i'. 582 | unset order redro deps 583 | 584 | for pkg do case $pkg in 585 | /*@*.tar.*) deps="$deps $pkg" ;; 586 | *@*.tar.*) deps="$deps $ppwd/$pkg" ;; 587 | */*) die "Not a package' ($pkg)" ;; 588 | *) pkg_depends "$pkg" raw 589 | esac done 590 | 591 | # Filter the list, only keeping explicit packages. The purpose of these 592 | # two loops is to order the argument list based on dependence. 593 | for pkg in $deps; do case " $* " in *" $pkg "*|*" ${pkg##"$ppwd/"} "*) 594 | order="$order $pkg" 595 | redro="$pkg $redro" 596 | esac done 597 | 598 | unset deps 599 | } 600 | 601 | pkg_strip() { 602 | # Strip package binaries and libraries. This saves space on the system as 603 | # well as on the tarballs we ship for installation. 604 | [ -f "$mak_dir/$pkg/nostrip" ] || equ "$KISS_STRIP" 0 && return 605 | 606 | log "$1" "Stripping binaries and libraries" 607 | 608 | # Strip only files matching the below ELF types. This uses 'od' to print 609 | # the first 18 bytes of the file. This is the location of the ELF header 610 | # (up to the ELF type) and contains the type information we need. 611 | # 612 | # Static libraries (.a) are in reality AR archives which contain ELF 613 | # objects. We simply read from the same 18 bytes and assume that the AR 614 | # header equates to an archive containing objects (.o). 615 | # 616 | # Example ELF output ('003' is ELF type): 617 | # 0000000 177 E L F 002 001 001 \0 \0 \0 \0 \0 \0 \0 \0 \0 618 | # 0000020 003 \0 619 | # 0000022 620 | # 621 | # Example AR output (.a): 622 | # 0000000 ! < a r c h > \n / 623 | # 0000020 624 | # 0000022 625 | while read -r file; do [ -h "$pkg_dir/$1$file" ] || case $file in 626 | # Look only in these locations for files of interest (libraries, 627 | # programs, etc). This includes all subdirectories. Old behavior 628 | # would run od on all files (upwards of 4000 for Python). 629 | */sbin/?*[!/]|*/bin/?*[!/]|*/lib/?*[!/]|\ 630 | */lib??/?*[!/]|*/lib???/?*[!/]|*/lib????/?*[!/]) 631 | 632 | case $(od -A o -t c -N 18 "$pkg_dir/$1$file") in 633 | # REL (object files (.o), static libraries (.a)). 634 | *177*E*L*F*0000020\ 001\ *|*\!*\<*a*r*c*h*\>*) 635 | run strip -g -R .comment -R .note "$pkg_dir/$1$file" 636 | ;; 637 | 638 | # EXEC (binaries), DYN (shared libraries). 639 | # Shared libraries keep global symbols in a separate ELF section 640 | # called '.dynsym'. '--strip-all/-s' does not touch the dynamic 641 | # symbol entries which makes this safe to do. 642 | *177*E*L*F*0000020\ 00[23]\ *) 643 | run strip -s -R .comment -R .note "$pkg_dir/$1$file" 644 | ;; 645 | esac 646 | esac done < "$pkg_dir/$1/$pkg_db/$1/manifest" || : 647 | } 648 | 649 | pkg_fix_deps() { 650 | # Dynamically look for missing runtime dependencies by checking each 651 | # binary and library with 'ldd'. This catches any extra libraries and or 652 | # dependencies pulled in by the package's build suite. 653 | log "$1" "looking for dependencies (using ${cmd_elf##*/})" 654 | 655 | tmp_file_copy "$1" depends depends 656 | tmp_file "$1" depends-fixed 657 | 658 | set +f 659 | set -f -- "$sys_db/"*/manifest 660 | 661 | unset _fdep_seen 662 | 663 | # False positive (not a write). 664 | # shellcheck disable=2094 665 | while read -r _file; do [ -h "$_file" ] || case $_file in 666 | # Look only in these locations for files of interest (libraries, 667 | # programs, etc). This includes all subdirectories. Old behavior 668 | # would run ldd on all files (upwards of 4000 for Python). 669 | */sbin/?*[!/]|*/bin/?*[!/]|*/lib/?*[!/]|\ 670 | */lib??/?*[!/]|*/lib???/?*[!/]|*/lib????/?*[!/]) 671 | 672 | # The readelf mode requires ldd's output to resolve the library 673 | # path for a given file. If ldd fails, silently skip the file. 674 | ldd=$(ldd -- "$pkg_dir/$repo_name$_file" 2>/dev/null) || continue 675 | 676 | # Attempt to get information from readelf. If this fails (or we 677 | # are in ldd mode), do full ldd mode (which has the downside of 678 | # listing dependencies of dependencies (and so on)). 679 | elf=$("$cmd_elf" -d "$pkg_dir/$repo_name$_file" 2>/dev/null) || elf=$ldd 680 | 681 | # Iterate over the output of readelf or ldd, extract file names, 682 | # resolve their paths and finally, figure out their owner. 683 | while read -r lib; do case $lib in *NEEDED*\[*\]|*'=>'*) 684 | # readelf: 0x0000 (NEEDED) Shared library: [libjson-c.so.5] 685 | lib=${lib##*\[} 686 | lib=${lib%%\]*} 687 | 688 | # Resolve library path. 689 | # ldd: libjson-c.so.5 => /lib/libjson-c.so.5 ... 690 | case $cmd_elf in 691 | *readelf) lib=${ldd#*" $lib => "} ;; 692 | *) lib=${lib##*=> } ;; 693 | esac 694 | lib=${lib%% *} 695 | 696 | # Skip files owned by libc, libc++ and POSIX. 697 | case ${lib##*/} in 698 | ld-* |\ 699 | lib[cm].so* |\ 700 | libc++.so* |\ 701 | libc++abi.so* |\ 702 | libcrypt.so* |\ 703 | libdl.so* |\ 704 | libgcc_s.so* |\ 705 | libmvec.so* |\ 706 | libpthread.so* |\ 707 | libresolv.so* |\ 708 | librt.so* |\ 709 | libstdc++.so* |\ 710 | libtrace.so* |\ 711 | libunwind.so* |\ 712 | libutil.so* |\ 713 | libxnet.so* |\ 714 | ldd) 715 | continue 716 | esac 717 | 718 | # Skip files we have seen before. 719 | case " $_fdep_seen " in 720 | *" $lib "*) continue ;; 721 | *) _fdep_seen="$_fdep_seen $lib" 722 | esac 723 | 724 | resolve_path "$lib" 725 | 726 | # Skip file if owned by current package 727 | ! pkg_owner -e "$_rpath" manifest || 728 | continue 729 | 730 | ! pkg_owner -e "$_rpath" "$@" || 731 | printf '%s\n' "$_owns" 732 | 733 | esac done < "$_tmp_file" 742 | 743 | # If the depends file was modified, show a diff and replace it. 744 | ! [ -s "$_tmp_file" ] || { 745 | diff -U 3 "$_tmp_file_pre" "$_tmp_file" 2>/dev/null || : 746 | 747 | # Replace the existing depends file if one exists, otherwise this 748 | # just moves the file to its final resting place. 749 | mv -f "$_tmp_file" depends 750 | 751 | # Generate a new manifest as we may be the creator of the depends 752 | # file. This could otherwise be implemented by inserting a line 753 | # at the correct place in the existing manifest. 754 | pkg_manifest "${PWD##*/}" "$pkg_dir" 755 | } 756 | } 757 | 758 | pkg_manifest() { 759 | # Generate the package's manifest file. This is a list of each file 760 | # and directory inside the package. The file is used when uninstalling 761 | # packages, checking for package conflicts and for general debugging. 762 | log "$1" "Generating manifest" 763 | 764 | tmp_file "$1" manifest 765 | 766 | # Create a list of all files and directories. Append '/' to the end of 767 | # directories so they can be easily filtered out later. Also filter out 768 | # all libtool .la files and charset.alias. 769 | { 770 | printf '%s\n' "$2/$1/$pkg_db/$1/manifest" 771 | 772 | ! [ -d "$2/$1/etc" ] || 773 | printf '%s\n' "$2/$1/$pkg_db/$1/etcsums" 774 | 775 | find "$2/$1" ! -path "$2/$1" -type d -exec printf '%s/\n' {} + \ 776 | -o \( ! -type d -a ! -name \*.la -a ! -name charset.alias \) -print 777 | 778 | # Sort the output in reverse. Directories appear after their contents. 779 | } | sort -ur > "$_tmp_file" 780 | 781 | # Remove the prefix from each line. 782 | while read -r file; do 783 | printf '%s\n' "${file#"$2/$1"}" 784 | done < "$_tmp_file" > "$2/$1/$pkg_db/$1/manifest" 785 | } 786 | 787 | pkg_manifest_validate() { 788 | # NOTE: _pkg comes from caller. 789 | log "$_pkg" "Checking if manifest valid" 790 | 791 | while read -r line; do 792 | [ -e "$tar_dir/$_pkg$line" ] || [ -h "$tar_dir/$_pkg$line" ] || { 793 | printf '%s\n' "$line" 794 | set -- "$@" "$line" 795 | } 796 | done < "$pkg_db/$_pkg/manifest" 797 | 798 | for f do 799 | die "$_pkg" "manifest contains $# non-existent files" 800 | done 801 | } 802 | 803 | pkg_manifest_replace() { 804 | # Replace the matching line in the manifest with the desired replacement. 805 | # This used to be a 'sed' call which turned out to be a little 806 | # error-prone in some cases. This new method is a tad slower but ensures 807 | # we never wipe the file due to a command error. 808 | tmp_file "$1" "manifest-replace-${2##*/}" 809 | 810 | while read -r line; do 811 | ! equ "$line" "$2" || line=$3 812 | 813 | printf '%s\n' "$line" 814 | done < "$sys_db/$1/manifest" | sort -r > "$_tmp_file" 815 | 816 | mv -f "$_tmp_file" "$sys_db/$1/manifest" 817 | } 818 | 819 | pkg_etcsums() { 820 | # Generate checksums for each configuration file in the package's /etc/ 821 | # directory for use in "smart" handling of these files. 822 | log "$repo_name" "Generating etcsums" 823 | 824 | # Minor optimization - skip packages without /etc/. 825 | [ -d "$pkg_dir/$repo_name/etc" ] || return 0 826 | 827 | # Create a list of all files in etc but do it in reverse. 828 | while read -r etc; do case $etc in /etc/*[!/]) 829 | set -- "$pkg_dir/$repo_name/$etc" "$@" 830 | esac done < manifest 831 | 832 | sh256 "$@" > etcsums 833 | } 834 | 835 | pkg_tar() { 836 | # Create a tarball from the built package's files. This tarball also 837 | # contains the package's database entry. 838 | # 839 | # NOTE: repo_ comes from caller. 840 | log "$1" "Creating tarball" 841 | 842 | _tar_file=$bin_dir/$1@$repo_ver-$repo_rel.tar.$KISS_COMPRESS 843 | 844 | # Use 'cd' to avoid needing tar's '-C' flag which may not be portable 845 | # across implementations. 846 | cd "$pkg_dir/$1" 847 | 848 | # Create a tarball from the contents of the built package. 849 | tar cf - . | case $KISS_COMPRESS in 850 | bz2) bzip2 -z ;; 851 | gz) gzip -6 ;; 852 | lzma) lzma -z ;; 853 | lz) lzip -z ;; 854 | xz) xz -z ;; 855 | zst) zstd -z ;; 856 | esac > "$_tar_file" 857 | 858 | cd "$OLDPWD" 859 | 860 | log "$1" "Successfully created tarball" 861 | 862 | # arg1: post-package 863 | # arg2: package name 864 | # arg3: path to tarball 865 | run_hook post-package "$1" "$_tar_file" 866 | } 867 | 868 | pkg_build_all() { 869 | # Build packages and turn them into packaged tarballs. 870 | # Order the argument list and filter out duplicates. 871 | 872 | # Mark packages passed on the command-line explicit. 873 | # Also resolve dependencies for all explicit packages. 874 | for pkg do 875 | pkg_depends "$pkg" expl filter 876 | explicit="$explicit $pkg " 877 | done 878 | 879 | # If this is an update, don't always build explicitly passsed packages 880 | # and instead install pre-built binaries if they exist. 881 | ok "$prefer_cache" || explicit_build=$explicit 882 | 883 | set -- 884 | 885 | # If an explicit package is a dependency of another explicit package, 886 | # remove it from the explicit list. 887 | for pkg in $explicit; do 888 | contains "$deps" "$pkg" || set -- "$@" "$pkg" 889 | done 890 | explicit_cnt=$# 891 | explicit=$* 892 | 893 | log "Building: explicit: $*${deps:+, implicit: ${deps## }}" 894 | 895 | # Intentional, globbing disabled. 896 | # shellcheck disable=2046,2086 897 | set -- $deps "$@" 898 | 899 | # Ask for confirmation if extra packages need to be built. 900 | equ "$#" "$explicit_cnt" || prompt 901 | 902 | log "Checking for pre-built dependencies" 903 | 904 | # Install any pre-built dependencies if they exist in the binary 905 | # directory and are up to date. 906 | for pkg in "$@"; do 907 | if ! contains "$explicit_build" "$pkg" && pkg_cache "$pkg"; then 908 | log "$pkg" "Found pre-built binary" 909 | 910 | # Intended behavior. 911 | # shellcheck disable=2030,2031 912 | (export KISS_FORCE=1; args i "$tar_file") 913 | else 914 | set -- "$@" "$pkg" 915 | fi 916 | 917 | shift 918 | done 919 | 920 | for pkg do 921 | pkg_source "$pkg" 922 | 923 | ! [ -f "$repo_dir/sources" ] || pkg_verify "$pkg" 924 | done 925 | 926 | # Finally build and create tarballs for all passed packages and 927 | # dependencies. 928 | for pkg do 929 | log "$pkg" "Building package ($((_build_cur+=1))/$#)" 930 | 931 | pkg_find_version_split "$pkg" 932 | 933 | # arg1: queue-status 934 | # arg2: package name 935 | # arg3: number in queue 936 | # arg4: total in queue 937 | run_hook queue "$pkg" "$_build_cur" "$#" 938 | 939 | ! [ -f "$repo_dir/sources" ] || pkg_extract "$pkg" 940 | 941 | pkg_build "$pkg" 942 | pkg_manifest "$pkg" "$pkg_dir" 943 | pkg_strip "$pkg" 944 | 945 | cd "$pkg_dir/$pkg/$pkg_db/$pkg" 946 | 947 | pkg_fix_deps "$pkg" 948 | pkg_etcsums 949 | pkg_tar "$pkg" 950 | 951 | if equ "${prefer_cache:=0}" 1 || ! contains "$explicit" "$pkg"; then 952 | log "$pkg" "Needed as a dependency or has an update, installing" 953 | 954 | # Intended behavior. 955 | # shellcheck disable=2030,2031 956 | (export KISS_FORCE=1; args i "$pkg") 957 | fi 958 | done 959 | 960 | # Intentional, globbing disabled. 961 | # shellcheck disable=2046,2086 962 | ! equ "${build_install:=1}" 1 || ! equ "${KISS_PROMPT:=1}" 1 || 963 | ! prompt "Install built packages? [$explicit]" || (args i $explicit) 964 | } 965 | 966 | pkg_build() { 967 | # Install built packages to a directory under the package name to 968 | # avoid collisions with other packages. 969 | mkcd "$mak_dir/$1" "$pkg_dir/$1/$pkg_db" 970 | 971 | log "$1" "Starting build" 972 | 973 | # arg1: pre-build 974 | # arg2: package name 975 | # arg3: path to build directory 976 | run_hook pre-build "$1" "$mak_dir/$1" 977 | 978 | # Attempt to create the log file early so any permissions errors are caught 979 | # before the build starts. 'tee' is run in a pipe and POSIX shell has no 980 | # pipe-fail causing confusing behavior when tee fails. 981 | : > "$log_dir/$1-$time-$KISS_PID" 982 | 983 | # Call the build script, log the output to the terminal and to a file. 984 | # There's no PIPEFAIL in POSIX shell so we must resort to tricks like kill. 985 | { 986 | # Give the script a modified environment. Define toolchain program 987 | # environment variables assuming a generic environment by default. 988 | # 989 | # Define DESTDIR and GOPATH to sane defaults as their use is mandatory 990 | # in anything using autotools, meson, cmake, etc. Define KISS_ROOT as 991 | # the sanitized value used internally by the package manager. This is 992 | # safe to join with other paths. 993 | AR="${AR:-ar}" \ 994 | CC="${CC:-cc}" \ 995 | CXX="${CXX:-c++}" \ 996 | NM="${NM:-nm}" \ 997 | RANLIB="${RANLIB:-ranlib}" \ 998 | DESTDIR="$pkg_dir/$1" \ 999 | RUSTFLAGS="--remap-path-prefix=$PWD=. $RUSTFLAGS" \ 1000 | GOFLAGS="-trimpath -modcacherw $GOFLAGS" \ 1001 | GOPATH="$PWD/go" \ 1002 | KISS_ROOT="$KISS_ROOT" \ 1003 | \ 1004 | "$repo_dir/build" "$pkg_dir/$1" "$repo_ver" 2>&1 || { 1005 | log "$1" "Build failed" 1006 | log "$1" "Log stored to $log_dir/$1-$time-$KISS_PID" 1007 | 1008 | # arg1: build-fail 1009 | # arg2: package name 1010 | # arg3: path to build directory 1011 | (run_hook build-fail "$pkg" "$mak_dir/$1") || : 1012 | 1013 | pkg_clean 1014 | kill 0 1015 | } 1016 | } | tee "$log_dir/$1-$time-$KISS_PID" 1017 | 1018 | # Delete the log file if the build succeeded to prevent the directory 1019 | # from filling very quickly with useless logs. 1020 | equ "$KISS_KEEPLOG" 1 || rm -f "$log_dir/$1-$time-$KISS_PID" 1021 | 1022 | # Copy the repository files to the package directory. 1023 | cp -LRf "$repo_dir" "$pkg_dir/$1/$pkg_db/" 1024 | 1025 | log "$1" "Successfully built package" 1026 | 1027 | # arg1: post-build 1028 | # arg2: package name 1029 | # arg3: path to DESTDIR 1030 | run_hook post-build "$1" "$pkg_dir/$1" 1031 | } 1032 | 1033 | pkg_checksum() { 1034 | pkg_source "$1" c 1035 | 1036 | [ -f "$repo_dir/sources" ] || return 0 1037 | 1038 | pkg_checksum_gen 1039 | 1040 | if ok "$hash"; then 1041 | printf '%s\n' "$hash" > "$repo_dir/checksums" 1042 | log "$1" "Generated checksums" 1043 | 1044 | else 1045 | log "$1" "No sources needing checksums" 1046 | fi 1047 | } 1048 | 1049 | pkg_checksum_gen() { 1050 | # Generate checksums for packages. 1051 | # 1052 | # NOTE: repo_ comes from caller. 1053 | while read -r src dest || ok "$src"; do 1054 | pkg_source_resolve "$repo_name" "$src" "$dest" >/dev/null 1055 | 1056 | case ${_res##git+*} in */*[!.]) 1057 | set -- "$@" "$_res" 1058 | esac 1059 | done < "$repo_dir/sources" 1060 | 1061 | _sh256 "$@" 1062 | } 1063 | 1064 | pkg_verify() { 1065 | # Verify all package checksums. This is achieved by generating a new set 1066 | # of checksums and then comparing those with the old set. 1067 | # 1068 | # NOTE: repo_dir comes from caller. 1069 | log "$repo_name" "Verifying sources" 1070 | 1071 | # Generate a new set of checksums to compare against. 1072 | pkg_checksum_gen >/dev/null 1073 | 1074 | # Intentional, globbing disabled. 1075 | # shellcheck disable=2038,2086 1076 | set -- $hash 1077 | 1078 | # Check that the first column (separated by whitespace) match in both 1079 | # checksum files. If any part of either file differs, mismatch. Abort. 1080 | null "$1" || while read -r chk _ || ok "$1"; do 1081 | printf '%s\n%s\n' "- ${chk:-missing}" "+ ${1:-no source}" 1082 | 1083 | equ "$1-${chk:-null}" "$chk-$1" || 1084 | equ "$1-${chk:-null}" "$1-SKIP" || 1085 | die "$repo_name" "Checksum mismatch" 1086 | 1087 | shift "$(($# != 0))" 1088 | done < "$repo_dir/checksums" 1089 | } 1090 | 1091 | pkg_conflicts() { 1092 | # Check to see if a package conflicts with another. 1093 | # _pkg comes from the caller. 1094 | log "$_pkg" "Checking for package conflicts" 1095 | 1096 | tmp_file "$_pkg" manifest-files 1097 | tmp_file "$_pkg" found-conflicts 1098 | 1099 | # Filter the tarball's manifest and select only files. Resolve all 1100 | # symlinks in file paths as well. 1101 | while read -r file; do case $file in *[!/]) 1102 | resolve_path "$file" 1103 | 1104 | printf '%s\n' "$_rpath" 1105 | esac done < "$PWD/$pkg_db/$_pkg/manifest" > "$_tmp_file_pre" 1106 | 1107 | cd "$tar_dir/$_pkg" 1108 | set +f 1109 | set -f "$sys_db"/*/manifest 1110 | 1111 | # Remove the current package from the manifest list. 1112 | fnr " $* " " $sys_db/$_pkg/manifest " " " 1113 | 1114 | # Intentional, globbing disabled. 1115 | # shellcheck disable=2046,2086 1116 | set -- $_fnr 1117 | 1118 | # Return here if there is nothing to check conflicts against. 1119 | ! equ "$#" 0 || return 0 1120 | 1121 | # Store the list of found conflicts in a file as we'll be using the 1122 | # information multiple times. Storing things in the cache dir allows 1123 | # us to be lazy as they'll be automatically removed on script end. 1124 | grep -Fxf "$_tmp_file_pre" -- "$@" 2>/dev/null > "$_tmp_file" || : 1125 | 1126 | # Enable alternatives automatically if it is safe to do so. 1127 | # This checks to see that the package that is about to be installed 1128 | # doesn't overwrite anything it shouldn't in '/var/db/kiss/installed'. 1129 | grep -q ":/var/db/kiss/installed/" "$_tmp_file" || safe=1 1130 | 1131 | if ! equ "$KISS_CHOICE" 1 && equ "$safe" 1 && [ -s "$_tmp_file" ]; then 1132 | # This is a novel way of offering an "alternatives" system. 1133 | # It is entirely dynamic and all "choices" are created and 1134 | # destroyed on the fly. 1135 | # 1136 | # When a conflict is found between two packages, the file 1137 | # is moved to a directory called "choices" and its name 1138 | # changed to store its parent package and its intended 1139 | # location. 1140 | # 1141 | # The package's manifest is then updated to reflect this 1142 | # new location. 1143 | # 1144 | # The 'kiss alternatives' command parses this directory and 1145 | # offers you the CHOICE of *swapping* entries in this 1146 | # directory for those on the filesystem. 1147 | # 1148 | # The alternatives command does the same thing we do here, 1149 | # it rewrites manifests and moves files around to make 1150 | # this work. 1151 | # 1152 | # Pretty nifty huh? 1153 | while IFS=: read -r _ con; do 1154 | printf '%s\n' "Found conflict $con" 1155 | 1156 | # Create the "choices" directory inside of the tarball. 1157 | # This directory will store the conflicting file. 1158 | mkdir -p "$PWD/$cho_db" 1159 | 1160 | # Construct the file name of the "db" entry of the 1161 | # conflicting file. (pkg_name>usr>bin>ls) 1162 | fnr "$con" '/' '>' 1163 | 1164 | # Move the conflicting file to the choices directory 1165 | # and name it according to the format above. 1166 | mv -f "$PWD$con" "$PWD/$cho_db/$_pkg$_fnr" 2>/dev/null || { 1167 | log "File must be in ${con%/*} and not a symlink to it" 1168 | log "This usually occurs when a binary is installed to" 1169 | die "/sbin instead of /usr/bin (example)" 1170 | } 1171 | done < "$_tmp_file" 1172 | 1173 | log "$_pkg" "Converted all conflicts to choices (kiss a)" 1174 | 1175 | # Rewrite the package's manifest to update its location 1176 | # to its new spot (and name) in the choices directory. 1177 | pkg_manifest "$_pkg" "$tar_dir" 1178 | 1179 | elif [ -s "$_tmp_file" ]; then 1180 | log "Package '$_pkg' conflicts with another package" "" "!>" 1181 | log "Run 'KISS_CHOICE=1 kiss i $_pkg' to add conflicts" "" "!>" 1182 | die "as alternatives." "" "!>" 1183 | fi 1184 | } 1185 | 1186 | pkg_alternatives() { 1187 | if equ "$1" -; then 1188 | while read -r pkg path; do 1189 | pkg_swap "$pkg" "$path" 1190 | done 1191 | 1192 | elif ok "$1"; then 1193 | pkg_swap "$@" 1194 | 1195 | else 1196 | # Go over each alternative and format the file 1197 | # name for listing. (pkg_name>usr>bin>ls) 1198 | set +f; for pkg in "$sys_ch/"*; do 1199 | fnr "${pkg##*/}" '>' '/' 1200 | printf '%s %s\n' "${_fnr%%/*}" "/${_fnr#*/}" 1201 | done 1202 | fi 1203 | } 1204 | 1205 | pkg_swap() { 1206 | # Swap between package alternatives. 1207 | [ -d "$sys_db/$1" ] || die "'$1' not found" 1208 | 1209 | fnr "$1$2" '/' '>' 1210 | 1211 | [ -f "$sys_ch/$_fnr" ] || [ -h "$sys_ch/$_fnr" ] || 1212 | die "Alternative '$1 ${2:-null}' doesn't exist" 1213 | 1214 | if [ -f "$KISS_ROOT$2" ]; then 1215 | pkg_owner "/${2#/}" || 1216 | die "File '$2' exists on filesystem but isn't owned" 1217 | 1218 | log "Swapping '$2' from '$_owns' to '$1'" 1219 | 1220 | # Convert the current owner to an alternative and rewrite its manifest 1221 | # file to reflect this. 1222 | cp -Pf "$KISS_ROOT$2" "$sys_ch/$_owns>${_fnr#*>}" 1223 | pkg_manifest_replace "$_owns" "$2" "/$cho_db/$_owns>${_fnr#*>}" 1224 | fi 1225 | 1226 | # Convert the desired alternative to a real file and rewrite the manifest 1227 | # file to reflect this. The reverse of above. 1228 | mv -f "$sys_ch/$_fnr" "$KISS_ROOT/$2" 1229 | pkg_manifest_replace "$1" "/$cho_db/$_fnr" "$2" 1230 | } 1231 | 1232 | file_rwx() { 1233 | # Convert the output of 'ls' (rwxrwx---) to octal. This is simply 1234 | # a 1-9 loop with the second digit being the value of the field. 1235 | # 1236 | # NOTE: This drops setgid/setuid permissions and does not include 1237 | # them in the conversion. This is intentional. 1238 | unset oct o 1239 | 1240 | rwx=$(ls -ld "$1") 1241 | 1242 | for c in 14 22 31 44 52 61 74 82 91; do 1243 | rwx=${rwx#?} 1244 | 1245 | case $rwx in 1246 | [rwx]*) o=$((o + ${c#?})) ;; 1247 | [st]*) o=$((o + 1)) ;; 1248 | esac 1249 | 1250 | case $((${c%?} % 3)) in 0) 1251 | oct=$oct$o 1252 | o=0 1253 | esac 1254 | done 1255 | } 1256 | 1257 | pkg_install_files() { 1258 | # Copy files and create directories (preserving permissions). 1259 | # The 'test $1' will run with '-z' for overwrite and '-e' for verify. 1260 | while { read -r file && _file=$KISS_ROOT$file; } do case $file in 1261 | */) 1262 | # Skip directories if they already exist in the file system. 1263 | # (Think /usr/bin, /usr/lib, etc). 1264 | [ -d "$_file" ] || { 1265 | file_rwx "$2/${file#/}" 1266 | mkdir -m "$oct" "$_file" 1267 | } 1268 | ;; 1269 | 1270 | *) 1271 | # Skip directories and files which exist in verify mode. 1272 | [ -d "$_file" ] || ! test "$1" "$_file" || 1273 | continue 1274 | 1275 | case $file in /etc/*[!/]) 1276 | # Handle /etc/ files in a special way (via a 3-way checksum) to 1277 | # determine how these files should be installed. Do we overwrite 1278 | # the existing file? Do we install it as $file.new to avoid 1279 | # deleting user configuration? etc. 1280 | # 1281 | # This is more or less similar to Arch Linux's Pacman with the 1282 | # user manually handling the .new files when and if they appear. 1283 | pkg_etc || continue 1284 | esac 1285 | 1286 | if [ -h "$_file" ]; then 1287 | # Copy the file to the destination directory overwriting 1288 | # any existing file. 1289 | cp -fP "$2$file" "${_file%/*}/." 1290 | 1291 | else 1292 | # Construct a temporary filename which is a) unique and 1293 | # b) identifiable as related to the package manager. 1294 | __tmp=${_file%/*}/__kiss-tmp-$_pkg-${file##*/}-$KISS_PID 1295 | 1296 | # Copy the file to the destination directory with the 1297 | # temporary name created above. 1298 | cp -fP "$2$file" "$__tmp" && 1299 | 1300 | # Atomically move the temporary file to its final 1301 | # destination. The running processes will either get 1302 | # the old file or the new one. 1303 | mv -f "$__tmp" "$_file" 1304 | fi 1305 | esac || return 1; done 1306 | } 1307 | 1308 | pkg_remove_files() { 1309 | # Remove a file list from the system. This function runs during package 1310 | # installation and package removal. Combining the removals in these two 1311 | # functions allows us to stop duplicating code. 1312 | while read -r file; do 1313 | case $file in /etc/?*[!/]) 1314 | sh256 "$KISS_ROOT/$file" >/dev/null 1315 | 1316 | read -r sum_pkg <&3 ||: 1317 | 1318 | equ "$hash" "$sum_pkg" || { 1319 | printf 'Skipping %s (modified)\n' "$file" 1320 | continue 1321 | } 1322 | esac 1323 | 1324 | _file=${KISS_ROOT:+"$KISS_ROOT/"}${file%%/} 1325 | 1326 | # Queue all directory symlinks for later removal. 1327 | if [ -h "$_file" ] && [ -d "$_file" ]; then 1328 | case $file in /*/*/) 1329 | set -- "$@" "$_file" 1330 | esac 1331 | 1332 | # Remove empty directories. 1333 | elif [ -d "$_file" ]; then 1334 | rmdir "$_file" 2>/dev/null || : 1335 | 1336 | # Remove everything else. 1337 | else 1338 | rm -f "$_file" 1339 | fi 1340 | done 1341 | 1342 | # Remove all broken directory symlinks. 1343 | for sym do 1344 | [ -e "$sym" ] || rm -f "$sym" 1345 | done 1346 | } 1347 | 1348 | pkg_etc() { 1349 | sh256 "$tar_dir/$_pkg$file" "$KISS_ROOT$file" >/dev/null 1350 | 1351 | sum_new=${hash%%"$newline"*} 1352 | sum_sys=${hash#*"$newline"} 1353 | 1354 | read -r sum_old <&3 2>/dev/null ||: 1355 | 1356 | # Compare the three checksums to determine what to do. 1357 | case ${sum_old:-null}${sum_sys:-null}${sum_new} in 1358 | # old = Y, sys = X, new = Y 1359 | "${sum_new}${sum_sys}${sum_old}") 1360 | return 1 1361 | ;; 1362 | 1363 | # old = X, sys = X, new = X 1364 | # old = X, sys = Y, new = Y 1365 | # old = X, sys = X, new = Y 1366 | "${sum_old}${sum_old}${sum_old}"|\ 1367 | "${sum_old:-null}${sum_sys}${sum_sys}"|\ 1368 | "${sum_sys}${sum_old}"*) 1369 | 1370 | ;; 1371 | 1372 | # All other cases. 1373 | *) 1374 | war "$_pkg" "saving $file as $file.new" 1375 | _file=$_file.new 1376 | ;; 1377 | esac 1378 | } 1379 | 1380 | pkg_removable() { 1381 | # Check if a package is removable and die if it is not. 1382 | # A package is removable when it has no dependents. 1383 | log "$1" "Checking if package removable" 1384 | 1385 | cd "$sys_db" 1386 | set +f 1387 | 1388 | ! grep -lFx -- "$1" */depends || 1389 | die "$1" "Not removable, has dependents" 1390 | 1391 | set -f 1392 | cd "$OLDPWD" 1393 | } 1394 | 1395 | pkg_remove() { 1396 | # Remove a package and all of its files. The '/etc' directory is handled 1397 | # differently and configuration files are *not* overwritten. 1398 | [ -d "$sys_db/$1" ] || die "'$1' not installed" 1399 | 1400 | trap_off 1401 | 1402 | # Intended behavior. 1403 | # shellcheck disable=2030,2031 1404 | equ "$KISS_FORCE" 1 || pkg_removable "$1" 1405 | 1406 | # arg1: pre-remove 1407 | # arg2: package name 1408 | # arg3: path to installed database 1409 | run_hook_pkg pre-remove "$1" 1410 | run_hook pre-remove "$1" "$sys_db/$1" 1411 | 1412 | # Make a backup of any etcsums if they exist. 1413 | tmp_file_copy "$1" etcsums-copy "$sys_db/$1/etcsums" 1414 | 1415 | log "$1" "Removing package" 1416 | pkg_remove_files < "$sys_db/$1/manifest" 3< "$_tmp_file" 1417 | 1418 | trap_on 1419 | log "$1" "Removed successfully" 1420 | } 1421 | 1422 | pkg_installable() { 1423 | # Check if a package is removable and die if it is not. 1424 | # A package is removable when all of its dependencies 1425 | # are satisfied. 1426 | log "$1" "Checking if package installable" 1427 | 1428 | # False positive. 1429 | # shellcheck disable=2094 1430 | ! [ -f "$2" ] || 1431 | 1432 | while read -r dep dep_type || ok "$dep"; do 1433 | case "$dep $dep_type" in [!\#]?*\ ) 1434 | ! [ -d "$sys_db/$dep" ] || continue 1435 | 1436 | printf '%s %s\n' "$dep" "$dep_type" 1437 | 1438 | set -- "$1" "$2" "$(($3 + 1))" 1439 | esac 1440 | done < "$2" 1441 | 1442 | case ${3:-0} in [1-9]*) 1443 | die "$1" "Package not installable, missing $3 package(s)" 1444 | esac 1445 | } 1446 | 1447 | pkg_install() { 1448 | # Install a built package tarball. 1449 | # 1450 | # Package installation works similarly to the method used by Slackware in 1451 | # some of their tooling. It's not the obvious solution to the problem, 1452 | # however it is the best solution at this given time. 1453 | # 1454 | # When an installation is an update to an existing package, instead of 1455 | # removing the old version first we do something different. 1456 | # 1457 | # The new version is installed overwriting any files which it has in 1458 | # common with the previously installed version of the package. 1459 | # 1460 | # A "diff" is then generated between the old and new versions and contains 1461 | # any files existing in the old version but not the new version. 1462 | # 1463 | # The package manager then goes and removes these files which leaves us 1464 | # with the new package version in the file system and all traces of the 1465 | # old version gone. 1466 | # 1467 | # For good measure the package manager will then install the new package 1468 | # an additional time. This is to ensure that the above diff didn't contain 1469 | # anything incorrect. 1470 | # 1471 | # This is the better method as it is "seamless". An update to busybox won't 1472 | # create a window in which there is no access to all of its utilities. 1473 | 1474 | # Install can also take the full path to a tarball. We don't need to check 1475 | # the repository if this is the case. 1476 | case $1 in 1477 | *.tar.*) 1478 | [ -f "$1" ] || die "File '$1' does not exist" 1479 | 1480 | tar_file=$1 1481 | _pkg=${1##*/} 1482 | _pkg=${_pkg%@*} 1483 | ;; 1484 | 1485 | *) 1486 | pkg_cache "$1" || die "$1" "Not yet built" 1487 | _pkg=$1 1488 | ;; 1489 | esac 1490 | 1491 | trap_off 1492 | mkcd "$tar_dir/$_pkg" 1493 | 1494 | # The tarball is extracted to a temporary directory where its contents are 1495 | # then "installed" to the filesystem. Running this step as soon as possible 1496 | # allows us to also check the validity of the tarball and bail out early 1497 | # if needed. 1498 | decompress "$tar_file" | tar xf - 1499 | 1500 | # Naively assume that the existence of a manifest file is all that 1501 | # determines a valid KISS package from an invalid one. This should be a 1502 | # fine assumption to make in 99.99% of cases. 1503 | [ -f "$PWD/$pkg_db/$_pkg/manifest" ] || die "Not a valid KISS package" 1504 | 1505 | # Intended behavior. 1506 | # shellcheck disable=2030,2031 1507 | equ "$KISS_FORCE" 1 || { 1508 | pkg_manifest_validate 1509 | pkg_installable "$_pkg" "$PWD/$pkg_db/$_pkg/depends" 1510 | } 1511 | 1512 | # arg1: pre-install 1513 | # arg2: package name 1514 | # arg3: path to extracted package 1515 | run_hook pre-install "$_pkg" "$PWD" 1516 | 1517 | pkg_conflicts 1518 | 1519 | log "$_pkg" "Installing package (${tar_file##*/})" 1520 | 1521 | # If the package is already installed (and this is an upgrade) make a 1522 | # backup of the manifest and etcsums files. 1523 | tmp_file_copy "$_pkg" manifest-copy "$sys_db/$_pkg/manifest" 1524 | tmp_file_copy "$_pkg" etcsums-copy "$sys_db/$_pkg/etcsums" 1525 | tmp_file "$_pkg" manifest-diff 1526 | 1527 | tar_man=$PWD/$pkg_db/$_pkg/manifest 1528 | 1529 | # Generate a list of files which exist in the currently installed manifest 1530 | # but not in the newer (to be installed) manifest. 1531 | grep -vFxf "$tar_man" "$_tmp_file_pre_pre" > "$_tmp_file" 2>/dev/null ||: 1532 | 1533 | # Reverse the manifest file so that we start shallow and go deeper as we 1534 | # iterate over each item. This is needed so that directories are created 1535 | # going down the tree. 1536 | tmp_file "$_pkg" manifest-reverse 1537 | sort "$tar_man" > "$_tmp_file" 1538 | 1539 | if 1540 | # Install the package's files by iterating over its manifest. 1541 | pkg_install_files -z "$PWD" < "$_tmp_file" 3< "$_tmp_file_pre_pre" && 1542 | 1543 | # This is the aforementioned step removing any files from the old 1544 | # version of the package if the installation is an update. Each file 1545 | # type has to be specially handled to ensure no system breakage occurs. 1546 | pkg_remove_files < "$_tmp_file_pre" 3< "$_tmp_file_pre_pre" && 1547 | 1548 | # Install the package's files a second time to fix any mess caused by 1549 | # the above removal of the previous version of the package. 1550 | pkg_install_files -e "$PWD" < "$_tmp_file" 3< "$_tmp_file_pre_pre" 1551 | 1552 | then 1553 | trap_on 1554 | 1555 | # arg1: post-install 1556 | # arg2: package name 1557 | # arg3: path to installed package database 1558 | run_hook_pkg post-install "$_pkg" 1559 | run_hook post-install "$_pkg" "$sys_db/$_pkg" 1560 | 1561 | log "$_pkg" "Installed successfully" 1562 | 1563 | else 1564 | pkg_clean 1565 | log "$_pkg" "Failed to install package." ERROR 1566 | die "$_pkg" "Filesystem now dirty, manual repair needed." 1567 | fi 1568 | } 1569 | 1570 | pkg_update() { 1571 | log "Updating repositories" 1572 | 1573 | # Create a list of all repositories. 1574 | # Intentional, globbing disabled. 1575 | # shellcheck disable=2046,2086 1576 | { IFS=:; set -- $KISS_PATH; unset IFS; } 1577 | 1578 | # Update each repository in '$KISS_PATH'. 1579 | for repo do 1580 | if git -C "$repo" rev-parse 'HEAD@{upstream}' >/dev/null 2>&1; then 1581 | repo_type=git 1582 | 1583 | # Get the Git repository root directory. 1584 | subm=$(git -C "$repo" rev-parse --show-superproject-working-tree) 1585 | repo=$(git -C "${subm:-"$repo"}" rev-parse --show-toplevel) 1586 | 1587 | elif ! [ -d "$repo" ]; then 1588 | continue 1589 | 1590 | else 1591 | unset repo_type 1592 | fi 1593 | 1594 | pkg_update_repo 1595 | done 1596 | 1597 | pkg_upgrade 1598 | } 1599 | 1600 | pkg_update_repo() { 1601 | cd "$repo" || die "Repository '$repo' inaccessible" 1602 | 1603 | contains "$repos" "$PWD" || { 1604 | repos="$repos $PWD" 1605 | 1606 | log "$PWD" " " 1607 | 1608 | am_owner "$PWD" || { 1609 | printf 'Need "%s" to update\n' "$user" 1610 | set -- as_user 1611 | } 1612 | 1613 | # arg1: pre-update 1614 | # arg2: need su? 1615 | # arg3: owner 1616 | # env: PWD is path to repository 1617 | run_hook pre-update "$#" "$user" 1618 | 1619 | case $repo_type in git) 1620 | pkg_update_git "$@" 1621 | esac 1622 | 1623 | # arg1: post-update 1624 | # env: PWD is path to repository 1625 | run_hook post-update 1626 | } 1627 | } 1628 | 1629 | pkg_update_git() { 1630 | # Display whether or not signature verification is enabled. 1631 | case $(git config --get merge.verifySignatures) in true) 1632 | printf 'Signature verification enabled.\n' 1633 | esac 1634 | 1635 | "$@" git pull 1636 | "$@" git submodule update --remote --init -f 1637 | } 1638 | 1639 | pkg_upgrade() { 1640 | log "Checking for new package versions" 1641 | set +f 1642 | 1643 | for pkg in "$sys_db/"*; do set -f 1644 | pkg_find_version "${pkg##*/}" "" "" "$sys_db" 1645 | pkg_find_version "${pkg##*/}" 1646 | 1647 | # Detect repository orphans (installed packages with no 1648 | # associated repository). 1649 | case $repo_dir in */var/db/kiss/installed/*) 1650 | _repo_orp="$_repo_orp$newline${pkg##*/}" 1651 | esac 1652 | 1653 | # Compare installed packages to repository packages. 1654 | equ "$ver_pre-$rel_pre" "$repo_ver-$repo_rel" || { 1655 | set -- "$@" "${pkg##*/}" 1656 | 1657 | printf '%s %s => %s\n' \ 1658 | "${pkg##*/}" "$ver_pre-$rel_pre" "$repo_ver-$repo_rel" 1659 | } 1660 | done 1661 | 1662 | case $_repo_orp in *?*) 1663 | war "Packages without repository$_repo_orp" 1664 | esac 1665 | 1666 | build_install=0 1667 | prefer_cache=1 1668 | 1669 | ! contains "$*" kiss || { 1670 | log "Detected package manager update" 1671 | log "The package manager will be updated first" 1672 | 1673 | prompt 1674 | pkg_build_all kiss 1675 | 1676 | log "Updated the package manager" 1677 | log "Re-run 'kiss update' to update your system" 1678 | return 0 1679 | } 1680 | 1681 | for _ do 1682 | pkg_order "$@" 1683 | 1684 | # Intentional, globbing disabled. 1685 | # shellcheck disable=2046,2086 1686 | set -- $order 1687 | 1688 | prompt "Packages to update ($#): $*" 1689 | pkg_build_all "$@" 1690 | log "Updated all packages" 1691 | return 0 1692 | done 1693 | 1694 | log "Nothing to do" 1695 | } 1696 | 1697 | pkg_clean() { 1698 | # Clean up on exit or error. This removes everything related to the build. 1699 | # If _KISS_LVL is (1) we are the top-level process - the entire cache will 1700 | # be removed. If _KISS_LVL is any other value, remove only the tar directory. 1701 | case ${KISS_DEBUG:-0}-${_KISS_LVL:-1} in 1702 | 0-1) rm -rf "$proc" ;; 1703 | 0-*) rm -rf "$tar_dir" 1704 | esac 1705 | } 1706 | 1707 | pkg_help_ext() { 1708 | log 'Installed extensions (kiss-* in PATH)' 1709 | 1710 | # Intentional, globbing disabled. 1711 | # shellcheck disable=2046,2030,2031 1712 | set -- $(pkg_find kiss-\* all -x "$PATH") 1713 | 1714 | # To align descriptions figure out which extension has the longest 1715 | # name by doing a simple 'name > max ? name : max' on the basename 1716 | # of the path with 'kiss-' stripped as well. 1717 | # 1718 | # This also removes any duplicates found in '$PATH', picking the 1719 | # first match. 1720 | for path do 1721 | p=${path#*/kiss-} 1722 | 1723 | case " $seen " in *" $p "*) 1724 | shift 1725 | continue 1726 | esac 1727 | 1728 | seen=" $seen $p " 1729 | max=$((${#p} > max ? ${#p}+1 : max)) 1730 | done 1731 | 1732 | # Print each extension, grab its description from the second line 1733 | # in the file and align the output based on the above max. 1734 | for path do 1735 | # Open the extension as a file descriptor. 1736 | exec 3< "$path" 1737 | 1738 | # Grab the second line in the extension. 1739 | { read -r _ && IFS=\#$IFS read -r _ cmt; } <&3 1740 | 1741 | printf "%b->%b %-${max}s %s\\n" \ 1742 | "$c1" "$c3" "${path#*/kiss-}" "$cmt" 1743 | done >&2 1744 | } 1745 | 1746 | trap_on() { 1747 | # Catch errors and ensure that build files and directories are cleaned 1748 | # up before we die. This occurs on 'Ctrl+C' as well as success and error. 1749 | trap trap_INT INT 1750 | trap trap_EXIT EXIT 1751 | } 1752 | 1753 | trap_INT() { 1754 | run_hook SIGINT 1755 | exit 1 1756 | } 1757 | 1758 | trap_EXIT() { 1759 | pkg_clean 1760 | run_hook SIGEXIT 1761 | } 1762 | 1763 | trap_off() { 1764 | # Block being able to abort the script with 'Ctrl+C'. Removes all risk of 1765 | # the user aborting a package install/removal leaving an incomplete package 1766 | # installed. 1767 | trap "" INT EXIT 1768 | } 1769 | 1770 | args() { 1771 | # Parse script arguments manually. This is rather easy to do in our case 1772 | # since the first argument is always an "action" and the arguments that 1773 | # follow are all package names. 1774 | action=$1 1775 | shift "$(($# != 0))" 1776 | 1777 | # Ensure that arguments do not contain invalid characters. Wildcards can 1778 | # not be used here as they would conflict with kiss extensions. 1779 | case $action in 1780 | a|alternatives) 1781 | case $1 in *\**|*\!*|*\[*|*\ *|*\]*|*/*|*"$newline"*) 1782 | die "Invalid argument: '!*[ ]/\\n' ($1)" 1783 | esac 1784 | ;; 1785 | 1786 | b|build|c|checksum|d|download|i|install|l|list|r|remove) 1787 | for _arg do case ${action%%"${action#?}"}-$_arg in 1788 | i-*\!*|i-*\**|i-*\[*|i-*\ *|i-*\]*|i-*"$newline"*) 1789 | die "Invalid argument: '!*[ ]\\n' ('$_arg')" 1790 | ;; 1791 | 1792 | [!i]-*\!*|[!i]-*\**|[!i]-*\[*|[!i]-*\ *|\ 1793 | [!i]-*\]*|[!i]-*/*|[!i]-*"$newline"*) 1794 | die "Invalid argument: '!*[ ]/\\n' ('$_arg')" 1795 | ;; 1796 | esac done 1797 | 1798 | # When no arguments are given on the command-line, use the basename 1799 | # of the current directory as the package name and add the parent 1800 | # directory to the running process' KISS_PATH. 1801 | case ${action%%"${action#?}"}-$# in [!l]-0) 1802 | export KISS_PATH=${PWD%/*}:$KISS_PATH 1803 | set -- "${PWD##*/}" 1804 | esac 1805 | 1806 | # Search the installed database first when removing packages. Dependency 1807 | # files may differ when repositories change. Removal is not dependent on 1808 | # the state of the repository. 1809 | case $action in r|remove) 1810 | export KISS_PATH=$sys_db:$KISS_PATH 1811 | esac 1812 | 1813 | # Order the argument list based on dependence. 1814 | pkg_order "$@" 1815 | 1816 | # Intentional, globbing disabled. 1817 | # shellcheck disable=2046,2086 1818 | set -- $order 1819 | ;; 1820 | esac 1821 | 1822 | # Need to increment _KISS_LVL here to ensure we don't wipe the cache 1823 | # early by non-asroot invocations. 1824 | export _KISS_LVL=$((_KISS_LVL + 1)) 1825 | 1826 | # Rerun the script as root with a fixed environment if needed. We sadly 1827 | # can't run singular functions as root so this is needed. 1828 | # 1829 | # Intended behavior. 1830 | # shellcheck disable=2030,2031 1831 | case $action in a|alternatives|i|install|r|remove) 1832 | if ok "$1" && ! am_owner "$KISS_ROOT/"; then 1833 | trap_off 1834 | 1835 | as_user env \ 1836 | LOGNAME="$user" \ 1837 | HOME="$HOME" \ 1838 | XDG_CACHE_HOME="$XDG_CACHE_HOME" \ 1839 | KISS_COMPRESS="$KISS_COMPRESS" \ 1840 | KISS_PATH="$KISS_PATH" \ 1841 | KISS_FORCE="$KISS_FORCE" \ 1842 | KISS_ROOT="$KISS_ROOT" \ 1843 | KISS_CHOICE="$KISS_CHOICE" \ 1844 | KISS_COLOR="$KISS_COLOR" \ 1845 | KISS_TMPDIR="$KISS_TMPDIR" \ 1846 | KISS_PID="$KISS_PID" \ 1847 | _KISS_LVL="$_KISS_LVL" \ 1848 | "$0" "$action" "$@" 1849 | 1850 | trap_on 1851 | return 1852 | fi 1853 | esac 1854 | 1855 | # Actions can be abbreviated to their first letter. This saves keystrokes 1856 | # once you memorize the commands. 1857 | case $action in 1858 | a|alternatives) pkg_alternatives "$@" ;; 1859 | b|build) pkg_build_all "$@" ;; 1860 | c|checksum) for pkg do pkg_checksum "$pkg"; done ;; 1861 | d|download) for pkg do pkg_source "$pkg"; done ;; 1862 | H|help-ext) pkg_help_ext "$@" ;; 1863 | i|install) for pkg do pkg_install "$pkg"; done ;; 1864 | l|list) pkg_list_version "$@" ;; 1865 | r|remove) for pkg in $redro; do pkg_remove "$pkg"; done ;; 1866 | s|search) for pkg do pkg_find "$pkg" all; done ;; 1867 | u|update) pkg_update ;; 1868 | U|upgrade) pkg_upgrade ;; 1869 | v|version) printf '5.5.28\n' ;; 1870 | 1871 | '') 1872 | log 'kiss [a|b|c|d|i|l|r|s|u|U|v] [pkg]...' 1873 | log 'alternatives List and swap alternatives' 1874 | log 'build Build packages' 1875 | log 'checksum Generate checksums' 1876 | log 'download Download sources' 1877 | log 'install Install packages' 1878 | log 'list List installed packages' 1879 | log 'remove Remove packages' 1880 | log 'search Search for packages' 1881 | log 'update Update the system and repositories' 1882 | log 'upgrade Update the system' 1883 | log 'version Package manager version' 1884 | 1885 | printf '\nRun "kiss [H|help-ext]" to see all actions\n' 1886 | ;; 1887 | 1888 | *) 1889 | # _KISS_LVL must be reset here so the that any extensions 1890 | # which call the package manager do not increment the value 1891 | # further than the parent instance. 1892 | pkg_find "kiss-$action*" "" -x "$PATH" 1893 | _KISS_LVL=0 "$repo_dir" "$@" 1894 | ;; 1895 | esac 1896 | } 1897 | 1898 | create_tmp_dirs() { 1899 | # Root directory. 1900 | KISS_ROOT=${KISS_ROOT%"${KISS_ROOT##*[!/]}"} 1901 | 1902 | # This allows for automatic setup of a KISS chroot and will 1903 | # do nothing on a normal system. 1904 | mkdir -p "$KISS_ROOT/" 2>/dev/null || : 1905 | 1906 | # System package database. 1907 | sys_db=$KISS_ROOT/${pkg_db:=var/db/kiss/installed} 1908 | sys_ch=$KISS_ROOT/${cho_db:=var/db/kiss/choices} 1909 | 1910 | # Top-level cache directory. 1911 | cac_dir=${XDG_CACHE_HOME:-"${HOME%"${HOME##*[!/]}"}/.cache"} 1912 | cac_dir=${cac_dir%"${cac_dir##*[!/]}"}/kiss 1913 | 1914 | # Persistent cache directories. 1915 | src_dir=$cac_dir/sources 1916 | log_dir=$cac_dir/logs/${time%-*} 1917 | bin_dir=$cac_dir/bin 1918 | 1919 | # Top-level Temporary cache directory. 1920 | proc=${KISS_TMPDIR:="$cac_dir/proc"} 1921 | proc=${proc%"${proc##*[!/]}"}/$KISS_PID 1922 | 1923 | # Temporary cache directories. 1924 | mak_dir=$proc/build 1925 | pkg_dir=$proc/pkg 1926 | tar_dir=$proc/extract 1927 | tmp_dir=$proc/tmp 1928 | 1929 | mkdir -p "$src_dir" "$log_dir" "$bin_dir" \ 1930 | "$mak_dir" "$pkg_dir" "$tar_dir" "$tmp_dir" 1931 | } 1932 | 1933 | main() { 1934 | # Globally disable globbing and enable exit-on-error. 1935 | set -ef 1936 | 1937 | # Color can be disabled via the environment variable KISS_COLOR. Colors are 1938 | # also automatically disabled if output is being used in a pipe/redirection. 1939 | equ "$KISS_COLOR" 0 || ! [ -t 2 ] || { 1940 | c1='\033[1;33m' 1941 | c2='\033[1;34m' 1942 | c3='\033[m' 1943 | } 1944 | 1945 | # Store the original working directory to ensure that relative paths 1946 | # passed by the user on the command-line properly resolve to locations 1947 | # in the filesystem. 1948 | ppwd=$PWD 1949 | 1950 | # Never know when you're gonna need one of these. 1951 | newline=" 1952 | " 1953 | 1954 | # Defaults for environment variables. 1955 | : "${KISS_COMPRESS:=gz}" 1956 | : "${KISS_PID:=$$}" 1957 | : "${LOGNAME:?POSIX requires LOGNAME be set}" 1958 | 1959 | # Figure out which 'sudo' command to use based on the user's choice or what 1960 | # is available on the system. 1961 | cmd_su=${KISS_SU:-"$( 1962 | command -v ssu || 1963 | command -v sudo || 1964 | command -v doas || 1965 | command -v su 1966 | )"} || cmd_su=su 1967 | 1968 | # Figure out which utility is available to dump elf information. 1969 | cmd_elf=${KISS_ELF:-"$( 1970 | command -v readelf || 1971 | command -v eu-readelf || 1972 | command -v llvm-readelf 1973 | )"} || cmd_elf=ldd 1974 | 1975 | # Figure out which sha256 utility is available. 1976 | cmd_sha=${KISS_CHK:-"$( 1977 | command -v openssl || 1978 | command -v sha256sum || 1979 | command -v sha256 || 1980 | command -v shasum || 1981 | command -v digest 1982 | )"} || die "No sha256 utility found" 1983 | 1984 | # Figure out which download utility is available. 1985 | cmd_get=${KISS_GET:-"$( 1986 | command -v aria2c || 1987 | command -v axel || 1988 | command -v curl || 1989 | command -v wget || 1990 | command -v wget2 1991 | )"} || die "No download utility found (aria2c, axel, curl, wget, wget2)" 1992 | 1993 | # Store the date and time of script invocation to be used as the name of 1994 | # the log files the package manager creates during builds. 1995 | time=$(date +%Y-%m-%d-%H:%M) 1996 | 1997 | create_tmp_dirs 1998 | trap_on 1999 | 2000 | args "$@" 2001 | } 2002 | 2003 | main "$@" 2004 | --------------------------------------------------------------------------------