#!/usr/bin/env bash set -euo pipefail # gen_simple_inplace.sh # Generate PEP 503 "simple" indexes in-place for existing directories: # //index.html # Optionally generates: # /index.html die() { echo "ERROR: $*" >&2; exit 1; } SIMPLE_DIR="" ONLY_PKG="" NO_TOP=0 DO_VERIFY=0 VERIFY_ONLY=0 while [ $# -gt 0 ]; do case "$1" in --simple-dir) SIMPLE_DIR="${2:-}"; shift 2 ;; --pkg) ONLY_PKG="${2:-}"; shift 2 ;; --no-top) NO_TOP=1; shift ;; --verify) DO_VERIFY=1; shift ;; --verify-only) VERIFY_ONLY=1; DO_VERIFY=1; shift ;; -h|--help) cat <<'EOF' Usage: ./gen_simple_inplace.sh --simple-dir /var/www/.../simple ./gen_simple_inplace.sh --simple-dir /var/www/.../simple --pkg cffi --no-top ./gen_simple_inplace.sh --simple-dir /var/www/.../simple --verify ./gen_simple_inplace.sh --simple-dir /var/www/.../simple --verify-only Options: --no-top Don't rewrite /simple/index.html --verify Verify that each /index.html href exists and sha256 matches --verify-only Verify only (do not regenerate any index.html) EOF exit 0 ;; *) die "Unknown arg: $1" ;; esac done [ -n "$SIMPLE_DIR" ] || die "--simple-dir is required" [ -d "$SIMPLE_DIR" ] || die "Not a directory: $SIMPLE_DIR" is_artifact() { case "$1" in *.whl|*.tar.gz|*.zip|*.tgz) return 0 ;; *) return 1 ;; esac } verify_pkg_index() { local pkgdir="$1" local pkgname="$2" local idx="$pkgdir/index.html" local errs=0 if [ ! -f "$idx" ]; then echo "VERIFY FAIL [$pkgname]: missing index.html" >&2 return 1 fi # Extract href targets like: filename#sha256=.... # We assume filenames don't contain quotes/spaces (true for wheels/sdists typically). while IFS= read -r href; do [ -n "$href" ] || continue local file="${href%%#sha256=*}" local want="${href##*#sha256=}" if [ ! -f "$pkgdir/$file" ]; then echo "VERIFY FAIL [$pkgname]: missing file: $file" >&2 errs=$((errs+1)) continue fi local got got="$(sha256sum "$pkgdir/$file" | awk '{print $1}')" if [ "$got" != "$want" ]; then echo "VERIFY FAIL [$pkgname]: sha256 mismatch for $file" >&2 echo " want: $want" >&2 echo " got : $got" >&2 errs=$((errs+1)) fi done < <( # Pull href="..."; keep only those containing #sha256= grep -oE 'href="[^"]+"' "$idx" \ | sed -E 's/^href="(.*)"$/\1/' \ | grep -E '#sha256=' || true ) if [ "$errs" -gt 0 ]; then echo "VERIFY FAIL [$pkgname]: $errs error(s)" >&2 return 1 fi echo "VERIFY OK [$pkgname]" return 0 } write_pkg_index() { local pkgdir="$1" local pkgname="$2" local idx="$pkgdir/index.html" # Collect artifacts in that pkg dir mapfile -t files < <(find "$pkgdir" -maxdepth 1 -type f \( -name '*.whl' -o -name '*.tar.gz' -o -name '*.zip' -o -name '*.tgz' \) -printf '%f\n' | sort) # If no artifacts, skip (or you can still write an empty index if you want) [ "${#files[@]}" -gt 0 ] || return 0 { echo "" echo "${pkgname}" for bn in "${files[@]}"; do # hash the file in place sha="$(sha256sum "$pkgdir/$bn" | awk '{print $1}')" printf '%s
\n' "$bn" "$sha" "$bn" done echo "" } > "$idx" } # Determine package dirs pkg_dirs=() if [ -n "$ONLY_PKG" ]; then [ -d "$SIMPLE_DIR/$ONLY_PKG" ] || die "Package dir not found: $SIMPLE_DIR/$ONLY_PKG" pkg_dirs+=("$SIMPLE_DIR/$ONLY_PKG") else # All subdirs except hidden ones while IFS= read -r d; do pkg_dirs+=("$d") done < <(find "$SIMPLE_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | sort) fi if [ "$VERIFY_ONLY" -eq 0 ]; then # Generate per-package indexes for d in "${pkg_dirs[@]}"; do pkg="$(basename "$d")" write_pkg_index "$d" "$pkg" done # Top index (optional) if [ "$NO_TOP" -eq 0 ] && [ -z "$ONLY_PKG" ]; then top="$SIMPLE_DIR/index.html" { echo "" echo "Simple Index" for d in "${pkg_dirs[@]}"; do pkg="$(basename "$d")" if find "$d" -maxdepth 1 -type f \( -name '*.whl' -o -name '*.tar.gz' -o -name '*.zip' -o -name '*.tgz' \) | grep -q .; then printf '%s
\n' "$pkg" "$pkg" fi done echo "" } > "$top" fi fi if [ "$DO_VERIFY" -eq 1 ]; then vfail=0 for d in "${pkg_dirs[@]}"; do pkg="$(basename "$d")" if ! verify_pkg_index "$d" "$pkg"; then vfail=1 fi done if [ "$vfail" -ne 0 ]; then die "Verification failed" fi fi if [ "$VERIFY_ONLY" -eq 1 ]; then echo "OK: verified indexes under: $SIMPLE_DIR" else echo "OK: indexes generated under: $SIMPLE_DIR" fi