pkgrepo (8155B)
1 #!/bin/sh 2 # pkgrepo - binary package repository database generator 3 # Reads a CRUX-style ports tree and generates a repo DB + checksums 4 # Usage: pkgrepo [options] 5 6 PORTSDIR="${PORTSDIR:-}" 7 PKGDIR="${PKGDIR:-/var/pkg/repo}" 8 PKGEXT="${PKGEXT:-.pkg.tar.gz .pkg.tar.xz .pkg.tar.bz2}" 9 VERBOSE=0 10 11 usage() { 12 cat <<EOF 13 Usage: pkgrepo [options] 14 15 Options: 16 -p <dir> Ports tree directory (auto-detected from prt-get if omitted) 17 -r <dir> Package repo directory (default: $PKGDIR) 18 -d <file> Output DB file (default: \$PKGDIR/repo.db) 19 -e <exts> Package extensions, space-separated (default: .pkg.tar.gz .pkg.tar.xz .pkg.tar.bz2) 20 -v Verbose output 21 -h Show this help 22 23 Environment: 24 PORTSDIR Ports tree root(s), space-separated (auto-detected from prt-get if not set) 25 PKGDIR Built packages directory 26 PKGEXT Package extensions to search, space-separated (default: .pkg.tar.gz .pkg.tar.xz .pkg.tar.bz2) 27 28 29 The ports tree should follow standard CRUX layout: 30 \$PORTSDIR/<name>/Pkgfile 31 \$PORTSDIR/<name>/pre-install (optional) 32 \$PORTSDIR/<name>/post-install (optional) 33 34 Dependencies are read from the Pkgfile comment: 35 # Depends on: foo bar baz 36 EOF 37 exit 0 38 } 39 40 log() { 41 [ "$VERBOSE" -eq 1 ] && printf '%s\n' "$*" >&2 42 } 43 44 err() { 45 printf 'pkgrepo: error: %s\n' "$*" >&2 46 } 47 48 die() { 49 err "$*" 50 exit 1 51 } 52 53 # --------------------------------------------------------------------------- 54 # _mktemp -- portable temp file creation (GNU + BSD) 55 # --------------------------------------------------------------------------- 56 _mktemp() { 57 mktemp 2>/dev/null || mktemp -t pkgrepo 2>/dev/null || \ 58 die "mktemp failed" 59 } 60 61 # --------------------------------------------------------------------------- 62 # _sha256 <file> -- print SHA256 hash, portable across implementations 63 # --------------------------------------------------------------------------- 64 _sha256() { 65 if command -v sha256sum >/dev/null 2>&1; then 66 sha256sum "$1" | awk '{print $1}' 67 elif command -v shasum >/dev/null 2>&1; then 68 shasum -a 256 "$1" | awk '{print $1}' 69 elif command -v sha256 >/dev/null 2>&1; then 70 sha256 -q "$1" 71 else 72 die "no sha256 tool found (need sha256sum, shasum, or sha256)" 73 fi 74 } 75 76 # Parse a Pkgfile and emit fields to stdout 77 # Output: name, version, release, deps (space-separated), desc 78 parse_pkgfile() { 79 _pkgfile="$1" 80 _name="" _version="" _release="" _deps="" _desc="" 81 82 while IFS= read -r _line; do 83 case "$_line" in 84 name=*) _name="${_line#name=}" ;; 85 version=*) _version="${_line#version=}" ;; 86 release=*) _release="${_line#release=}" ;; 87 '# Depends on:'*) _deps="${_line#*# Depends on:}" 88 # trim leading whitespace 89 _deps="${_deps#"${_deps%%[! ]*}"}" 90 # normalize to space-separated (handles commas) 91 _deps=$(printf '%s' "$_deps" | tr ',' ' ' | tr -s ' ') ;; 92 '# Description:'*) _desc="${_line#*# Description:}" 93 _desc="${_desc#"${_desc%%[! ]*}"}" ;; 94 esac 95 done < "$_pkgfile" 96 97 printf 'name:%s\nversion:%s\nrelease:%s\ndeps:%s\ndesc:%s\n' \ 98 "$_name" "$_version" "$_release" "$_deps" "$_desc" 99 } 100 101 # Find the built package file for a given name/version/release 102 # PKGEXT is a space-separated list of extensions to try 103 find_pkg() { 104 _n="$1" _v="$2" _r="$3" 105 for _ext in $PKGEXT; do 106 # Try exact match first 107 _path="$PKGDIR/${_n}#${_v}-${_r}${_ext}" 108 [ -f "$_path" ] && { printf '%s' "$_path"; return 0; } 109 # Try glob (version/release may differ if pkg was rebuilt) 110 for _f in "$PKGDIR/${_n}#"*"${_ext}"; do 111 [ -f "$_f" ] && { printf '%s' "$_f"; return 0; } 112 done 113 done 114 return 1 115 } 116 117 # Generate repo DB 118 gen_db() { 119 _tmpdb=$(_mktemp) || die "failed to create temp file" 120 _tmpsum=$(_mktemp) || die "failed to create temp file" 121 _count=0 122 _missing=0 123 124 for _portsdir in $PORTSDIR; do 125 [ -d "$_portsdir" ] || continue 126 for _pkgfile in "$_portsdir"/*/Pkgfile; do 127 [ -f "$_pkgfile" ] || continue 128 _portdir="${_pkgfile%/Pkgfile}" 129 _portname="${_portdir##*/}" 130 131 log "processing: $_portname" 132 133 # Parse fields from Pkgfile 134 _info=$(parse_pkgfile "$_pkgfile") 135 _name=$(printf '%s' "$_info" | awk -F: '/^name:/{print $2}') 136 _version=$(printf '%s' "$_info" | awk -F: '/^version:/{print $2}') 137 _release=$(printf '%s' "$_info" | awk -F: '/^release:/{print $2}') 138 _deps=$(printf '%s' "$_info" | awk -F: '/^deps:/{print $2}') 139 _desc=$(printf '%s' "$_info" | awk -F: '/^desc:/{print $2}') 140 141 # Skip if no name/version (malformed Pkgfile) 142 [ -z "$_name" ] || [ -z "$_version" ] && { 143 err "skipping $_portname: missing name or version" 144 continue 145 } 146 147 # Find built package 148 _pkgpath=$(find_pkg "$_name" "$_version" "$_release") 149 if [ $? -ne 0 ] || [ -z "$_pkgpath" ]; then 150 log " WARNING: no built package found for $_name-$_version-$_release" 151 _missing=$((_missing + 1)) 152 continue 153 fi 154 155 _pkgfile_basename="${_pkgpath##*/}" 156 157 # Checksum the package 158 _sum=$(_sha256 "$_pkgpath") 159 160 # Embed pre/post install scripts as base64 in the DB stanza 161 _pre="" 162 _post="" 163 if [ -f "$_portdir/pre-install" ]; then 164 _pre=$(base64 < "$_portdir/pre-install" | tr -d '\n') 165 fi 166 if [ -f "$_portdir/post-install" ]; then 167 _post=$(base64 < "$_portdir/post-install" | tr -d '\n') 168 fi 169 170 # Write stanza to DB 171 { 172 printf 'name:%s\n' "$_name" 173 printf 'version:%s\n' "$_version" 174 printf 'release:%s\n' "$_release" 175 printf 'file:%s\n' "$_pkgfile_basename" 176 printf 'deps:%s\n' "$_deps" 177 printf 'desc:%s\n' "$_desc" 178 [ -n "$_pre" ] && printf 'pre-install:b64:%s\n' "$_pre" 179 [ -n "$_post" ] && printf 'post-install:b64:%s\n' "$_post" 180 printf '\n' 181 } >> "$_tmpdb" 182 183 # Write checksum 184 printf '%s %s\n' "$_sum" "$_pkgfile_basename" >> "$_tmpsum" 185 186 _count=$((_count + 1)) 187 log " -> $_pkgfile_basename [$_sum]" 188 done 189 done 190 191 mv "$_tmpdb" "$DBFILE" 192 mv "$_tmpsum" "$SUMFILE" 193 194 printf 'pkgrepo: wrote %d packages to %s\n' "$_count" "$DBFILE" 195 [ "$_missing" -gt 0 ] && \ 196 printf 'pkgrepo: WARNING: %d ports skipped (no built package)\n' "$_missing" 197 } 198 199 # --- main --- 200 201 while getopts 'p:r:d:e:vh' _opt; do 202 case "$_opt" in 203 p) PORTSDIR="$OPTARG" ;; 204 r) PKGDIR="$OPTARG" ;; 205 d) DBFILE="$OPTARG" ;; 206 e) PKGEXT="$OPTARG" ;; 207 v) VERBOSE=1 ;; 208 h) usage ;; 209 *) usage ;; 210 esac 211 done 212 213 # Strip trailing slashes for clean path concatenation 214 PKGDIR="${PKGDIR%/}" 215 216 # Auto-detect ports directory from prt-get if not set by env or -p flag 217 if [ -z "$PORTSDIR" ]; then 218 if command -v prt-get >/dev/null 2>&1 && [ -f /etc/prt-get.conf ]; then 219 PORTSDIR=$(grep '^prtdir ' /etc/prt-get.conf | awk '{print $2}' | tr '\n' ' ') 220 PORTSDIR="${PORTSDIR% }" 221 fi 222 : "${PORTSDIR:=/usr/ports}" 223 fi 224 225 # Strip trailing slashes from each ports directory word 226 _PDIRS="" 227 for _pd in $PORTSDIR; do 228 _PDIRS="${_PDIRS} ${_pd%/}" 229 done 230 PORTSDIR="${_PDIRS# }" 231 232 # Validate at least one ports directory exists 233 _ports_ok=0 234 for _pd in $PORTSDIR; do 235 [ -d "$_pd" ] && { _ports_ok=1; break; } 236 done 237 [ "$_ports_ok" -eq 1 ] || die "no ports directory found (tried: $PORTSDIR)" 238 239 [ -d "$PKGDIR" ] || die "package repo dir not found: $PKGDIR" 240 241 # Default DBFILE/SUMFILE after PKGDIR is finalized (so -r takes effect) 242 : "${DBFILE:=$PKGDIR/repo.db}" 243 : "${SUMFILE:=$PKGDIR/repo.sha256}" 244 245 gen_db